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
关键字使其成为内联函数,但需注意:- 是否真正内联由编译器决定,
inline
只是建议。 - 内联函数不能过大,否则可能导致代码膨胀(例如简单的
IsAdult
方法可直接写在.h
文件中作为内联)。 - 内联函数不适用于递归函数,否则可能导致无限展开。
- 虚函数不能是内联函数。
- 是否真正内联由编译器决定,
- 直接在
内联函数知识点主要做了解即可,大多数时候无需显式使用内联,对代码影响较小。
头文件中的防止重复编译命令
什么是头文件重复编译?
头文件可能被多个源文件(.cpp
)或同一个源文件间接多次包含。若没有防护机制,会导致编译错误(通常是重复定义错误)。
示例说明
- 编译器通常会在
.h
文件自动添加#pragma once
指令。 - 若手动删除该指令(如
Person.h
),并在代码中重复包含:
会触发重复定义错误。#include "Person.h" #include "Person.h" // 或通过其他头文件间接包含
C++ 防止重复编译的两种方式
传统宏定义方式:
使用#ifndef
、#define
、#endif
组合:#ifndef 自定义宏名 #define 自定义宏名 // 头文件内容 class Person {}; #endif
自定义宏名规则: 通常使用全大写文件名,用下划线替换点号(如
PERSON_H
)。例如:
//#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名包括口追 全部打下 点用下划线替代 //#define PERSON_H // // class Person(){} // // //#endif
现代
#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
如何防止头文件重复包含
使用传统宏定义来防止头文件重复编译
// #ifndef 自定义宏名 // 如果没有定义 自定义宏名 // #define 自定义宏名 // 定义 自定义宏名 // #endif // 结束条件编译
例如:
//#ifndef PERSON_H 第一种方式 那么后面的自定义宏名规则 一般是 脚本名包括口追 全部打下 点用下划线替代 //#define PERSON_H // // class Person(){} // // //#endif
使用更加方便的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