5.成员方法

5.面向对象-封装-成员方法


5.1 知识点

成员方法的声明和定义

  • 成员方法(函数)用于表现对象行为

  • 在类的语句块中声明

  • 规则与函数声明规则相同

  • 受到访问修饰符影响

  • 返回值与参数类型不受限

  • 方法数量不做限制

  • 定义位置可选:

    • .h 类内声明并定义(适用于小型函数、极短的构造函数)
    • .h 内声明,在 .cpp 中定义(推荐)
    • 虽然C++中允许以上两种写法,但是都建议大家使用在 .h 内声明,在 .cpp 中定义(之后学习的内联函数和模板类除外)
#pragma once
//#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名全部打下 点用下划线替代
//#define PERSON_H

#include <iostream>
using namespace std;
class Person
{
    //基本概念
    //成员方法(函数) 用来表现对象行为
    //1.声明在类语句块中
    //2.是用来描述对象的行为的
    //3.规则和函数声明规则相同
    //4.受到访问修饰符规则影响
    //5.返回值参数不做限制
    //6.方法数量不做限制
    //7.定义位置有几种选择


public:
    int age = 0;
    string name = "";
    //当前朋友数
    int friendNum = 0;
    //代表具体的朋友对象
    Person* friends[30] = {nullptr};


    //	方式一:
    //	在.h类内声明并定义(小型函数、极短的构造函数)
    //判断是否成年
    bool IsAdult()
    {
        return age >= 18;
    }

    ////一个人的行为 —— 说话
    //void Speak(string str)
    //{
    //	cout << name << "说:" << str;
    //}

    ////记录添加朋友的方法 
    //void AddFriend(Person* p)
    //{
    //	//不能超过最大容量 进行一个判断
    //	if (friendNum < 30)
    //	{
    //		friends[friendNum] = p;
    //		++friendNum;
    //	}
    //	
    //}

    ////显示我朋友的名字的方法
    //void ShowFriends()
    //{
    //	for (int i = 0; i < friendNum; i++)
    //	{
    //		cout << "我的第" << i + 1 << "个朋友" << endl;
    //		cout << friends[i]->name << endl;
    //	}
    //}


    //	方式二:
    //	在.h内声明,在.cpp中定义(推荐使用该方法)
    //	虽然C++中允许以上两种写法,但是都建议大家使用方式二(之后学习的内联函数和模板类除外)
    //bool IsAdult();
    inline void Speak(string str);
    void AddFriend(Person* p);
    void ShowFriends();
    void ChangeAge(int age);
};

//#endif
#include "Person.h"
//bool Person::IsAdult()
//{
//	return age >= 18;
//}

inline void Person::Speak(string str)
{
    cout << name << "说:" << str;
}

void Person::AddFriend(Person* p)
{
    //不能超过最大容量 进行一个判断
    if (friendNum < 30)
    {
        friends[friendNum] = p;
        ++friendNum;
    }
}

void Person::ShowFriends()
{
    for (int i = 0; i < friendNum; i++)
    {
        cout << "我的第" << i + 1 << "个朋友" << endl;
        cout << friends[i]->name << endl;
    }
}

//当传入参数名和成员变量同名时 我们通过在前面加上 this指针 来进行区分时成员变量 还是外部传入的参数
void Person::ChangeAge(int age)
{
    this->age = age;
}

成员方法的使用

  • 必须先实例化对象,再通过对象调用方法
// 栈上
Person p;
p.age = 16;
p.name = "韬老狮";
if (p.IsAdult())
    cout << "已经成年1" << endl;
p.ChangeAge(19);
if (p.IsAdult())
    cout << "已经成年2" << endl;  // 已经成年2
p.ShowFriends();

// 堆上
Person* p2 = new Person();
p2->age = 14;
p2->name = "小申猪猪";
if (p2->IsAdult())
    cout << "已经成年3" << endl;

p.AddFriend(p2);
p.ShowFriends();  // 我的第1个朋友 小申猪猪
// 注意 p2 的释放时机需自行管理

内联成员方法

  • 在类中将成员方法变成内联函数的作用和外部一样

  • 内联函数回顾:

    • 使用 inline 关键字对函数进行声明或定义。
    • 主要作用是告诉编译器,在调用该函数时,将函数的代码直接插入到调用位置,而非执行常规函数调用过程,以减少函数调用开销。
    • 适用于小型、频繁调用的函数。
  • 注意:

    • 直接在 .h 类内定义的成员方法,默认是内联的(如知识点一中讲解的 Speak 方法)。
    • 在类外定义成员方法时,可显式加上 inline 关键字使其成为内联函数,但需注意:
      1. 是否真正内联由编译器决定,inline 只是建议。
      2. 内联函数不能过大,否则可能导致代码膨胀(例如简单的 IsAdult 方法可直接写在 .h 文件中作为内联)。
      3. 内联函数不适用于递归函数,否则可能导致无限展开。
      4. 虚函数不能是内联函数。
  • 内联函数知识点主要做了解即可,大多数时候无需显式使用内联,对代码影响较小。

