C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
一、封装
1.1封装的意义
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:**class 类名{ 访问权限: 属性 / 行为 }**
示例 1:封装类的属性、行为、访问权限
设计一个圆类,求圆的周长
提示:
1、圆的周长的公式:2PI半径
#include <iostream>
using namespace std;
static double PI=3.1415926;
class Circle
{
public: //访问权限
int m_r; //属性//半径
double calculateZC() //行为
{
return 2*PI*m_r;
}
};
int main()
{
Circle C1;
C1.m_r=10;
cout<<"圆的周长为:"<<C1.calculateZC()<<endl;
return 0;
}
示例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以生的姓名和学号
#include <iostream>
using namespace std;
class student
{
public:
string name;
int id;
void Showstudent()
{
cout << "学生的名字: " << name << " 学生的学号: " << id << endl;
}
};
int main()
{
student stu;
stu.name = "张三";
stu.id = 202101;
stu.Showstudent();
return 0;
}
示例3:通过行为将上面的示例对类内的属性进行赋值
#include <iostream>
using namespace std;
class student
{
public:
string name;
int id;
void Showstudent()
{
cout << "学生的名字: " << name << " 学生的学号: " << id<<endl;
}
void setName(string s_name)
{
name= s_name;
}
void setID(int s_id)
{
id=s_id ;
}
};
int main()
{
student stu;
stu.setID(202102);
stu.setName("李四");
stu.Showstudent();
return 0;
}
结果:
注意:我们将类中的属性和行为都称为成员
- 属性:成员属性、成员变量
-
1.2、封装-访问权限
封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种: public 公共权限 类内可以访问 类外可以访问
- protected 保护权限 类内可以访问 类外不可以访问 (在继承中,儿子可以防问保护权限)
- private 私有权限 类内可以访问 类外不可以访问(在继承中,儿子不可以防问私有权限)
示例:
```cppinclude
using namespace std; class Person { public: //公共权限 string M_name; protected: //保护权限 string M_car; private: //私有权限 int M_password; void func() {
}M_name = "张三"; M_car = "奔驰"; M_password = 123456;
}; int main() { Person P1; P1.M_name = “李四”; P1.M_car = “劳斯莱斯”; P1.M_password=123123 return 0; }
**结果:**<br />**![image.png](https://cdn.nlark.com/yuque/0/2021/png/2352229/1614330249502-009e8441-6baf-40a3-8cdc-477b8ec2d15e.png#align=left&display=inline&height=411&margin=%5Bobject%20Object%5D&name=image.png&originHeight=505&originWidth=574&size=30509&status=done&style=none&width=467)**<br />**注意:**类外无法访问:protected和 private权限的内容。
<a name="SbVDh"></a>
## 1.3、struct和class的区别
在C++中struct和class唯一的区别就在于**默认的访问权限不同** <br />**区别:**
- struct默认权限为public
- class默认权限为private
```cpp
struct Person
{
string M_name; //默认为public
};
class Person
{
string M_name; //默认为private
}
1.4、成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
示例1:
#include <iostream>
using namespace std;
class Student
{
public:
void Print_Student()
{
cout << "学生姓名:" << M_name << " 学生ID:" << M_id << " 学生年龄:" <<M_age << endl;
}
void setName(string name)
{
M_name = name;
}
void setID(string id)
{
M_id = id;
}
void setAge(int age)
{
M_age = age;
}
private:
string M_name;
string M_id;
int M_age;
};
int main()
{
Student stu1;
stu1.setAge(22);
stu1.setID("20210226");
stu1.setName("张三");
stu1.Print_Student();
return 0;
}
示例2:设置程序可读写
#include <iostream>
using namespace std;
class Student
{
public:
void setName(string name) //设置姓名 姓名可写
{
M_name = name;
}
string getName() //获取姓名 姓名可读
{
return M_name;
}
void setID(string id) //设置ID ID可写
{
M_id = id;
}
string getID() //获取ID ID可读
{
return M_id;
}
void setAge(int age) //设置年龄 年龄仅可写不可读
{
M_age = age;
}
private:
string M_name;
string M_id;
int M_age;
};
int main()
{
Student stu1;
stu1.setAge(22);
stu1.setID("20210226");
stu1.setName("张三");
cout <<"学生姓名:"<<stu1.getName() <<" 学生ID:"<< stu1.getID() << endl;
return 0;
}
练习案例1:点和圆的关系
设计一个圆形类(Circle),和一个点类(Point),计算点和圆的关系,点是在圆上,还是圆内,或是在圆外。
提示:
设圆半径的长度为m_R,则:
- 如果点到圆心的距离=m_R 则点在圆上
- 如果点到圆心的距离>m_R 则点在圆外
- 如果点到圆心的距离<m_R 则点在圆内
求点和圆心的距离公式:(X1-X2)(X1-X2)+(Y1-Y2)(Y1-Y2)和m_R*m_R进行对比即可
#include <iostream>
using namespace std;
class Point //构建点类
{
public:
void Set_P_x(int x) //设置点的x坐标
{
P_x = x;
}
int Get_P_x() //获取点的x坐标
{
return P_x;
}
void Set_P_y(int y) //设置点的y坐标
{
P_y = y;
}
int Get_P_y() //获取点的y坐标
{
return P_y;
}
private:
int P_x;
int P_y;
};
class Circle //构建圆类
{
public:
void Set_C_r(int r) //设置半径
{
C_r = r;
}
int Get_C_r() //获取半径
{
return C_r;
}
void Set_Center(Point Center) //设置圆心
{
C_center = Center;
}
Point Get_Center() //获取圆心
{
return C_center;
}
private:
Point C_center; //构建圆心的位置的对象,因为圆心也是个点,所以这里直接用了点的类,构建了对象
int C_r;
};
void PinC(Circle& c, Point& p) //计算位置,确定点和圆的关系,这里用了引用,形参会改变实参
{ //这里使用了提示中计算点到圆心距离的公式
int ref = (c.Get_Center().Get_P_x() - p.Get_P_x()) * (c.Get_Center().Get_P_x() - p.Get_P_x()) + (c.Get_Center().Get_P_y() - p.Get_P_y()) * (c.Get_Center().Get_P_y() - p.Get_P_y());
if (ref == c.Get_C_r()* c.Get_C_r())
{
cout << "点在圆上!" << endl;
}
else if (ref < c.Get_C_r() * c.Get_C_r())
{
cout << "点在圆内!" << endl;
}
else
{
cout << "点在圆外!" << endl;
}
}
int main()
{
Circle circle; //构建圆类对象
Point point; //构建点类对象
Point center; //构建圆心点类对象
center.Set_P_x(10); //设置圆心X坐标
center.Set_P_y(0); //设置圆心Y坐标
circle.Set_C_r(5); //设置圆的半径为10
circle.Set_Center(center); //设置圆心的位置
point.Set_P_x(10); //设置点X坐标的位置
point.Set_P_y(10); //设置点Y坐标的位置
PinC(circle, point); //计算位置
return 0;
}
1.5、对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
1.5.1、构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对基使用后果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供提供 的构造函数和析构函数是空实现。
- 构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名(){}
析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加符号~
- 析构函数不可以有参数,因此不可以发生重载
程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
示例:
#include <iostream> using namespace std; class Person { public: //建立Person的构造函数 //构造函数的名称与类名相同 //构造函数没有返回值也不需要写void //构造函数可以有参数,因此可以发生重载 //构造函数会在程序执行前由系统自动调用一次 Person() { cout << "Person 的构造函数的调用" << endl; } //建立Person的析构函数 //析构函数的名称与类名相同,前面加符号~ //析构函数不可以有参数,因此不能发生重载 //析构函数会在程序销毁前由系统自动调用一次 ~Person() { cout << "Person 的析构函数的调用"<<endl; } }; void test() { Person p; //注意这里,我并没有调用对象P,但是构造和析构自动调用了 } int main() { test(); system("pause"); return 0; }
结果:
如果我将上面的程序改动一下,如下#include <iostream> using namespace std; class Person { public: //建立Person的构造函数 //构造函数的名称与类名相同 //构造函数没有返回值也不需要写void //构造函数可以有参数,因此可以发生重载 //构造函数会在程序执行前由系统自动调用一次 Person() { cout << "Person 的构造函数的调用" << endl; } //建立Person的析构函数 //析构函数的名称与类名相同,前面加符号~ //析构函数不可以有参数,因此不能发生重载 //析构函数会在程序销毁前由系统自动调用一次 ~Person() { cout << "Person 的析构函数的调用"<<endl; } }; int main() { Person p; //注意,改动在这里,我去掉了函数test(),直接创建对象P system("pause"); return 0; }
结果:
思考一下为什么?为什么析构函数没有被调用呢?
首先我们再来看一下析构函数的定义,是在程序销毁前自动调用一次,但是上面的程序有一个system(“pause”);所以程序是卡在了“请按任意键继续”,等咱们按完了任意键后,程序才是销毁前,所以这个析构函数会在按完任意键后被执行。
结果:
1.5.2构造函数的分类以及调用
两种分类方式:
按参数分为:有参构造函数和无参构造函数
- 按类型分为:普通构造函数和拷贝构造函数
三种调用方式:
- 括号法
- 显示法
-
示例1:括号法
#include <iostream> using namespace std; class Person { public: //构造函数的分类 //无参构造函数(默认构造) Person() { cout << "Person 的无参构造函数的调用" << endl; } //有参构造函数 Person(int a) { age = a; cout << "Person 的有参构造函数的调用" << endl; } //拷贝构造函数 Person(const Person &p) { age = p.age; //将传入人身上的属性,拷贝到我(当前对象)身上 cout << "Person 的拷贝构造函数的调用" << endl; } ~Person() { cout << "Person 的析构函数的调用"<<endl; } int age; }; void test() { Person p; //无参构造函数(默认构造函数)的调用 Person p1(10); //有参构造函数的调用 括号法 Person p2(p1); //拷贝构造函数的高用 括号法 cout << "p1的年龄为:" << p1.age << endl; cout << "p2的年龄为:" << p2.age << endl; return; } void test1() { return; } int main() { test(); system("pause"); return 0; }
结果:
注意事项:
调用无参构造函数的时候不要有(),例如Person p();这段代码编译器会认为是函数的声明
示例2:显示法
#include <iostream>
using namespace std;
class Person
{
public:
//构造函数的分类
//无参构造函数(默认构造)
Person()
{
cout << "Person 的无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "Person 的有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person &p)
{
age = p.age; //将传入人身上的属性,拷贝到我(当前对象)身上
cout << "Person 的拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person 的析构函数的调用"<<endl;
}
int age;
};
void test()
{
Person p1=Person(10); //有参构造函数的调用 显示法
//这里的Person(10)叫匿名对象,特点是执行完当前行,马上析构
//不要用拷贝构造函数来初始化一个匿名对象,例如:Person(p3)
Person p2=Person(p1); //拷贝构造函数的高用 显示法
cout << "p1的年龄为:" << p1.age << endl;
cout << "p2的年龄为:" << p2.age << endl;
return;
}
int main()
{
test();
system("pause");
return 0;
}
示例3:隐式转换法
#include <iostream>
using namespace std;
class Person
{
public:
//构造函数的分类
//无参构造函数(默认构造)
Person()
{
cout << "Person 的无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "Person 的有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person &p)
{
age = p.age; //将传入人身上的属性,拷贝到我(当前对象)身上
cout << "Person 的拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person 的析构函数的调用"<<endl;
}
int age;
};
void test()
{
Person p1 = 10; //隐式转换法 相当于写了Person p1(10)
Person p2 = p1; //隐式转换法 相当于写了Person p2(p1)
}
int main()
{
test();
system("pause");
return 0;
}
1.5.3、拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
示例1:使用一个已经创建完毕的对象来初始化一个新对象
#include <iostream> using namespace std; class Person { public: //构造函数的分类 //无参构造函数(默认构造) Person() { cout << "Person 的无参构造函数的调用" << endl; } //有参构造函数 Person(int a) { age = a; cout << "Person 的有参构造函数的调用" << endl; } //拷贝构造构数 Person(const Person& p) { age = p.age; cout << "Person 的拷贝构造函数的调用" << endl; } ~Person() { cout << "Person 的析构函数的调用"<<endl; } int age; }; void test() { Person p1(20); Person p2(p1); cout << "p2的年龄:" << p2.age<<endl; } int main() { test(); system("pause"); return 0; }
结果:
示例2:值传递的方式给函数参数传值
```cppinclude
using namespace std; class Person { public:
Person() {//构造函数的分类 //无参构造函数(默认构造)
} //有参构造函数 Person(int a) {cout << "Person 的无参构造函数的调用" << endl;
} //拷贝构造构数 Person(const Person& p) {age = a; cout << "Person 的有参构造函数的调用" << endl;
} ~Person() {age = p.age; cout << "Person 的拷贝构造函数的调用" << endl;
} int age; };cout << "Person 的析构函数的调用"<<endl;
void dowork(Person p) { return; } void test() { Person p1; dowork(p1); } int main() { test(); system(“pause”); return 0; }
<a name="nNO5J"></a>
#### 结果:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/2352229/1614482809811-d25b4445-b16c-4b52-9de2-8ddebf13468e.png#align=left&display=inline&height=119&margin=%5Bobject%20Object%5D&name=image.png&originHeight=119&originWidth=272&size=10954&status=done&style=none&width=272)
<a name="QzBXB"></a>
#### 示例3:以值方式返回局部对象
```cpp
#include <iostream>
using namespace std;
class Person
{
public:
//构造函数的分类
//无参构造函数(默认构造)
Person()
{
cout << "Person 的无参构造函数的调用" << endl;
}
//有参构造函数
Person(int a)
{
age = a;
cout << "Person 的有参构造函数的调用" << endl;
}
//拷贝构造构数
Person(const Person& p)
{
age = p.age;
cout << "Person 的拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person 的析构函数的调用"<<endl;
}
int age;
};
Person dowork()
{
Person p1;
return p1;
}
void test()
{
Person p=dowork(); //这行代码相当于 Person p(p1) 也是上面讲的显示法调用拷贝函数 Person p= person(p1);
return;
}
int main()
{
test();
system("pause");
return 0;
}
结果:
1.5.4、构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但会提供默认拷贝构造
-
示例:如果我们自己写了一个拷贝构造函数,那么系统将不再提供有参和无参构造
#include <iostream> using namespace std; class Person { public: //这里只写了拷贝构造构数 Person(const Person& p) { age = p.age; cout << "Person 的拷贝构造函数的调用" << endl; } ~Person() { cout << "Person 的析构函数的调用"<<endl; } int age; }; void test() { Person p1; Person p2(10); Person p3(p1); } int main() { test(); system("pause"); return 0; }
结果:
这里就印证了如果自己写了一个拷贝构造函数,系统就不再提供有参和无参的构造构说,另外Person P实际上也是创建对象的操作,如果你只写了拷贝构造函数,那就说明你只能用 Person p(p1)这样的方式来创建对象了。1.5.5、深拷贝和浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
-
示例 1:浅拷贝
#include <iostream> using namespace std; class Person { public: Person() { cout << "Person 的无参构造函数的调用" << endl; } Person(int age) { cout << "Person 的有参构造函数的调用" << endl; m_age = age; } ~Person() { cout << "Person 的析构函数的调用"<<endl; } int m_age; }; void test() { Person p1(18); cout << "p1的年龄:" << p1.m_age << endl; Person p2(p1); //浅拷贝 cout << "p2的年龄:" << p2.m_age << endl; } int main() { test(); system("pause"); return 0; }
示例2:浅拷贝存在的问题:堆区的内存重复释放
#include <iostream> using namespace std; class Person { public: Person() { cout << "Person 的无参构造函数的调用" << endl; } Person(int age,int height) { cout << "Person 的有参构造函数的调用" << endl; m_age = age; m_height = new int(height); } ~Person() { if (m_height != NULL) //如果m_height指针不为空,则清空内存地址 这段代码主要是将在栈开辟的内存空间释放 { delete m_height; m_height = NULL; //防止野指针,为m_height设置为NULL } cout << "Person 的析构函数的调用"<<endl; } int m_age; int* m_height; }; void test() { Person p1(18,160); cout << "p1的年龄:" << p1.m_age << "身高为:"<< *p1.m_height<<endl; Person p2(p1); //浅拷贝 cout << "p21的年龄:" << p2.m_age << "身高为:" << *p2.m_height << endl; } int main() { test(); system("pause"); return 0; }
结果:报错,程序运行不起来
所以,浅拷贝带来的问题要用深拷贝来解决示例3:深拷贝
#include <iostream> using namespace std; class Person { public: Person() { cout << "Person 的无参构造函数的调用" << endl; } Person(int age,int height) { cout << "Person 的有参构造函数的调用" << endl; m_age = age; m_height = new int(height); } Person(const Person& p)//自己写个拷贝构造函数来解决浅拷贝带来的问题 深拷贝 { cout << "Person 拷贝构造函数的调用" << endl; m_age = p.m_age; //m_height = p.m_height; //这个是由编译器默认生成的就是这行代码,存在问题 m_height = new int(*p.m_height); //这个是自己编写的深拷贝代码,这样的目的是让系统再次在栈区开启一块新的内存空间,这样就不存在重复释放的问题 } ~Person() { if (m_height != NULL) //如果m_height指针不为空,则清空内存地址 这段代码主要是将在栈开辟的内存空间释放 { delete m_height; m_height = NULL; //防止野指针,为m_height设置为NULL } cout << "Person 的析构函数的调用"<<endl; } int m_age; int* m_height; }; void test() { Person p1(18,160); cout << "p1的年龄:" << p1.m_age << "身高为:"<< *p1.m_height<<endl; Person p2(p1); //浅拷贝 cout << "p21的年龄:" << p2.m_age << "身高为:" << *p2.m_height << endl; } int main() { test(); system("pause"); return 0; }
1.5.6、初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性(值 1),属性2(值 2)……..{}
这个是构造函数的另外一种初始化列表的方式示例1:传统的利用构造函数的初始化操作
#include <iostream> using namespace std; class Person { public: //传统的初始化操作,用构造函数来进行初始化 Person(int a,int b,int c) { m_A = a; m_B = b; m_C = c; } int m_A; int m_B; int m_C; }; void test() { Person p(10,20,30); cout << "a=" << p.m_A << " b= " << p.m_B << " c= " << p.m_C << endl; } int main() { test(); system("pause"); return 0; }
结果:
示例 2:利用初始化列表对成员进行初始化操作#include <iostream> using namespace std; class Person { public: //利用初始化列表进行初始化成员属性 Person() :m_A(10), m_B(20), m_C(30) { } int m_A; int m_B; int m_C; }; void test() { Person p; cout << "a=" << p.m_A << " b= " << p.m_B << " c= " << p.m_C << endl; } int main() { test(); system("pause"); return 0; }
结果:
示例 3:利用初始化列表对成员进行初始化操作的第二种方式#include <iostream> using namespace std; class Person { public: //利用初始化列表进行初始化成员属性 Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c) { } int m_A; int m_B; int m_C; }; void test() { Person p(30,20,10); cout << "a=" << p.m_A << " b= " << p.m_B << " c= " << p.m_C << endl; } int main() { test(); system("pause"); return 0; }
1.5.7、类对象作为另一个类的成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例如:class A {}; class B { A a; }
B类中有对象A作为成赠,A为对象成员
那么当创建对象时,A与B的构造和析构的顺序是谁先谁后?示例1:构造和析构的顺序
#include <iostream> using namespace std; class A { public: A() { cout << "A 的构造函数的调用" << endl; } ~A() { cout << "A 的析构函数的调用" << endl; } }; class B { public: B() { cout << "B 的构造函数的调用" << endl; } A a; ~B() { cout << "B 的析构函数的调用" << endl; } }; void test() { B B; } int main() { test(); system("pause"); return 0; }
示例2:
#include <iostream> using namespace std; class Phone //手机类 { public: Phone(string p) { Pname = p; } //手机品牌 string Pname; }; class Person //人类 { public: Person(string name, string Pname) :P_name(name), P_phone(Pname) { } //姓名 string P_name; //手机 Phone P_phone; }; void test() { Phone p("三星"); Person P1("张三", "三星"); cout << "手机:" << p.Pname << endl; cout << P1.P_name << "拿着一部" << P1.P_phone.Pname <<endl; } int main() { test(); system("pause"); return 0; }
1.5.8、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享一同一个函数
- 静态成员函数只能访问静态成员变量
示例1:静态成员变量
结果:#include <iostream> using namespace std; class Person { public: static int m_A; //静态成员变量类内声明 }; int Person::m_A=100; //类外初始化 void test() { Person p; p.m_A = 200; //静态成员变量不属于某个对象上,所有对象都共享一份数据 ,这里改成200,如果有另外一个对象,那么m_A=200 cout << p.m_A << endl; } int main() { test(); }
静态成员变量的两种访问方式:示例1.1:通过对象访问
结果:#include <iostream> using namespace std; class Person { public: static int m_A; //静态成员变量类内声明 }; int Person::m_A=100; //类外初始化 void test() { Person p; cout << p.m_A << endl; } int main() { test(); }
示例1.2:通过类名访问
结果:#include <iostream> using namespace std; class Person { public: static int m_A; //静态成员变量类内声明 }; int Person::m_A=100; //类外初始化 void test() { cout<<Person::m_A<<endl; } int main() { test(); }
示例2:静态成员函数
结果:#include <iostream> using namespace std; class Person { public: static void func() //静态成员函数 { cout << "静态成员函数的调用!" << endl; } }; void test() { Person p; //通过对象调用静态成员函数 p.func(); //通过类名调用静态成员函灵敏 Person::func(); } int main() { test(); system("pause"); }
另外需要注意的是,静态成员函数只能调用静态成员变量,不能调用类内的普通成员变量。
示例2.1:#include <iostream> using namespace std; class Person { public: static void func() //静态成员函数 { A = 100; //静态成员函数只能访问静态成员变量,不能够访问类内普通的成员变量 B = 200; //这里调用了非静态成员变量,是错误的用法; cout << "静态成员函数的调用!" << endl; } static int A; int B; }; int Person ::A = 0; void test() { Person p; //通过对象访问静态成员函数 p.func(); Person::func(); } int main() { test(); system("pause"); }
2、C++对象模型和this指针
2.1、成变变量和成员函数分开存储
- 在C++中,类内的成员变量和成员函数分开存储
- 只有非静态成员变量才属于类的对象上
- 静态成员函数、静态成员变量和非静态成员函数都不属于类的对象上
2.2、this指针
通过2.1我们知道C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分哪个对象在调用自己呢?
C++通过提供特殊的对象指针,this指针,解决上述问题,this指针指向被调用的成员函数所属的对象.
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义 ,直接使用即可
this 指针的用途:
当形参和成员变量同名时,可用this指针来区分
当类的非静态成员函数中返回对象本身,可使用retrun this
*示例:
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
this->age = age; //this指针被谁调用就指向谁
}
int age;
};
void test()
{
Person p1(10); // 这里的this指针被p1调用,所以是指向p1的
cout << p1.age << endl;
}
int main()
{
test();
system("pause");
}
3、空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
示例:
#include <iostream>
using namespace std;
class Person
{
public:
void showage()
{
if (this == NULL) //为了防止空指针报错,这行代码增加了代码的健壮性,如果遇到空指针,程序直接结束
{
return;
}
cout << "年龄为:"<< this->age << endl;
}
int age;
};
void test()
{
Person* p=NULL; //建立一个Person类的空指针
p->showage();
}
int main()
{
test();
system("pause");
}
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数内不可以修改成员属性
成员属性声明时加关键排班字mutable后,在常函数中依然可以修改
常对象:
声明对象前加const称该对象为常对象
-
4.1、常函数
示例:
#include <iostream> using namespace std; class Person { public: void A() { this->age = 100; //这里的this其本质是一个指针常量 } int age; }; int main() { system("pause"); }
看一下这行代码,其中this->age = 100; 这行代码需要注意一点,这里的this相当于指针常量,也就是说指针的指向不可以改变,但是值是可以改变的,相当于Person * const this,那么常函数应该如何修饰呢,如下:
public: void A() const //在函数后边加了const即是常函数,其本质相当于const Person * const this { this->age = 100; //const Person * const this 也就是说指针指向和里面的值都不可以改动,所以这段代码是错误的 } int age; };
在函数后边加了const修饰后就变成了常函数,相当于const Person * const this,也就是说指针指向和里面的值都不可以改动,所以this->age = 100;这行代码是错误的。
那如果还是需要更改里面的值怎么办,那在成员变量前加一个mutable的关键字即可,如下:public: void A() const { this->age = 100; } mutable int age; //成员变量前加关键字mutable };
4.2、常对象
```cpp
include
using namespace std; class Person { public:
void A() const {
this->age = 200; cout << this->age << endl;
} mutable int age;
}; void test() {
const Person p; //常对象
p.age = 100;
p.A(); //常对象只能调用常函数
} int main() { test(); system(“pause”); } ``` 定义常对象的方法:const Person p; 需要注意的是常对象只能调用常函数,具体原因是:因为如果不是常函数的话,函数内部是可以修改成员变量的值,这个与常函数的定义相违背。