本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓。
内存分区模型
C++程序再执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
不同区域存放的数据,赋予不同的声明周期,给我们更大的灵活编程
程序运行前
在程序编译后,生成了exe
可执行程序,未执行该程序前分为两个区域
代码区:
存放CPU执行的机器命令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此
全局区还包含了常量区,字符串常量和其他常量也存放在此
该区域的数据在程序结束后由操作系统释放。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| int g_a = 10; int g_b = 10;
const int c_g_a = 10; const int c_g_b = 10;
int main() {
int a = 10; int b = 10; cout << "局部变量a的地址为:" << &a << endl; cout << "局部变量b的地址为:" << &b << endl; cout << "全局变量g_a的地址为:" << &g_a << endl; cout << "全局变量g_b的地址为:" << &g_b << endl;
static int s_a = 10; static int s_b = 10; cout << "静态变量s_a的地址为:" << &s_a << endl; cout << "静态变量s_b的地址为:" << &s_b << endl;
cout << "字符串常量的地址为\t:" << &"hello world" << endl;
cout << "全局常量c_g_a地址为:" << &c_g_a << endl; cout << "全局常量c_g_b地址为:" << &c_g_b << endl;
const int c_l_a = 10; const int c_l_b = 10;
cout << "局部常量c_l_a地址为:" << &c_l_a << endl; cout << "局部常量c_l_b地址为:" << &c_l_b << endl;
return 0; }
|
总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放const修饰的全局常量和字符串常量
程序运行后
栈区
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int * func(int b) { int a = 10; return &a; }
int main() { int *p = func(1); cout << *p << endl; cout << *p << endl; return 0; }
|
堆区
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在c++中主要利用new在堆区开辟内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int * func() { int *p = new int(10); return p; }
int main() { int *p = func(); cout << *p << endl; return 0; }
|
总结:
堆区数据由程序员管理开辟和释放
堆区数据利用new关键字进行开辟内存
new操作符
C++中利用new
操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| int * func() { int *p = new int(10); return p; }
void test01(){ int *p = func(); cout << *p << endl; cout << *p << endl; delete(p); cout << *p << endl; }
int * test02(){ int * arr = new int[10]; for (int i = 0; i < 10; ++i) { arr[i] = i + 100; } for (int i = 0; i < 10; ++i) { cout << arr[i] << endl; } delete[] arr;
for (int i = 0; i < 10; ++i) { cout << arr[i] << endl; } }
|
引用
引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| int main () {
int a = 10; int &b = a;
cout << "a = " << a << endl; cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
|
引用注意事项
- 引用必须初始化(int &b;// 错误的)
- 引用在初始化后,就不可以更改了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int main () { int a = 10; int &b = a;
int c = 20; b = c;
cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; return 0; }
|
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
void swap01(int a, int b) { int temp = a; a = b; b = temp; cout << "(swap01) a = " << a << endl; cout << "(swap01) b = " << b << endl; }
void swap02(int * a, int * b) { int temp = *a; *a = *b; *b = temp; cout << "(swap02) a = " << *a << endl; cout << "(swap02) b = " << *b << endl; }
void swap03(int &a, int &b) { int temp = a; a = b; b = temp; cout << "(swap03) a = " << a << endl; cout << "(swap03) b = " << b << endl; }
int main () { int a = 10; int b = 20;
swap03(a,b);
cout << "(main) a = " << a << endl; cout << "(main) b = " << b << endl;
return 0; }
|
总结:通过引用参数产生的效果同地址传递时一样的,引用的语法更清楚简单
引用做函数返回值
作用:引用时可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
int & test01() { int a01 = 10; cout << "a01 = " << a01 << endl; return a01; }
int & test02(){ static int a02 = 10; cout << "a02 = " << a02 << endl; return a02; }
int main () { int &ref01 = test01(); cout << "ref01 = " << ref01 << endl; int &ref02 = test02(); cout << "ref02 = " << ref02 << endl;
test02() = 1000; cout << "ref02 = " << ref02 << endl;
return 0; }
|
引用的本质
本质:引用的本质在c++内部实现是一个指针常量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void func(int & ref) { ref = 100; }
int main () { int a = 10;
int & ref = a;
ref = 20;
cout << "a = " << a << endl; cout << "ref = " << ref << endl;
func(a); return 0; }
|
常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void showValue(const int & val){ cout << "val = " << val << endl; }
int main () {
const int & ref2 = 10;
int a = 100; showValue(a); cout << "a = " << a << endl;
return 0; }
|
函数提高
函数默认参数
在c++中,函数的形参列表中的形参是可以有默认值的。
语法:返回值类型 函数名(参数=默认值){}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
int func2(int a, int b = 20, int c = 30);
int func(int a, int b = 20, int c = 30) { return a + b + c; }
int func2(int a, int b, int c) { return a + b + c; }
int main () { int result = func(20); cout << "result = " << result << endl; return 0; }
|
函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名(数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术
1 2 3 4 5 6 7 8 9 10 11 12 13
|
void func(int a, int = 10) { cout << "this is func" << endl; }
int main () { func(10); return 0; }
|
函数重载
函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一作用域下
- 函数名称相同
- 函数参数类型不同或者个数不同或者顺序不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
void func(){ cout << "hello" << endl; }
void func(int a){ cout << "helloa" << endl; }
int main () { func(); func(10); return 0; }
|
函数重载注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
void func(int &a){ cout << "func(int &a)调用" << endl; } void func(const int &a){ cout << "func(const int &a)调用" << endl; }
void func2(int a, int b = 10){ cout << "func2(int a, int b)调用" << endl; } void func2(int a){ cout << "func2(int a)调用" << endl; }
int main () { int a = 10; func(a);
func(10);
return 0; }
|
类和对象
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重…,行为有走、跑、跳、吃饭、唱歌…
车也可以作为对象,属性有轮胎、方向盘、车灯…,行为有载人、放音乐、放空调…
具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
封装
封装的意义
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限: 属性/行为}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| const double PI = 3.14;
class Circle { public : int m_r = 0;
double calculateZC(){ return 2 * m_r * PI; } }; int main () { Circle cl; cl.m_r = 10; cout << "圆的周长为" << cl.calculateZC() << endl; return 0; }
|
示例:设计学生类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Student { public:
string m_Name; int m_Id;
void setMName(const string &mName) { m_Name = mName; }
void setMId(int mId) { m_Id = mId; } void showStudent() { cout << "姓名:" << m_Name << " 学号:" << m_Id << endl; } };
int main() { Student s1; s1.setMName("张三"); s1.setMId(1);
s1.showStudent();
Student s2; s2.setMName("李四"); s2.setMId(2);
s2.showStudent(); return 0; }
|
封装意义一:
类在设置时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限
- protected 保护权限
- private 私有权限
struct和class区别
在C++中struct和class唯一的区别就在于**默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class C1{ int m_A; void setMA(int mA) { m_A = mA; } };
struct C2{ int m_A;
};
int main() { C1 c1;
struct C2 c2; c2.m_A = 10; return 0; }
|
成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
class Person{ public: void setMName(string mName){ m_Name = mName; }
string getMName(){ return m_Name; }
int getAge(){ return m_Age; }
void setAge(int age){ if (age >= 0 && age <= 150){ m_Age = age; } else { m_Age = 0; } }
void setLover(string lover){ m_Lover = lover; }
private: string m_Name; int m_Age; string m_Lover; };
int main() { Person p; p.setMName("张三"); cout << p.getMName() << endl;
p.setAge(18); cout << p.getAge() << endl;
p.setLover("李四"); return 0; }
|
练习案例1:设计立方体类
设计立方体类(Cube)
求出立方体类的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
class Cube { public:
int calS() { return 2 * (m_H * m_L + m_H * m_W + m_L * m_W); }
int calV() { return m_H * m_L * m_W; }
void setMH(int mH) { m_H = mH; }
void setML(int mL) { m_L = mL; }
void setMW(int mW) { m_W = mW; }
int getMH() const { return m_H; }
int getML() const { return m_L; }
int getMW() const { return m_W; }
bool isEqual(const Cube &c) { if (m_H == c.getMH() && m_L == c.getML() && m_W == c.getMW()) { return true; } else { return false; } }
private: int m_H; int m_L; int m_W; };
bool isEqual(const Cube &c1, const Cube &c2) { if (c1.getMH() == c2.getMH() && c1.getML() == c2.getML() && c1.getMW() == c2.getMW()) { return true; } else { return false; } }
int main() { Cube c1; c1.setMH(10); c1.setML(10); c1.setMW(10);
cout << "c1的面积为:" << c1.calS() << endl; cout << "c1的体积为:" << c1.calV() << endl;
Cube c2; c2.setMH(10); c2.setML(10); c2.setMW(10);
cout << (c1.isEqual(c2)?"(成员函数 c1.isEqual(c2) )c2 和 c1 是相等的":"c2 和 c1 是不相等的") << endl;
cout << (isEqual(c1,c2)?"(全局函数 isEqual(c1,c2) )c1 和 c2 是相等的":"c1 和 c2 是不相等的") << endl; return 0; }
|
练习案例2:点和圆的关系
设计一个圆形类(Circle), 和一个点类(Point), 计算点和圆的关系
文件分拆:
point.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #pragma once #include<iostream> using namespace std;
class Point { public: int getMX() const;
void setMX(int mX);
int getMY() const;
void setMY(int mY); private: int m_X; int m_Y; };
|
point.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "point.h"
int Point::getMX() const { return m_X; }
void Point::setMX(int mX) { m_X = mX; }
int Point::getMY() const { return m_Y; }
void Point::setMY(int mY) { m_Y = mY; }
|
circle.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #pragma once #include<iostream> #include "point.h" using namespace std;
class Circle{ public: const Point &getMCenter() const;
void setMCenter(const Point &mCenter);
int getMR() const;
void setMR(int mR);
private: Point m_Center; int m_R; };
|
circle.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include "circle.h"
const Point &Circle::getMCenter() const { return m_Center; }
void Circle::setMCenter(const Point &mCenter) { m_Center = mCenter; }
int Circle::getMR() const { return m_R; }
void Circle::setMR(int mR) { m_R = mR; }
|
main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include "point.h" #include "circle.h"
using namespace std;
void isInCircle(Circle & circle, Point & point){ int cx = circle.getMCenter().getMX(); int cy = circle.getMCenter().getMY(); int px = point.getMX(); int py = point.getMY(); int distance = (cx - cy) * (cx - cy) + (px - py) * (px - py); int rDistance = circle.getMR() * circle.getMR(); if (distance > rDistance){ cout << "点在圆外" << endl; } else if (distance < rDistance) { cout << "点在圆内" << endl; } else { cout << "点在圆上" << endl; }
} int main() { Point point1; point1.setMX(10); point1.setMY(0); Circle circle; circle.setMCenter(point1); circle.setMR(10);
Point point2; point2.setMX(10); point2.setMY(9);
isInCircle(circle,point2);
return 0; }
|
对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称加上符号
~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Person{
public: Person(){ cout << "Person 构造函数的调用" << endl; } ~Person(){ cout << "Person 析构函数的调用" << endl; } };
int main() { Person p; return 0; }
|
构造函数的分类及调用
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
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 getAge() const { return age; }
private: int age; };
void test01(){ Person p1; Person p2(10); Person p3(p2);
cout << "p2的年龄为:" << p2.getAge() << endl; cout << "p3的年龄为:" << p3.getAge() << endl;
Person p4; Person p5 = Person(10); Person p6 = Person(p5);
Person(10);
Person p7 = 10; Person p8 = p7; }
int main() { test01(); return 0; }
|
拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数传值
- 以值方式返回局部对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
class Person { public: Person() { cout << "Person的默认构造函数调用" << endl; }
Person(int age){ m_Age = age; cout << "Person的有参构造函数调用" << endl; }
Person(const Person &p) { m_Age = p.m_Age; cout << "Person的拷贝构造函数调用" << endl; }
~Person() { cout << "Person的析构函数调用" << endl; }
int getAge() const { return m_Age; }
private: int m_Age; };
void test01(){ Person p1(20); Person p2(p1);
cout << "p2的年龄为:" << p2.getAge() << endl; }
void doWork(Person p){ } void test02(){ Person p; doWork(p); }
Person doWork2(){ Person p1; cout << &p1 << endl; return p1; }
void test03(){ Person p = doWork2(); cout << &p << endl; }
int main() { test03(); return 0; }
|
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会在提供其他构造函数
深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
class Person { public: Person() { cout << "Person的默认构造函数调用" << endl; }
Person(int age, int height){ m_Age = age; m_Height = new int(height); cout << "Person的有参构造函数调用" << endl; }
Person(const Person &p) { m_Age = p.m_Age; m_Height = new int(*p.m_Height); cout << "Person的拷贝构造函数调用" << endl; }
~Person() { if (m_Height != NULL) { delete m_Height; m_Height = NULL; } cout << "Person的析构函数调用" << endl; }
int getAge() const { return m_Age; }
int *getMHeight() const { return m_Height; }
private: int m_Age; int *m_Height; };
void test01(){ Person p1(18,160); cout << "p1的年龄为:" << p1.getAge() << " 身高为:" << *p1.getMHeight() << endl; Person p2(p1); cout << "p2的年龄为:" << p2.getAge() << " 身高为:" << *p2.getMHeight() << endl; } int main() { test01(); return 0; }
|
初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法:构造函数(): 属性1(值1),属性2(值2)...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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; };
int main() { Person p(10,20,30); cout << "m_A = " << p.m_A << endl; cout << "m_B = " << p.m_B << endl; cout << "m_C = " << p.m_C << endl; return 0; }
|
类对象成为类的成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Person {
public: int m_A; static int m_B; void func() {
} static void fun2c() {
} };
void test() { Person p; cout << "size of p = " << sizeof(p) << endl; }
int main() { test(); return 0; }
|
this指针概念
通过上一节我们知道c++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
class Person { public: Person(int age) { this -> age = age; } Person& PersonAddAge(Person &p){ this -> age += p.age; return *this; } int age; };
int main() { Person p(18); Person p2(1); p.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(p2); cout << p.age << endl; return 0; }
|
空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Person { public: void showClassName(){ cout << "this is Person class" << endl; }
void showPersonAge(){ if(this == NULL){ return; } cout << "age = " << m_Age << endl; }
int m_Age; };
int main() { Person * p = NULL; p->showClassName(); p->showPersonAge(); return 0; }
|
const修饰成员函数
常函数:
- 成员函数后加const我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Person { public: Person(){
}
void showPerson() const { this->m_B = 100; } void func(){
}
int m_A; mutable int m_B; };
int main() { const Person p; p.m_B = 10;
p.showPerson(); return 0; }
|
友元
生活中你的家有客厅(Public),有你的卧室(Private)
客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去
但是呢,你也可以允许你的好闺蜜好基友进去。
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的关键字为friend
友元的三种实现
全局函数做友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Building { friend void goodGuy(Building *building); public: Building() { m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } public: string m_SittingRoom;
private: string m_BedRoom; };
void goodGuy(Building *building){ cout << "好基友全局函数 正在访问:" << building -> m_SittingRoom << endl; cout << "好基友全局函数 正在访问:" << building -> m_BedRoom << endl; }
int main() { Building building; goodGuy(&building); }
|
类做友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Building; class GoodGuy{ public: void visit(); GoodGuy(); Building * building; }; class Building{ friend class GoodGuy; public: Building(); string m_SittingRoom; private: string m_BedRoom; };
Building::Building(){ m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } GoodGuy::GoodGuy(){ building = new Building; } void GoodGuy::visit(){ cout << "好基友类正在访问:" << building -> m_SittingRoom << endl; cout << "好基友类正在访问:" << building -> m_BedRoom << endl; }
int main() { GoodGuy goodGuy; goodGuy.visit(); }
|
成员函数做友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| class Building; class GoodGuy{ public: GoodGuy(); void visit(); void visit2(); Building * building; }; class Building{ friend void GoodGuy::visit(); public: Building(); string m_SittingRoom; private: string m_BedRoom; };
Building::Building(){ m_SittingRoom = "客厅"; m_BedRoom = "卧室"; } GoodGuy::GoodGuy(){ building = new Building; }
void GoodGuy::visit(){ cout << "visit好基友类正在访问:" << building -> m_SittingRoom << endl; cout << "visit好基友类正在访问:" << building -> m_BedRoom << endl; }
void GoodGuy::visit2(){ cout << "visit2好基友类正在访问:" << building -> m_SittingRoom << endl; }
int main() { GoodGuy goodGuy; goodGuy.visit(); goodGuy.visit2(); }
|
运算符重载
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class Person{ public: int m_A; int m_B; Person operator+ (Person &p){ Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; } };
int main() { Person p1 ; p1.m_B = 13; p1.m_A = 10; Person p2 ; p2.m_B = 10; p2.m_A = 12;
Person p3 = p1 + p2; cout << p3.m_A << endl; cout << p3.m_B << endl; }
|