头文件中的防止重复编译命令

什么是头文件重复编译?

头文件可能被多个源文件(.cpp)或同一个源文件间接多次包含。若没有防护机制,会导致编译错误(通常是重复定义错误)。

示例说明

  • 编译器通常会在 .h 文件自动添加 #pragma once 指令。
  • 若手动删除该指令(如 Person.h),并在代码中重复包含:
    #include "Person.h"
    #include "Person.h"  // 或通过其他头文件间接包含
    
    会触发重复定义错误。

C++ 防止重复编译的两种方式

  1. 传统宏定义方式:
    使用 #ifndef#define#endif 组合:

    #ifndef 自定义宏名
    #define 自定义宏名
    
    // 头文件内容
    class Person {};
    
    #endif
    

    自定义宏名规则: 通常使用全大写文件名,用下划线替换点号(如 PERSON_H)。

    例如:

    //#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名包括口追 全部打下 点用下划线替代
    //#define PERSON_H
    // 
    // class Person(){}
    // 
    // 
    //#endif
    
  2. 现代 #pragma once 指令:
    直接在头文件顶部添加:

    #pragma once
    
    // 头文件内容
    class Person {};
    

    推荐使用: 语法更简洁,且由编译器保证唯一性(避免宏名冲突)。


5.2 知识点代码

Person.h

#pragma once
//#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名包括口追 全部打下 点用下划线替代
//#define PERSON_H

#include <iostream>
using namespace std;
class Person
{
    //基本概念
    //成员方法(函数) 用来表现对象行为
    //1.声明在类语句块中
    //2.是用来描述对象的行为的
    //3.规则和函数声明规则相同
    //4.受到访问修饰符规则影响
    //5.返回值参数不做限制
    //6.方法数量不做限制
    //7.定义位置有几种选择


public:
    int age = 0;
    string name = "";
    //当前朋友数
    int friendNum = 0;
    //代表具体的朋友对象
    Person* friends[30] = {nullptr};


    //	方式一:
    //	在.h类内声明并定义(小型函数、极短的构造函数)
    //判断是否成年
    bool IsAdult()
    {
        return age >= 18;
    }

    ////一个人的行为 —— 说话
    //void Speak(string str)
    //{
    //	cout << name << "说:" << str;
    //}

    ////记录添加朋友的方法 
    //void AddFriend(Person* p)
    //{
    //	//不能超过最大容量 进行一个判断
    //	if (friendNum < 30)
    //	{
    //		friends[friendNum] = p;
    //		++friendNum;
    //	}
    //	
    //}

    ////显示我朋友的名字的方法
    //void ShowFriends()
    //{
    //	for (int i = 0; i < friendNum; i++)
    //	{
    //		cout << "我的第" << i + 1 << "个朋友" << endl;
    //		cout << friends[i]->name << endl;
    //	}
    //}


    //	方式二:
    //	在.h内声明,在.cpp中定义(推荐使用该方法)
    //	虽然C++中允许以上两种写法,但是都建议大家使用方式二(之后学习的内联函数和模板类除外)
    //bool IsAdult();
    inline void Speak(string str);
    void AddFriend(Person* p);
    void ShowFriends();
    void ChangeAge(int age);
};

//#endif

Person.cpp

#include "Person.h"
//bool Person::IsAdult()
//{
//	return age >= 18;
//}

inline void Person::Speak(string str)
{
    cout << name << "说:" << str;
}

void Person::AddFriend(Person* p)
{
    //不能超过最大容量 进行一个判断
    if (friendNum < 30)
    {
        friends[friendNum] = p;
        ++friendNum;
    }
}

void Person::ShowFriends()
{
    for (int i = 0; i < friendNum; i++)
    {
        cout << "我的第" << i + 1 << "个朋友" << endl;
        cout << friends[i]->name << endl;
    }
}

//当传入参数名和成员变量同名时 我们通过在前面加上 this指针 来进行区分时成员变量 还是外部传入的参数
void Person::ChangeAge(int age)
{
    this->age = age;
}

Lesson05_面向对象_封装_成员方法.cpp

#include <iostream>
#include "Person.h"
#include "Person.h"
//#include "A.h" 有可能引用的某个头文件 也引用了 Person.h
int main()
{
    #pragma region 知识点一 成员方法的声明和定义

    //基本概念
    //成员方法(函数) 用来表现对象行为
    //1.声明在类语句块中
    //2.是用来描述对象的行为的
    //3.规则和函数声明规则相同
    //4.受到访问修饰符规则影响
    //5.返回值参数不做限制
    //6.方法数量不做限制
    //7.定义位置有几种选择
    //	方式一:
    //	在.h类内声明并定义(小型函数、极短的构造函数)

    //	方式二:
    //	在.h内声明,在.cpp中定义(推荐使用该方法)

    //	虽然C++中允许以上两种写法,但是都建议大家使用方式二(之后学习的内联函数和模板类除外)

    #pragma endregion

    #pragma region 知识点二 成员方法的使用

    //成员方法 必须实例化出对象 再通过对象来使用 相当于该对象执行了某个行为

    //栈上
    Person p;
    p.age = 16;
    p.name = "韬老狮";
    if (p.IsAdult())
        cout << "已经成年1" << endl;
    p.ChangeAge(19);
    if (p.IsAdult())
        cout << "已经成年2" << endl;//已经成年2
    p.ShowFriends();

    //堆上
    Person* p2 = new Person();
    p2->age = 14;
    p2->name = "小申猪猪";
    if (p2->IsAdult())
        cout << "已经成年3" << endl;

    p.AddFriend(p2);
    p.ShowFriends();//我的第1个朋友 小申猪猪
    // 注意p2要自己看情况释放

    #pragma endregion

    #pragma region 知识点三 内联成员方法
    //在类中将成员方法变成内联函数的作用和外部一样

    //内联函数回顾:
    //使用 inline 关键字对函数进行声明或定义
    //主要作用是告诉编译器,在调用该函数时,将函数的代码直接插入到调用该函数的位置
    //而不是执行常规的函数调用过程。
    //这样做的目的是减少函数调用的开销
    //适用于哪些小型的、频繁调用的函数

    //注意:
    //直接在.h类内定义的成员方法,默认是内联的
    //也就是我们在知识点一中讲解的方式一定义函数的方式 比如Speak方法
    //当我们在类外定义成员方式时,可以显示的加上inline关键词让其成为内联函数
    //但是在使用时需要特别注意:
    //1.是否真正内联是由编译器决定的,inline只是建议
    //2.内联函数不能过大,否则可能导致代码膨胀 比如判断是否成年IsAdult方法是个简单的函数 可以直接写在.h文件作为内联
    //3.内联函数不适用于递归函数,否则可能导致无限展开
    //4.虚函数不能是内联

    //因此对于内联函数知识点来说,主要做了解即可
    //大多数时候,完全可以不用显示使用内联,影响不会太大

    #pragma endregion

    #pragma region 知识点四 头文件中的防止重复编译命令

    //什么是头文件重复编译
    //头文件可能会被多个源文件(.cpp)或同一个源文件间接多次包含
    //如果果没有适当的防护机制,会导致编译错误(一般会报重复定义的错误)

    //说人话
    // 创建.h文件时 编译器会自动帮我们在最前面加#pragma once指令
    // 如果没有这条指令 比如Person.h中把这条指令删了
    // 然后连续引用#include两次"Person.h"(或者#include "A.h" A.h中又引用了 Person.h)
    // 就会报错 例如下面
    // 
    //#include "Person.h"
    //#include "Person.h"
    ////#include "A.h" 有可能引用的某个头文件 也引用了 Person.h

    //C++中为了防止重复编译
    //提供过了2种方式

    //1.使用传统宏定义来防止头文件重复编译
    //	#ifndef 自定义宏名  // 如果没有定义 自定义宏名
    //	#define 自定义宏名  // 定义 自定义宏名

    //	#endif  // 结束条件编译

    //例如:
    //#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名包括口追 全部打下 点用下划线替代
    //#define PERSON_H
    // 
    // class Person(){}
    // 
    // 
    //#endif



    //2.使用更加方便的pragma指令来防止重复编译 一般使用这一种
    //	#pragma once

    #pragma endregion
}

5.3 练习题

基于成员变量练习题:为人类定义说话、走路、吃饭等方法

#pragma once
#include <iostream>
using namespace std;

class Person
{
public:
    void Speak(string str);
    void Walk();
    void Eat();
public:
    string name = "";
    float height = 0;
    int age = 0;
    string homeAddress = "";
};
#include "Person.h"

void Person::Speak(string str)
{
    cout << name << "说:" << str << endl;
}

void Person::Walk()
{
    cout << name << "开始走路了" << endl;
}

void Person::Eat(Food food)
{
    cout << name << "开始吃东西了" << endl;
}

基于成员变量练习题:为学生类定义学习、吃饭等方法

#pragma once
#include <iostream>
using namespace std;

class Student
{
public:
    void Learn();
    void Eat();
public:
    string name = "";
    int num = 0;
    int age = 0;
    Student* deskmate = nullptr;
};
#include "Student.h"

void Student::Learn()
{
    cout << name << "开始学习了" << endl;
}

void Student::Eat(Food food)
{
    cout << name << "开始吃东西了" << endl;
}

定义一个食物类,有名称,热量等特征 思考如何和人类以及学生类联系起来

#pragma once
#include <iostream>
using namespace std;

//注意 以下会照成头文件的循环依赖 照成报错 
// 因为Person.h和Student.h中存在#include "Food.h"
//#include "Person.h"
//#include "Student.h"
//需要前向声明

//前向声明 告诉编译器 有这两个类 具体实现 先不管
class Person;
class Student;

//注意:
//前向声明不是必须的
//只要.h头文件include不循环依赖其实就不会报错 比如Person和Student引用Food 保证Food不引用Person和Student就行
//比如如果想使用内联函数 必须在.h文件中声明和定义的话 保证不循环依赖即可
//在cpp文件各种引用.h无所谓 因为是要真正使用了

class Food
{
public:
    string name;
    int kaLuLi;

public:
    void BeiEat(Person p);
    void BeiEat(Student s);
};
#include "Food.h"
#include "Person.h"
#include "Student.h"

void Food::BeiEat(Person p) 
{
    cout << name << "被人" << p.name << "吃了" << endl;
}
void Food::BeiEat(Student s) 
{
    cout << name << "被学生" << s.name << "吃了" << endl;
}
#pragma once
#include <iostream>
#include "Food.h"
using namespace std;

//前向声明
//class Food;

class Person
{
public:
    void Speak(string str);
    void Walk();
    void Eat(Food food);

public:
    string name = "";
    float height = 0;
    int age = 0;
    string homeAddress = "";

private:
    int money = 999;
};
#include "Person.h"
#include "Food.h"

void Person::Speak(string str)
{
    cout << name << "说:" << str << endl;
}

void Person::Walk()
{
    cout << name << "开始走路了" << endl;
}

void Person::Eat(Food food)
{
    cout << name << "开始吃" << food.name << ",获得了" << food.kaLuLi << "能量" << endl;
}
#pragma once
#include <iostream>
#include "Food.h"
using namespace std;

//class Food;

class Student
{
public:
    void Learn();
    void Eat(Food food);
public:
    string name = "";
    int num = 0;
    int age = 0;
    Student* deskmate = nullptr;
};
#include "Student.h"
#include "Food.h"
void Student::Learn()
{
    cout << name << "开始学习了" << endl;
}

void Student::Eat(Food food)
{
    cout << name << "开始吃" << food.name << ",获得了" << food.kaLuLi << "能量" << endl;
}
//补充了知识点 (循环依赖 两个头文件 相互彼此都对对方有引用 , 
//              前置声明 相当于告诉编译器 某一个关键字是一个类)

Person p;
p.name = "韬老狮";

Student s;
s.name = "小申宝宝";

Food f;
f.name = "粑粑";
f.kaLuLi = 0;

p.Eat(f); //韬老狮开始吃粑粑,获得了0能量
s.Eat(f); //小申宝宝开始吃粑粑,获得了0能量

//食物被吃可以有函数重载
f.BeiEat(p); //粑粑被人韬老狮吃了
f.BeiEat(s); //粑粑被学生小申宝宝吃了

基于成员变量练习题:为人类添加一个私有成员变量 money(表示拥有多少钱),想办法让其能在外部可以被获取,但是不能被修改

#pragma once
#include <iostream>
#include "Food.h"
using namespace std;

//前向声明
//class Food;

class Person
{
public:
    void Speak(string str);
    void Walk();
    void Eat(Food food);

    int GetMoney()
    {
        return money;
    }

    void SetMoney(int value)
    {
        money = value;
    }

public:
    string name = "";
    float height = 0;
    int age = 0;
    string homeAddress = "";

private:
    int money = 999;
};
cout << p.GetMoney() << endl; //999
p.SetMoney(666);
cout << p.GetMoney() << endl; //666

如何防止头文件重复包含

  1. 使用传统宏定义来防止头文件重复编译

    //	#ifndef 自定义宏名  // 如果没有定义 自定义宏名
    //	#define 自定义宏名  // 定义 自定义宏名
    
    //	#endif  // 结束条件编译
    

    例如:

    //#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名包括口追 全部打下 点用下划线替代
    //#define PERSON_H
    // 
    // class Person(){}
    //
    //
    //#endif
    
  2. 使用更加方便的pragma指令来防止重复编译 一般使用这一种

    //	#pragma once
    

5.4 练习题代码

Person.h

#pragma once
#include <iostream>
#include "Food.h"
using namespace std;

//前向声明
//class Food;

class Person
{
public:
    void Speak(string str);
    void Walk();
    void Eat(Food food);
    int GetMoney()
    {
        return money;
    }

    void SetMoney(int value)
    {
        money = value;
    }
public:
    string name = "";
    float height = 0;
    int age = 0;
    string homeAddress = "";

private:
    int money = 999;


};

Person.cpp

#include "Person.h"
#include "Food.h"

void Person::Speak(string str)
{
    cout << name << "说:" << str << endl;
}

void Person::Walk()
{
    cout << name << "开始走路了" << endl;
}

void Person::Eat(Food food)
{
    cout << name << "开始吃" << food.name << ",获得了" << food.kaLuLi << "能量" << endl;
}

Student.h

#pragma once
#include <iostream>
#include "Food.h"
using namespace std;

//class Food;

class Student
{
public:
    void Learn();
    void Eat(Food food);
public:
    string name = "";
    int num = 0;
    int age = 0;
    Student* deskmate = nullptr;
};

Student.cpp

#include "Student.h"
#include "Food.h"
void Student::Learn()
{
    cout << name << "开始学习了" << endl;
}

void Student::Eat(Food food)
{
    cout << name << "开始吃" << food.name << ",获得了" << food.kaLuLi << "能量" << endl;
}

Food.h

#pragma once
#include <iostream>
using namespace std;

//注意 以下会照成头文件的循环依赖 照成报错 
// 因为Person.h和Student.h中存在#include "Food.h"
//#include "Person.h"
//#include "Student.h"
//需要前向声明


//前向声明 告诉编译器 有这两个类 具体实现 先不管
class Person;
class Student;


//注意:
//前向声明不是必须的
//只要.h头文件include不循环依赖其实就不会报错 比如Person和Student引用Food 保证Food不引用Person和Student就行
//比如如果想使用内联函数 必须在.h文件中声明和定义的话 保证不循环依赖即可
//在cpp文件各种引用.h无所谓 因为是要真正使用了

class Food
{
public:
    string name;
    int kaLuLi;

public:
    void BeiEat(Person p);
    void BeiEat(Student s);
};

Food.cpp

#include "Food.h"
#include "Person.h"
#include "Student.h"

void Food::BeiEat(Person p) 
{
    cout << name << "被人" << p.name << "吃了" << endl;
}
void Food::BeiEat(Student s) 
{
    cout << name << "被学生" << s.name << "吃了" << endl;
}

Lesson05_练习题.cpp

#include <iostream>
#include "Person.h"
#include "Student.h"
#include "Food.h"
int main()
{
    #pragma region 练习题一
    //基于成员变量练习题
    //为人类定义说话、走路、吃饭等方法
    #pragma endregion

    #pragma region 练习题二
    //基于成员变量练习题
    //为学生类定义学习、吃饭等方法
    #pragma endregion

    #pragma region 练习题三
    //定义一个食物类,有名称,热量等特征
    //思考如何和人类以及学生类联系起来

    //补充了知识点 (循环依赖 两个头文件 相互彼此都对对方有引用 , 
    //              前置声明 相当于告诉编译器 某一个关键字是一个类)
    Person p;
    p.name = "韬老狮";


    Student s;
    s.name = "小申宝宝";

    Food f;
    f.name = "粑粑";
    f.kaLuLi = 0;

    p.Eat(f);//韬老狮开始吃粑粑,获得了0能量
    s.Eat(f);//小申宝宝开始吃粑粑,获得了0能量

    //食物被吃可以有函数重载
    f.BeiEat(p);//粑粑被人韬老狮吃了
    f.BeiEat(s);//粑粑被学生小申宝宝吃了

    #pragma endregion

    #pragma region 练习题四
    //基于成员变量练习题
    //为人类一个私有成员变量money(表示拥有多少钱)
    //想办法让其能在外部可以被获取,但是不能被修改

    cout << p.GetMoney() << endl;//999
    p.SetMoney(666);
    cout << p.GetMoney() << endl;//666

    #pragma endregion

    #pragma region 练习题五
    //如何防止头文件重复包含
    #pragma endregion
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