C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材

目录

  • 前言
  • 1.多继承
      • 1.1 多继承下内存布局
      • 1.2 多继承体系下构造函数调用
      • 1.3 多继承-虚函数
      • 1.4 同名函数
      • 1.5 同名成员变量(C++允许)
  • 2. 菱形继承
      • 2.1 菱形继承带来的问题
      • 2.2 菱形继承内存剖析
  • 3. 虚继承
      • 3.1 虚继承内存剖析
          • 3.1.1 虚继承与继承
          • 3.1.2 虚继承中 子类与父类
          • 3.1.3 虚继承中虚表
  • 4. 静态成员 (static)
      • 4.1 静态成员变量
          • 4.1.1 存储在数据段(全局区、类似于全局变量),整个程序运行过程中只有一份内存
          • 4.1.2 对比全局变量,它可以设定访问权限(public、protect、private),达到局部共享的目的
          • 4.1.3 必须初始化,必须在类外面初始化,初始化时不能带static
      • 4.2 静态成员函数
          • 4.2.1 内部不能使用this指针
          • 4.2.2 不能是虚函数
          • 4.2.3 内部不能访问非静态成员变量、函数,只能访问静态成员变量、函数
          • 4.2.4 非静态成员函数内部可以访问静态成员变量、函数
          • 4.2.5 构造函数、析构函数不能是静态
          • 4.2.6 当声明和实现分离时,实现部分不能带static
      • 4.3 窥探反汇编
          • 4.3.1 静态成员变量与非静态成员变量的本质
          • 4.3.1 同一静态成员变量,剖析反汇编
      • 4.4 窥探内存
          • 4.4.1 继承关系下,不同类调用静态变量的地址
          • 4.4.2 继承关系下,子类定义与父类同名的静态成员
      • 4.5 应用场景
          • 4.5.1 统计car的数量
          • 4.5.2 单例模式
          • 4.5.3 单例模式补充
  • 5. const成员
      • 5.1 const成员变量
          • 5.1.1 必须初始化(类内部初始化),可以再声明的时候直接初始化赋值
          • 5.1.2 非static 的 const 成员变量还可以在初始化列表中初始化
          • 5.1.3 static 修饰的 const 成员变量只能在声明时定义
      • 5.2 const成员函数(非静态)
          • 5.2.1 const关键字写在参数列表后面,函数的声明和实现必须带const
          • 5.2.2 内部不能修改非static成员变量
          • 5.2.3 内部只能调用const成员函数,static成员函数
          • 5.2.4 非const成员函数可以调用const成员函数
          • 5.2.5 const成员函数和非const成员函数构成重载
          • 5.2.6 非const对象(指针)优先调用非const 成员函数
          • 5.2.7 const对象(指针)只能调用const成员函数、static成员函数
  • 6. 引用类型成员
      • 6.1 引用类型成员变量必须初始化(不考虑static情况)
      • 6.2 在声明的时候直接初始化
      • 6.3 通过初始化列表初始化
  • 7. 拷贝构造函数(Copy Constructor)
      • 7.1 构造函数初始化
          • 7.1.1 利用对象去初始化,不会调用构造函数
      • 7.2 利用对象去构造函数初始化的对象(不使用拷贝构造)
      • 7.3 拷贝构造函数初始化
          • 7.3.1 当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化
      • 7.4 拷贝构造使用场景:
      • 7.5 调用父类的拷贝构造函数
      • 7.6 拷贝构造函数-固定写法剖析
          • 7.6.1 为什么拷贝构造函数的参数定义要使用 const 与 & 限制条件?
  • 8. 浅拷贝、深拷贝
      • 8.1 编译器默认的提供的拷贝是浅拷贝(shallow copy)
          • 8.1.1 堆空间指向栈空间(很危险)
          • 8.1.2 strcpy在vs2019中的使用
          • 8.1.3 在堆空间->堆空间与堆空间->栈空间
          • 8.1.3 构造函数参数加上 const 同时接收字符串和字符数组
      • 8.2 浅拷贝(shallow copy)
          • 8.2.1 double free 双重析构双重释放
      • 8.3 深拷贝(deep copy)
          • 8.3.1 深拷贝使用场景
      • 8.4 总结
          • 8.4.1 编译器默认的提供的拷贝是浅拷贝(shallow copy)
          • 8.4.2 如果需要实现深拷贝(deep copy),就需要自定义拷贝构造函数
  • 9. 对象型参数和返回值
      • 9.1 对象型参数
      • 9.2 对象型返回值
      • 9.3 多此产生拷贝构造的优化
  • 10. 匿名对象(临时对象)
      • 10.1 传入匿名对象
      • 10.2 返回匿名对象
  • 11. 隐式构造
      • 11.1 正常创建对象
      • 11.2 隐式的调用int类型构造函数
      • 11.3 对象型参数(隐式的调用)
      • 11.4 对象型返回值(隐式的调用)
      • 11.5 先显示创建对象,再隐式创建对象
      • 11.6 explicit 禁止调用隐式构造
  • 12. 编译器自动生成的构造函数
      • 12.1 成员变量在声明的同时进行了初始化
      • 12.2 有定义虚函数
      • 12.3 虚继承了其他类
      • 12.4 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义)
      • 12.5 父类有构造函数(编译器生成或自定义)
      • 12.6 总结
  • 13. 友元
      • 13.1 友元函数
      • 13.2 友元类
      • 13.3 利弊
  • 14. 内部类
      • 14.1 使用场景
      • 14.2 内部类的特点
          • 14.2.1 支持public、protected、private权限
          • 14.2.2 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
          • 14.2.3 成员函数可以直接不带类名、对象名访问其外部类的static成员
          • 14.2.4 不会影响外部类的内存布局
          • 14.2.5 可以再外部类内部声明,在外部类外面进行定义
  • 15. 局部类
      • 15.1 局部类得特点
          • 15.1.1 作用域仅限于所在函数内部
          • 15.1.2 其所有的成员必须定义在类内部,不允许定义static成员变量
          • 15.1.3 成员函数不能直接访问函数的局部变量(static 变量除外)
          • 15.1.4 不会影响外部类的内存布局
          • 15.1.5 可以再外部类内部声明,在外部类外面进行定义

前言

由于在CSDN上无法看到markdown的目录,有需要额小伙伴可以联系我,附送本文的 .md文件,可以再本地typora上更加方便的学习
这篇文章和 汇编入门基础(点此链接) 为想结合内容,请大家在学习时可以同时参考

1.多继承

C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)

#include 
using namespace std;

struct Student {
	int m_score;
	void study() {
		cout << "Student::study() - score = " << m_score << endl;
	}
};

struct Worker {
	int m_salary;
	void work() {
		cout << "Worker::Worker() - salary = " << m_salary << endl;
	}
};

struct Undergraduate :Student,Worker {
	int m_grade;
	void play() {
		cout << "undergraduate::play() - grade = " << m_grade<<endl;
	}
};

int main() {

	Undergraduate ug;
	ug.m_score = 100;
	ug.m_salary = 2000;
	ug.m_grade = 4;

	ug.study();
	ug.work();
	ug.play();

	return 0;
}
输出:
Student::study() - score = 100
Worker::Worker() - salary = 2000
undergraduate::play() - grade = 4

1.1 多继承下内存布局

根据继承的先后顺序,先继承的在内存的前面(和父类定义的顺序无关)

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第1张图片

1.2 多继承体系下构造函数调用

一般情况由于封装,父类的成员变量为私有,只能用构造函数去调用父类成员变量

#include 
using namespace std;

struct Student {
	int m_score;
	Student(int score) :m_score(score) {}
	void study(){
		cout << "Student::study() - score = " << m_score << endl;
	}
};

struct Worker {
	int m_salary;
	Worker(int salary) :m_salary(salary) {}
	void work() {
		cout << "Worker::Worker() - salary = " << m_salary << endl;
	}
};

struct Undergraduate :Student,Worker {
	int m_grade;
	//一般情况下,父类的成员变量为私有,只能用构造函数去调用
	Undergraduate(int score, int salary, int grade) :m_grade(grade), Student(score), Worker(salary) {}
	void play() {
		cout << "undergraduate::play() - grade = " << m_grade<<endl;
	}
};

int main() {
	return 0;
}

如图可见:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第2张图片

1.3 多继承-虚函数

如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表(地址)

#include 
using namespace std;

class Student {
public:
	virtual void study() {
		cout << "Student::study()" << endl;
	}
};

class Worker {
public:
	virtual void worker() {
		cout << "Worker::work()" << endl;
	}
};

class Undergraduate :public Student, public Worker {
public:
	void study() {
		cout << "Worker::work()" << endl;
	}
	virtual void worker() {
		cout << "Worker::work()" << endl;
	}
	virtual void play() {
		cout << "Undergraduate::play()" << endl;
	}
};

int main() {
	cout << sizeof(Undergraduate) << endl;
	return 0;
}
输出:8

如图可见:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第3张图片

如果子类没有重写父类函数
如下代码:

class Student {
public:
	virtual void study() {
		cout << "Student::study()" << endl;
	}
};

class Worker {
public:
	virtual void worker() {
		cout << "Worker::work()" << endl;
	}
};

class Undergraduate :public Student, public Worker {
public:
	void study() {
		cout << "Undergraduate ::work()" << endl;
	}
};
cout << sizeof(Undergraduate) << endl; 
仍然输出:8

此时虚表地址1:存放的是父类 Student 的虚函数地址
此时虚表地址2:存放的是父类 Worker 的虚函数地址

1.4 同名函数

#include 
using namespace std;

class Student {
public:
	 void eat() {
		cout << "Student::eat()" << endl;
	}
};

class Worker {
public:
	 void eat() {
		cout << "Worker::eat()" << endl;
	}
};

class Undergraduate :public Student, public Worker {
public:
	void eat() {
		cout << "Undergraduate::work()" << endl;
	}

};

int main() {
	Undergraduate ug;
	ug.eat(); // 默认为当前对象的类型
	ug.Student::eat();
	ug.Worker::eat();
	ug.Undergraduate::eat();

	return 0;
}
输出:
Undergraduate::work()
Student::eat()
Worker::eat()
Undergraduate::work()

1.5 同名成员变量(C++允许)

#include 
using namespace std;

struct Student {
	int m_age;
};
struct Worker {
	int m_age;
};
struct Undergraduate:Student ,Worker {
	int m_age;
};

int main() {
	cout << sizeof(Undergraduate) << endl;
	Undergraduate ug;
	ug.m_age = 10;
	ug.Student::m_age = 11;
	ug.Worker::m_age = 12;
	ug.Undergraduate::m_age = 12;

	return 0;
}
输出:12

汇编剖析:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第4张图片

内存剖析:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第5张图片

2. 菱形继承

2.1 菱形继承带来的问题

菱形继承带来的问题:
1)最底下子类从基类继承的成员变量冗余、重复
2)最底下子类无法访问基类的成员,有二义性

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第6张图片 C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第7张图片

二义性:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第8张图片

2.2 菱形继承内存剖析

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第9张图片

代码如下:

#include 
using namespace std;

struct Person {
	int m_age;
};
struct Student:Person {
	int m_score;
};
struct Worker :Person {
	int m_salary;
};
struct Undergraduate :Student, Worker {
	int m_grade;
};

int main() {
	Undergraduate ug;
	cout << sizeof(ug) << endl;

	return 0;
}
输出:12

3. 虚继承

虚继承可以解决菱形继承带来的问题
Person 类被称为虚基类

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第10张图片

代码如下:

#include 
using namespace std;

struct Person {
	int m_age;
};
struct Student: virtual Person {
	int m_score;
};
struct Worker :virtual Person {
	int m_salary;
};
struct Undergraduate :Student, Worker {
	int m_grade;
};

int main() {
	Undergraduate ug;
	ug.m_age = 10;
	return 0;
}
如上代码
struct Student: virtual Person  
与 
struct Worker :virtual Person 
只要有一个不是虚继承,则破坏虚继承结构,导致调用基类成员变量产生二义性

3.1 虚继承内存剖析

3.1.1 虚继承与继承

1)虚继承:父类成员变量放最后
2)继承:父类成员变量放最前面

3.1.2 虚继承中 子类与父类

代码片段分析:

// 父类
struct Person {
	int m_age;
};
// 子类
struct Student: virtual Person {
	int m_score;
};
此时 Student 类的内存大小为 12个字节 C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第11张图片
即:4+4+4(x86下虚表地址4个字节,父类Person 的 m_age 占4个字节,子类Student 的 m_score 占4个字节)
3.1.3 虚继承中虚表

1) 虚表指针与本类起始的偏移量(一般是0)
2) 虚基类第一成员变量与本类起始的偏移量

// 父类
struct Person {
	int m_age;
};
// 子类
struct Student: virtual Person {
	int m_score;
};
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第12张图片

虚表中0,表示虚表指针 与 student 类的起始指针 是一样的差为0
虚表中8,表示虚基类 Person 第一个成员变量m_age 与 本类 Student的起始指针 相差(8个字节,即:虚表指针4字节+m-score 4个字节)

全部代码:

#include 
using namespace std;

struct Person {
	int m_age=1;
};
struct Student: virtual Person {
	int m_score=2;
};
struct Worker :virtual Person {
	int m_salary=3;
};
struct Undergraduate :Student, Worker {
	int m_grade=4;
};

int main() {
	Undergraduate ug;
	cout << sizeof(Undergraduate) << endl;
	return 0;
}
输出:24

内存剖析:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第13张图片

4. 静态成员 (static)

静态成员:被static修饰的成员变量、函数

如何调用:
1)通过对象:对象.静态成员
2)通过对象指针:对象指针->静态成员
3)通过类访问:类型::静态成员

静态成员与全局变量、函数
唯一区别就是:增加访问限制,仅此而已

4.1 静态成员变量

#include 
using namespace std;

class Car {
public:
	static int m_price;
	void run() {
		cout << "run()" << endl;
	}
};

// 在类的外面初始化成员变量
int Car::m_price = 0;

int main() {
	Car car1;
	car1.m_price = 10;

	Car car2;
	car2.m_price = 20;

	Car car3;
	car3.m_price = 30;
	
	cout << Car::m_price << endl;
	return 0;
}
输出:30
4.1.1 存储在数据段(全局区、类似于全局变量),整个程序运行过程中只有一份内存
4.1.2 对比全局变量,它可以设定访问权限(public、protect、private),达到局部共享的目的
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第14张图片
4.1.3 必须初始化,必须在类外面初始化,初始化时不能带static

如果类的声明和实现分离(在实现的 .cpp 中初始化)

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第15张图片

4.2 静态成员函数

#include 
using namespace std;

class Car {
public:
	static int m_price;
	static void run() {
		cout << "run()" << endl;
	}
};

// 在类的外面初始化成员变量
int Car::m_price = 0;

int main() {
	Car car1;
	car1.run();

	Car *car2;
	car2->run();

	Car::run();

	return 0;
}
输出:
run()
run()
run()
4.2.1 内部不能使用this指针

this 指针只能用在非静态成员函数内部

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第16张图片

默认在类内部调用成员变量会有this指针
由于static变量内存在全局区,所以不存在this指针,所以不能调用非静态成员

4.2.2 不能是虚函数

虚函数只能是非静态成员函数

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第17张图片
4.2.3 内部不能访问非静态成员变量、函数,只能访问静态成员变量、函数
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第18张图片
4.2.4 非静态成员函数内部可以访问静态成员变量、函数
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第19张图片
4.2.5 构造函数、析构函数不能是静态
4.2.6 当声明和实现分离时,实现部分不能带static
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第20张图片

(修订:图中在Car中,静态成员函数不应该有{} 主体)

4.3 窥探反汇编

4.3.1 静态成员变量与非静态成员变量的本质

对于非静态成员变量:将值写入ebp(基址指针寄存器(extended base pointer))
对于静态成员变量:将值写入 ds(全局区、数据段:data segment )

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第21张图片
4.3.1 同一静态成员变量,剖析反汇编
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第22张图片

4.4 窥探内存

4.4.1 继承关系下,不同类调用静态变量的地址

因为静态成员在全局区(代码段)所以内存是唯一的

#include 
using namespace std;

class Person {
public:
	static int m_age;

};
int Person::m_age = 20;
class Student:public Person {

};

int main() {
	cout << &Student::m_age << endl;
	cout << &Person::m_age << endl;

	return 0;
}
输出:
00BEA000
00BEA000
4.4.2 继承关系下,子类定义与父类同名的静态成员
#include 
using namespace std;

class Person {
public:
	static int m_age;

};
int Person::m_age = 20;
class Student:public Person {
public:
	static int m_age;

};

int Student::m_age = 30;

int main() {

	cout << &Student::m_age << endl;
	cout << &Person::m_age << endl;

	return 0;
}
输出:
013AA03C
013AA038

4.5 应用场景

4.5.1 统计car的数量

ms定义 静态成员变量
m:member
s:static

#include 
using namespace std;

// 如果写在全局区,任意函数都可以修改
//int g_count;

class Car {
	static int ms_count;
public:
	static int getCount() {
		return ms_count;
	}
	Car() {
		ms_count ++ ;
	}
	~Car() {
		ms_count--;
	}
};
int Car::ms_count = 0;
Car car;
int main() {
	Car car;

	Car* car1 = new Car();
	delete car1;

	cout << Car::getCount() << endl;
	return 0;
}
4.5.2 单例模式

第一步,保证类永远只创建一个对象

#include 
using namespace std;

//单例模式:设计模式的一种,保证某个类永远只创建一个对象
//1.构造函数私有化

class Rocket {
	Rocket() {}
	~Rocket() {}
	static Rocket* ms_rocket;
public:
	static int  ms_price;
	static Rocket* sharedRocket() {
		if (ms_rocket == NULL) {
			ms_rocket = new  Rocket();
		}
		return ms_rocket;
	}
};
//初始化static变量
Rocket* Rocket::ms_rocket = NULL;

int main() {
	//Rocket rp;
	Rocket * p1 = Rocket::sharedRocket();
	Rocket* p2 = Rocket::sharedRocket();
	Rocket* p3 = Rocket::sharedRocket();
	Rocket* p4 = Rocket::sharedRocket();
	cout << p1 << endl;
	cout << p2 << endl;
	cout << p3 << endl;
	cout << p4 << endl;

	return 0;
}
输出:
001B3AF0
001B3AF0
001B3AF0
001B3AF0
说明四个类都指向一个地址

第二步,定义一个私有的static成员变量指向唯一的那个单例对象

#include 
using namespace std;

//单例模式:设计模式的一种,保证某个类永远只创建一个对象
//1.构造函数私有化
//2.定义一个私有的static成员变量指向唯一的那个单例对象
//3.提供一个公共的访问单例对象的接口

class Rocket {
	Rocket() {}
	//防止未创建对象前,去delete
	~Rocket() {}
	static Rocket* ms_rocket;
public:
	static int  ms_price;
	static Rocket* sharedRocket() {
		//这里要考虑多线程安全
		if (ms_rocket == NULL) {
			ms_rocket = new  Rocket();
		}
		return ms_rocket;
	}

	static void deleteRocket() {
		//这里要考虑多线程安全
		if (ms_rocket!=NULL) {
			// 清空指针指向的堆空间,可能会产生野指针
			delete ms_rocket;
			// 清空指针存的地址
			ms_rocket = NULL;
		}
	}

	void run() {
		cout << "run()" << endl;
	}
};
//初始化static变量
Rocket* Rocket::ms_rocket = NULL;


int main() {
	Rocket* p1 = Rocket::sharedRocket();
	Rocket* p2 = p1->sharedRocket();
	cout << p1 << endl;
	cout << p2 << endl;

	return 0;
}
输出:
0142A6F0
0142A6F0
class Rocket {
	Rocket() {}
	//防止未创建对象前,去delete
	~Rocket() {}
	static Rocket  ms_rocket;
}

为什么不能使用 :static Rocket ms_rocket;
因为堆空间更好管理,单例模式建议使用:static Rocket * ms_rocket;

4.5.3 单例模式补充

1)利用运算符重写=,将赋值运算符=重写,为了不让对象成员赋值

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第23张图片

2)拷贝构造函数私有化

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第24张图片

5. const成员

const成员:被const修饰的成员变量、非静态成员函数

5.1 const成员变量

5.1.1 必须初始化(类内部初始化),可以再声明的时候直接初始化赋值

初始化方式一,声明时直接初始化

#include 
using namespace std;

class Car {
public:
	//初始化方式一
	const int m_price=0;
	Car() {}
};

int main() {
	return 0;
}
5.1.2 非static 的 const 成员变量还可以在初始化列表中初始化

初始化方式二,构造函数的初始化列表中初始化

#include 
using namespace std;

class Car {
public:
	//初始化方式一
	const int m_price;
	Car():m_price(0){}
};

int main() {
	return 0;
}
5.1.3 static 修饰的 const 成员变量只能在声明时定义
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第25张图片

如下代码:

#include 
using namespace std;

class Car {
public:
	//初始化方式一
	static const int m_price=0;
	Car() {}
};

int main() {
	return 0;
}

5.2 const成员函数(非静态)

5.2.1 const关键字写在参数列表后面,函数的声明和实现必须带const
#include 
using namespace std;

class Car {
public:
	//初始化方式一
	static const int m_price=0;
	Car() {}
	void run() const;
};

void Car::run() const {
	cout << "run()" << endl;
}

int main() {
	return 0;
}
5.2.2 内部不能修改非static成员变量
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第26张图片
5.2.3 内部只能调用const成员函数,static成员函数

const 成员函数 不能调用非static、const函数

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第27张图片

const 成员函数 可以调用非static函数
因为static 函数不可以调用普通成员变量(只能调用 static 成员)

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第28张图片

const可以调用static函数

#include 
using namespace std;

class Car {
public:
	//初始化方式一
	int m_price;
	Car() {}
	void test() const{
		run();
	}
	static void run()  {
	
	}
};

int main() {
	return 0;
}

非const成员可与

5.2.4 非const成员函数可以调用const成员函数
#include 
using namespace std;

class Car {
public:
	//初始化方式一
	int m_price;
	Car() {}
	void test() const{
		
	}
	void run()  {
		test();
	}
};

int main() {
	return 0;
}
5.2.5 const成员函数和非const成员函数构成重载
#include 
using namespace std;

class Car {
public:
	void run()  {
		cout << "run()" << endl;
	}
	void run() const {
		cout << "run() const" << endl;
	}
};
int main() {
	Car car1;
	car1.run();

	const Car car2;
	car2.run();

	return 0;
}
输出:
run()
run() const
5.2.6 非const对象(指针)优先调用非const 成员函数

当非const对象没有可调用的非const函数,则可以调用const成员函数

#include 
using namespace std;

class Car {
public:
	/*void run()  {
		cout << "run()" << endl;
	}*/

	void run() const {
		cout << "run() const" << endl;
	}
};

int main() {
	Car car1;
	car1.run();
	const Car car2;
	car2.run();
	return 0;
}
5.2.7 const对象(指针)只能调用const成员函数、static成员函数
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第29张图片

代码如下:

#include 
using namespace std;

class Car {
public:
	void run()  {
		cout << "run()" << endl;
	}
	void run() const {
		cout << "run() const" << endl;
	}
	static void test() {
	}
};

int main() {
	Car car1;
	car1.run();

	const Car car2;
	car2.run();
	car2.test();

	return 0;
}

6. 引用类型成员

6.1 引用类型成员变量必须初始化(不考虑static情况)

6.2 在声明的时候直接初始化

6.3 通过初始化列表初始化

#include 
using namespace std;

class Person{
	int age;
	int& m_price = age;
public:
	Person(int& price) :m_price(price) { }
}
int main() {
	return 0;
}

7. 拷贝构造函数(Copy Constructor)

拷贝构造函数是构造函数的一种

构造函数:在创建完对象时(初始化)创建
拷贝构造函数:利用一个已经存在的对象,来创建一个新对象

7.1 构造函数初始化

#include 
using namespace std;

class Car {
	int m_price;
	int m_length;
public:
	Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
		cout << "Car(int price = 0, int length = 0) " << endl;
	}
	void display() {
		cout << "Price=" << m_price << ",length=" << m_length << endl;
	}
};

int main() {
	//构造函数是在创建完对象时
	Car car1;
	Car car2(100);
	Car car3(100,5);

	return 0;
}
输出:
Car(int price = 0, int length = 0)
Car(int price = 0, int length = 0)
Car(int price = 0, int length = 0)
7.1.1 利用对象去初始化,不会调用构造函数
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第30张图片

7.2 利用对象去构造函数初始化的对象(不使用拷贝构造)

如:Car car4(car3);

#include 
using namespace std;

class Car {
	int m_price;
	int m_length;
public:
	Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
		cout << "Car(int price = 0, int length = 0) " << endl;
	}
	//拷贝构造函数
	/*Car(const Car& car) {
		cout << "Car(const Car& car) " << endl;
	}*/
	void display() {
		cout << "Price=" << m_price << ",length=" << m_length << endl;
	}
};

int main() {
	//构造函数是在创建完对象时
	Car car1;
	Car car2(100);
	Car car3(100,5);
	//利用已经存在的car 对象创建一个car4新对象
	//car4初始化时会调用拷贝构造函数
	Car car4(car3);
	car4.display();

	return 0;
}
输出:
Car(int price = 0, int length = 0)
Car(int price = 0, int length = 0)
Car(int price = 0, int length = 0)
Price=100,length=5

反汇编剖析

    32: 	Car car4(car3);
 mov         eax,dword ptr [ebp-30h]  
 mov         dword ptr [ebp-40h],eax  
 mov         ecx,dword ptr [ebp-2Ch]  
 mov         dword ptr [ebp-3Ch],ecx  
    33: 	car4.display();
 lea         ecx,[ebp-40h]  
 call        00F0141A  

从反汇编可以得出
eax,dword ptr [ebp-30h] 与 ecx,dword ptr [ebp-2Ch]
相差4个字节(x86下)都取出4个字节(dword)
给了另外相差4个字节的
dword ptr [ebp-40h],eax 与 dword ptr [ebp-3Ch],ecx
等同于

	Car car4(car3);
	car4.m_price = car3.m_price;
	car4.m_length = car3.m_length;

将car3的成员变量给car4,完成拷贝

7.3 拷贝构造函数初始化

#include 
using namespace std;

class Car {
	int m_price;
	int m_length;
public:
	Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
		cout << "Car(int price = 0, int length = 0) " << endl;
	}
	//拷贝构造函数
	Car(const Car& car) {
		cout << "Car(const Car& car) " << endl;
	}
	void display() {
		cout << "Price=" << m_price << ",length=" << m_length << endl;
	}
};

int main() {
	//利用已经存在的car 对象创建一个car4新对象
	//car4初始化时会调用拷贝构造函数
	Car car3(100, 5);
	Car car4(car3);
	car4.display();

	return 0;
}
输出:
Car(int price = 0, int length = 0)
Car(const Car& car)  // 调用拷贝构造
Price=-858993460,length=-858993460 

price 和 length输出为cc
栈空间初始化为cc

7.3.1 当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化

拷贝构造函数中使用初始化列表

#include 
using namespace std;

class Car {
	int m_price;
	int m_length;
public:
	Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
		cout << "Car(int price = 0, int length = 0) " << endl;
	}
	//拷贝构造函数
	Car(const Car& car):m_price(car.m_price),m_length(car.m_length) {
		cout << "Car(const Car& car) " << endl;
	}
	void display() {
		cout << "Price=" << m_price << ",length=" << m_length << endl;
	}
};


int main() {
	//利用已经存在的car 对象创建一个car4新对象
	//car4初始化时会调用拷贝构造函数
	Car car3(100, 5);
	Car car4(car3);

	car4.display();

	return 0;
}

7.4 拷贝构造使用场景:

如果全部需要将成员变量在创建对象时初始化,则不需要使用拷贝构造函数
如果部分需要将成员变量在创建对象时初始化,则需要使用拷贝构造函数

7.5 调用父类的拷贝构造函数

父类和子类的成员变量应该为私有,公有只为调试使用

#include 
using namespace std;

class Person {
	//int m_age;
public:
	int m_age;
	Person(int age=0) :m_age(age) { }
	//拷贝构造
	Person(const Person &person) :m_age(person.m_age) { }
};

class Student :public Person {
	//int m_score;
public:
	int m_score;
	Student(int age = 0,int score=0) :Person(age),m_score(score) {}
	//拷贝构造
	Student(const Student &student) :Person(student), m_score(student.m_score) {}
};


int main() {
	Student stu1(18, 100);
	Student stu2(stu1);
	cout << stu2.m_age << endl;
	cout << stu2.m_score << endl;
	return 0;
}
输出:
18
100

如果不使用拷贝构造,仍可以初始化父类的所有的成员变量

#include 
using namespace std;

class Car {
public:
	int m_price;
	int m_length;
	Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
		cout << "Car(int price = 0, int length = 0)" << endl;
	}
	Car(const Car &car) :m_price(car.m_price), m_length(car.m_length) {
		cout << "Car(const Car &car)" << endl;
	}
	void display() {
		cout << "price=" << m_price << ", length=" << m_length << endl;
	}
};

int main() {
	Car car1(100, 5);
	Car car2(car1);
	Car car3 = car2;
	Car car4;
	car4 = car3;

	return 0;
}
输出:
Car(int price = 0, int length = 0)
Car(const Car &car)
Car(const Car &car)
Car(int price = 0, int length = 0)
对象 输出
Car car1(100, 5);
Car car2(car1);
Car car3 = car2;
Car car4;
Car(int price = 0, int length = 0)
Car(const Car &car)
Car(const Car &car)
Car(int price = 0, int length = 0)

这里 来进一步理解一下:car4 = car3;

#include 
using namespace std;

class Car {
public:
	int m_price;
	int m_length;
	Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
		cout << "Car(int price = 0, int length = 0)" << endl;
	}
	Car(const Car &car) :m_price(car.m_price), m_length(car.m_length) {
		cout << "Car(const Car &car)" << endl;
	}
	void display() {
		cout << "price=" << m_price << ", length=" << m_length << endl;
	}
};

int main() {
	Car car1(100, 5);
	Car car2(car1);
	Car car3 = car2;
	Car car4;

	car4.display();
	car4 = car3;
	car4.display();
	return 0;
}
输出:
Car(int price = 0, int length = 0)
Car(const Car &car)
Car(const Car &car)
Car(int price = 0, int length = 0)
price=0, length=0
price=100, length=5

car4 = car3; 不满足利用一个已经存在的对象,去初始化一个新对象
所以这里只是单纯的赋值(并没有调用拷贝构造函数)

7.6 拷贝构造函数-固定写法剖析

Car(const Car &car) {
		cout << "Car(const Car &car)" << endl;
}
7.6.1 为什么拷贝构造函数的参数定义要使用 const 与 & 限制条件?
  1. 使用 & 为了避免传入的对象参数类型,产生不必要的中间对象(而且出现无限套娃:死循环)
  2. 使用 const 可以接收 const 与 非const,所以增加参数的接收范围

8. 浅拷贝、深拷贝

8.1 编译器默认的提供的拷贝是浅拷贝(shallow copy)

#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
public:
	Car(int price = 0, char* name = NULL) :m_price(price), m_name(name) { }
	void display() {
		cout << "price is" << m_price << ", name is" << m_name << endl;
	}
};

int main() {
	const char* name1 = "bmw"; // 字符串需要用const修饰
	// 存放在栈空间
	char name[] = { 'b','m','w','\0' }; // name 和 name2 等价,\0为字符串结束 name2比name字符串长度多1
	//cout << strlen(name) << endl;
	Car* car = new Car(100, name);

	return 0;
}

如上代码执行的,内存分布
char name[] 的 name存储在空间
Car* car的 car指针存储在空间

8.1.1 堆空间指向栈空间(很危险)
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第31张图片

如图所示:栈空间的car指针指向了堆空间的car对象,堆空间中car对象的m_name指向了name数组
这种做法很危险:堆空间指向栈空间(栈空间无法自己控制生命周期,堆空间可以)

可见当栈空间回收后,如下图代码执行结果:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第32张图片

代码如下:

#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
public:
	Car(int price = 0, char* name = NULL) :m_price(price), m_name(name) { }
	void display() { 
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};
Car* g_car;
void test(){
	char name[] = { 'b','m','w','\0' }; 
	g_car = new Car(100, name);
}

int main() {
	test();
	g_car->display();

	return 0;
}
g_car = new Car(100, name); 

堆空间中 name 指针没有回收,所以效果如下图:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第33张图片

如上图中堆空间m_name指向空,此时成为野指针

改进后将堆空间的m_name指向堆空间新开辟的一块内存存放 name 数组,如下图:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第34张图片
8.1.2 strcpy在vs2019中的使用
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第35张图片
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第36张图片
8.1.3 在堆空间->堆空间与堆空间->栈空间

1)堆空间->堆空间,不会被栈空间回收所影响堆空间中的指针,导致野指针

#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
public:

	Car(int price = 0, char* name = NULL) :m_price(price) { 
		if (name == NULL) return;
		//申请新的堆空间
		m_name = new char[strlen(name) + 1]{};// 使用 {} 可以保证最后一个字符是 \0
		//拷贝字符串数据到新的堆空间
		strcpy(m_name, name);
	}
	void display() { 
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {

	char name[] = { 'b','w','m','\0' };
	Car* car = new Car(100, name);
	//test();
	car->display();

	return 0;
}

内存剖析

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第37张图片

2)堆空间->栈空间,会被栈空间回收所影响堆空间中的指针,导致野指针产生

#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
public:
	Car(int price = 0, char* name = NULL) :m_price(price), m_name(name) { 

	}
	void display() { 
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {

	char name[] = { 'b','w','m','\0' };
	Car* car = new Car(100, name);

	car->display();

	return 0;
}

内存剖析

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第38张图片
8.1.3 构造函数参数加上 const 同时接收字符串和字符数组
#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
public:

	Car(int price = 0,const char* name = NULL) :m_price(price) { 
		if (name == NULL) return;
		//申请新的堆空间
		m_name = new char[strlen(name) + 1]{};// 使用 {} 可以保证最后一个字符是 \0
		//拷贝字符串数据到新的堆空间
		strcpy(m_name, name);
	}
	void display() { 
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {

	char name[] = { 'b','w','m','\0' };
	Car* car = new Car(100, name);
	Car* car2 = new Car(100, "xiali");

	//test();
	car->display();
	car2->display();

	return 0;
}
输出:
price is100, name is bwm
price is100, name is xiali

8.2 浅拷贝(shallow copy)

浅拷贝:指针类型的变量只会拷贝地址值

8.2.1 double free 双重析构双重释放

由于第二次释放的指向为空,就会报错(导致崩溃)
代码如下:

#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
public:
	Car(int price = 0,const char* name = NULL) :m_price(price) { 
		if (name == NULL) return;
		//申请新的堆空间
		m_name = new char[strlen(name) + 1]{};// 使用 {} 可以保证最后一个字符是 \0
		//拷贝字符串数据到新的堆空间
		strcpy(m_name, name);
	}
	//析构函数
	~Car() {
		if (m_name == NULL) return;
		delete[] m_name; // 删除数组
		m_name = NULL;	
	}
	void display() { 
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {

	Car car1(100, "BWM");
	Car car2 = car1;
	car2.display();

	return 0;
}

如下内存剖析

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第39张图片

原理图

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第40张图片

8.3 深拷贝(deep copy)

深拷贝:将指针指向的内容拷贝到新的存储空间

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第41张图片
8.3.1 深拷贝使用场景

当成员变量都是int类型(基本数据类型),没必要使用拷贝构造函数
如果成员变量有指向堆空间的指针,则需要使用拷贝构造函数,实现深拷贝(防止double free)

代码如下: (提示:Car car2(car1); 等价于 Car car3 = car2; 都是用一个已经存在的对象去创建一个新对象,会调用拷贝构造)

#include 
using namespace std;

class Car{
	int m_price;
	char* m_name;
	void copy(const char* name) {
		if (name == NULL) return;
		//申请新的堆空间
		m_name = new char[strlen(name) + 1]{};// 使用 {} 可以保证最后一个字符是 \0
		//拷贝字符串数据到新的堆空间
		strcpy(m_name, name);
	}
public:
	Car(int price = 0,const char* name = NULL) :m_price(price) { 
		copy(name);
	}
	//拷贝构造
	Car(const Car &car):m_price(car.m_price) {
		copy(car.m_name);
	}
	//析构函数
	~Car() {
		if (m_name == NULL) return;
		delete[] m_name; // 删除数组
		m_name = NULL;	
	}
	void display() { 
		cout << "price is" << m_price << ", name is " << m_name << endl;
	}
};

int main() {

	Car car1(100, "BWM");
	Car car2 = car1;
	car2.display();

	return 0;
}

内存剖析

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第42张图片

8.4 总结

8.4.1 编译器默认的提供的拷贝是浅拷贝(shallow copy)

1)将一个对象中所有成员变量的值拷贝到另一个对象
2)如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
3)可能会导致堆空间多次free的问题

8.4.2 如果需要实现深拷贝(deep copy),就需要自定义拷贝构造函数

将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间

示例一:

#include 
using namespace std;

class Car {
	int m_price;
};
class Person {
	int m_age;
	Car car;
};
int main() {
	return 0;
}

,这段代码等价于

#include 
using namespace std;

class Car {
	int m_price;
};
class Person {
	int m_age;
	int m_price;
};

int main() {
	return 0;
}

是否需要拷贝构造根据需求


示例二:

#include 
using namespace std;

class Car {
	int m_price;
	char* m_name;
};
class Person {
	int m_age;
	Car m_car;
};

int main() {
	return 0;
}

这段代码等价于

#include 
using namespace std;

class Car {
	int m_price;
	char* m_name;
};

class Person {
	int m_age;
	int m_price;
	char* m_name;
};


int main() {
	return 0;
}

是否需要拷贝构造根据需求 m_name 是否需要每个对象都不同

这块相关内容,可以参考
C++挖掘程序本质(第二章C++面向对象-中)李明杰-M了个J 配套教材 这篇文章中的 1.内存管理的4张内存图

9. 对象型参数和返回值

使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象

在C++中建议:不要直接使用对象类型参数(可以改为指针类型或引用类型)

9.1 对象型参数

#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {
		cout << "Car()" << endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car)" << endl;
	}
	void run() {
		cout << "run()" << endl;
	}
};

void test(Car car) {
}

int main() {
	Car car;
	test(car);
	return 0;
}
输出:
Car()
Car(const Car &car)

输出多了一个拷贝构造函数,因为 test() 函数的参数是Car对象
相当于 void test(Car car=car) {}
Car car=car 这是拷贝构造的本质:由一个已经存在的对象,去创建一个新的对象
所以这才会调用 拷贝构造函数

修改后代码如下:

#include 
using namespace std;
class Car {
public:
	int m_price;
	Car() {
		cout << "Car()" << endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car)" << endl;
	}
	/*~Car() {
		cout << "~Car()" << endl;
	}*/
	void run() {
		cout << "run()" << endl;
	}
};

// 由对象改为引用,不用再去调用拷贝构造函数
void test(Car &car) {}
int main() {
	Car car;
	test(car);
	return 0;
}
输出:Car()

9.2 对象型返回值

#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {
		cout << "Car() - " <<this<< endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car) - " << this << endl;
	}
	/*~Car() {
		cout << "~Car()" << endl;
	}*/
	void run() {
		cout << "run()" << endl;
	}
};

Car test2() {
	Car car;
	return car;
}

int main() {
	Car car2;
	car2 = test2();
	return 0;
}
输出:
Car() - 00F3FD7C
Car() - 00F3FC88
Car(const Car &car) - 00F3FCB0

输出多了一个拷贝构造函数,因为 test2() 函数的返回值是Car对象
相当于 void test2() { Car car; return car;}
car2 = test2(); 相当于 car2 = Car(test()),在main中将test2进行了拷贝构造
所以这才会调用 拷贝构造函数

9.3 多此产生拷贝构造的优化

本来函数执行完会产生一个car对象(存在于main函数的栈空间),但编译器发现car3可以当做存这个car对象的栈空间,就少一次拷贝

#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {
		cout << "Car() - " <<this<< endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car) - " << this << endl;
	}
	/*~Car() {
		cout << "~Car()" << endl;
	}*/
	void run() {
		cout << "run()" << endl;
	}
};

Car test2() {
	Car car;
	return car;
}

int main() {
	/*Car car;
	test(car);*/
	/*Car car2;
	car2 = test2();*/

	Car car3 = test2();
	return 0;
}
输出:
Car() - 012FF728
Car(const Car &car) - 012FF810

由于编译器的优化,由2次Car(const Car &car) 变为了一次

10. 匿名对象(临时对象)

匿名对象:没有变量名、没有被指针指向的对象,用完后马上调用析构函数

#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {
		cout << "Car() - " <<this<< endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car) - " << this << endl;
	}
	/*~Car() {
		cout << "~Car()" << endl;
	}*/
	void run() {
		cout << "run()" << endl;
	}
};

int main() {
	cout << 1 << endl;
	Car();
	cout << 2 << endl;

	return 0;
}
输出:
1
Car() - 0115F734
2
int main() {
	cout << 1 << endl;
	Car().run();
	cout << 2 << endl;

	return 0;
}
输出:
1
Car() - 005FF854
run()
2

10.1 传入匿名对象

匿名对象传入对象类型参数,只调用一次构造与析构

#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {
		cout << "Car() - " <<this<< endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car) - " << this << endl;
	}
	~Car() {
		cout << "~Car()" << endl;
	}
	void run() {
		cout << "run()" << endl;
	}
};

// 由对象改为引用,不用再去调用拷贝构造函数
void test(Car car) {
}

int main() {
	test(Car());
	return 0;
}
输出:
Car() - 012FF9BC
~Car()

10.2 返回匿名对象

#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {
		cout << "Car() - " <<this<< endl;
	}
	Car(const Car &car) {
		cout << "Car(const Car &car) - " << this << endl;
	}
	~Car() {
		cout << "~Car()" << endl;
	}
	void run() {
		cout << "run()" << endl;
	}
};

Car test3() {
	return Car();
}

int main() {
	Car car2;
	car2 = test3();

	return 0;
}
输出:
Car() - 003EF780
Car() - 003EF6B4
~Car()
~Car()

11. 隐式构造

C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数

11.1 正常创建对象

#include 
using namespace std;

class Person {
	int m_age;
public:
	
	Person() {
		cout << "Person() - " << this << endl;
	}
	Person(int age):m_age(age) {
		cout << "Person(int) - " << this << endl;
	}
	Person(const Person &person){
		cout << "Person(const Person &person) - " << this << endl;
	}
	~Person() {
		cout << "~Person() - " << this << endl;
	}
	void display() {
		cout << "display() -age is "<< m_age << endl;
	} 
};

int main() {
	Person p1;
	Person p2(10);
	Person p3 = p2;
	return 0;
}
输出:
Person() - 00D9FAB4
Person(int) - 00D9FAA8
Person(const Person &person) - 00D9FA9C

~Person() - 00D9FA9C
~Person() - 00D9FAA8
~Person() - 00D9FAB4

11.2 隐式的调用int类型构造函数

隐式的调用int类型构造函数,而不是将int数值赋值给person对象

#include 
using namespace std;

class Person {
	int m_age;
public:
	
	Person() {
		cout << "Person() - " << this << endl;
	}
	Person(int age):m_age(age) {
		cout << "Person(int) - " << this << endl;
	}
	Person(const Person &person){
		cout << "Person(const Person &person) - " << this << endl;
	}
	~Person() {
		cout << "~Person() - " << this << endl;
	}
	void display() {
		cout << "display() -age is "<< m_age << endl;
	} 
};




int main() {
	Person p1 = 20;
	// 俩种等价写法
	Person p2(20);

	return 0;
}
输出:
Person(int) - 013FF960
Person(int) - 013FF954
~Person() - 013FF954
~Person() - 013FF960

11.3 对象型参数(隐式的调用)

#include 
using namespace std;

class Person {
	int m_age;
public:
	
	Person() {
		cout << "Person() - " << this << endl;
	}
	Person(int age):m_age(age) {
		cout << "Person(int) - " << this << endl;
	}
	Person(const Person &person){
		cout << "Person(const Person &person) - " << this << endl;
	}
	~Person() {
		cout << "~Person() - " << this << endl;
	}
	void display() {
		cout << "display() -age is "<< m_age << endl;
	} 
};


void test1(Person person) {
}

int main() {
	test1(30);
	return 0;
}
输出:
Person(int) - 008FFAB0
~Person() - 008FFAB0

11.4 对象型返回值(隐式的调用)

#include 
using namespace std;

class Person {
	int m_age;
public:
	Person() {
		cout << "Person() - " << this << endl;
	}
	Person(int age):m_age(age) {
		cout << "Person(int) - " << this << endl;
	}
	Person(const Person &person){
		cout << "Person(const Person &person) - " << this << endl;
	}
	~Person() {
		cout << "~Person() - " << this << endl;
	}
	void display() {
		cout << "display() -age is "<< m_age << endl;
	} 
};

void test1(Person person) {
}

Person test2() {
	return 40;
	//等价于
	//return Person(40);
}

int main() {
	test2();

	return 0;
}
输出:
Person(int) - 00B3FB80
~Person() - 00B3FB80

11.5 先显示创建对象,再隐式创建对象

#include 
using namespace std;

class Person {
	int m_age;
public:
	Person() {
		cout << "Person() - " << this << endl;
	}
	Person(int age):m_age(age) {
		cout << "Person(int) - " << this << endl;
	}
	Person(const Person &person){
		cout << "Person(const Person &person) - " << this << endl;
	}
	~Person() {
		cout << "~Person() - " << this << endl;
	}
	void display() {
		cout << "display() -age is "<< m_age << endl;
	} 
};


void test1(Person person) {

}

Person test2() {
	return 40;
}

int main() {
	Person p1;
	p1 = 40;

	return 0;
}
输出:
Person() - 010FFAB0
Person(int) - 010FF9E4
~Person() - 010FF9E4
~Person() - 010FFAB0

11.6 explicit 禁止调用隐式构造

为了防止代码奇异,阅读者混淆,可读性很差

class Person {
	int m_age;
public:
	Person() {
		cout << "Person() - " << this << endl;
	}
	explicit Person(int age):m_age(age) {
		cout << "Person(int) - " << this << endl;
	}
	Person(const Person &person){
		cout << "Person(const Person &person) - " << this << endl;
	}
	~Person() {
		cout << "~Person() - " << this << endl;
	}
	void display() {
		cout << "display() -age is "<< m_age << endl;
	} 
};

12. 编译器自动生成的构造函数

C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数

12.1 成员变量在声明的同时进行了初始化

默认不写构造函数,创建对象(非new Person())的方式,不会生成构造函数,如下图:

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第43张图片

成员变量在声明的同时进行了初始化,会产生构造函数


#include 
using namespace std;

class Person {
public:
	int m_age =5;
};

int main() {
	Person person;

	return 0;
}
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第44张图片

12.2 有定义虚函数

定义虚函数,会产生构造函数

#include 
using namespace std;

class Person {
public:
	int m_age;
	virtual void run() {
	}
};

int main() {
	Person person;

	return 0;
}
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第45张图片

12.3 虚继承了其他类


#include 
using namespace std;

class Person {
public:
	int m_age;
	virtual void run() {
	}
};

class Student:virtual public Person {
public:
	int m_age;
	virtual void run() {
	}
};

int main() {
	Person person;

	return 0;
}
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第46张图片

12.4 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义)

person 包含了car对象,如何car内没有构造函数,则编译器不会对person创建构造函数
如下代码:person会生成构造函


#include 
using namespace std;

class Car {
public:
	int m_price;
	Car() {}
};


class Person {
public:
	Car m_car;
};

int main() {
	Person person;

	return 0;
}
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第47张图片

person会生成构造函

#include 
using namespace std;

class Car {
public:
	int m_price=0;
};

class Person {
public:
	Car m_car;
};

int main() {
	Person person;

	return 0;
}

12.5 父类有构造函数(编译器生成或自定义)

#include 
using namespace std;

class Person {
public:
	int m_price=0;
};

class Student : public Person {
public:
	
};

int main() {
	Student student;

	return 0;
}

12.6 总结

对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会其自动生成无参的构造函数

13. 友元

友元包括友元函数和友元类
友元函数和友元类不能混用
友元不受public、private、protected限制

引入使用场景,代码如下:

#include 
using namespace std;

class  Point
{
private:
	int m_x;
	int m_y;
public:
	int getX() { return m_x; }
	int getY() { return m_y; }
	Point(int x, int y) :m_x(x), m_y(y) {}
	void display() {
		cout << "(" << m_x << "," << m_y << ")" << endl;
	}

};
Point add(Point p1 ,Point p2) {
	return Point(p1.getX() + p2.getX(), p1.getY()+p2.getY());
}
int main() {
	Point p1(10, 20);
	Point p2(20, 30);
	Point p3 = add(p1, p2);
	p3.display();

	return 0;
}

如果这里频繁使用 add() 函数,每执行一次add()就调用4次 get() 函数,而且是只读
这里如果在add() 里面访问类中的私有成员变量

13.1 友元函数

如果将函数A(非成员函数,即:类外部)生命为类C的友元函数,那么在函数A内部就能直接访问类C对象的所有成员

#include 
using namespace std;

class  Point
{
	friend Point add(Point, Point);
private:
	int m_x;
	int m_y;
public:
	int getX() { return m_x; }
	int getY() { return m_y; }

	Point(int x, int y) :m_x(x), m_y(y) {}
	void display() {
		cout << "(" << m_x << "," << m_y << ")" << endl;
	}

};

Point add(Point p1 ,Point p2) {
	//return Point(p1.getX() + p2.getX(), p1.getY()+p2.getY());
	return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);

}


int main() {
	Point p1(10, 20);
	Point p2(20, 30);
	Point p3 = add(p1, p2);
	p3.display();

	return 0;
}
输出:(30,50)

13.2 友元类

如果将类A声明为类C的友元类,那么在类A的所有成员函数内部都能直接访问类C对象的所有成员

#include 
using namespace std;

class  Point
{
	//friend Point add(Point, Point);
	friend class Math;
private:
	int m_x;
	int m_y;
public:
	int getX() { return m_x; }
	int getY() { return m_y; }

	Point(int x, int y) :m_x(x), m_y(y) {}
	void display() {
		cout << "(" << m_x << "," << m_y << ")" << endl;
	}
};

class Math {
public:
	Point add(Point p1, Point p2) {
		//return Point(p1.getX() + p2.getX(), p1.getY()+p2.getY());
		return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);

	}
	void test() {
	
		Point p1(10, 20);
		p1.m_x = 10;

	}
};

int main() {
	Point p1(10, 20);
	Point p2(20, 30);
	Point p3 = Math().add(p1, p2);
	p3.display();

	return 0;
}


13.3 利弊

友元破坏了面向对象的封装性
在某些频繁访问成员变量的地方可以提高性能

14. 内部类

定义:如果将类A按定义在类C的内部,那么类A就是一个内部类(嵌套类)

14.1 使用场景

如果需要对一个类进行访问权限控制-使用内部类

#include 
using namespace std;

class  Person{
	int m_age;
public:
	class Car {
		int m_price;
	};
};

int main() {
	Person::Car car1;
	return 0;
}

14.2 内部类的特点

14.2.1 支持public、protected、private权限

可以通过权限达到对类的控制

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第48张图片
14.2.2 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
#include 
using namespace std;

class  Person{
	int m_age;
	void test(){
		Car car;
		car.m_price = 10; // 报错
	}

protected:
	class Car {
		int m_price;
		void run() {
			Person person;
			person.m_age = 10;
		}
	};
};

int main() {
	//Person::Car car1;

	return 0;
}
C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第49张图片
14.2.3 成员函数可以直接不带类名、对象名访问其外部类的static成员
#include 
using namespace std;

class  Person{
	static int m_age;
	static void test(){
	}

protected:
	class Car {
		int m_price;
		void run() {
			m_age = 10;
			test();
			/*Person person;
			person.m_age = 10;*/
		}
	};
};

int main() {
	//Person::Car car1;
	return 0;
}
14.2.4 不会影响外部类的内存布局

person 对象 只占4个字节(age的大小)

#include 
using namespace std;

class  Person{
	 int m_age;
	 void test(){
		//Car car;
		//car.m_price = 10; // 报错
	}

protected:
	class Car {
		int m_price;
		void run() {
		}
	};
};

int main() {
	//Person::Car car1;
	Person person;
	return 0;
}
14.2.5 可以再外部类内部声明,在外部类外面进行定义

内部类-声明和实现分离

C++挖掘程序本质(第二章C++面向对象-下)李明杰-M了个J 配套教材_第50张图片

15. 局部类

在意函数内部定义的类,成为局部类

#include 
using namespace std;

void test() {
	//局部类
	class Car {
	};
}
int main() {
	return 0;
}

15.1 局部类得特点

15.1.1 作用域仅限于所在函数内部
15.1.2 其所有的成员必须定义在类内部,不允许定义static成员变量

因为 static 的初始化要在类的外部,与上一条规则冲突

15.1.3 成员函数不能直接访问函数的局部变量(static 变量除外)
#include 
using namespace std;

void test() {
	static int age = 10;
	//局部类
	class Car {
		void run() {
			age = 20;
		}
	};
}

int main() {
	return 0;
}
15.1.4 不会影响外部类的内存布局
#include 
using namespace std;

void test() {
	static int age = 10;
	//局部类
	class Car {
	public:
		void run() {
			age = 20;
		}
	};
	Car car;
	car.run();
}

int main() {
	return 0;
}

其中这段代码,是放在代码段(区)的不占用其他内存

	//局部类
	class Car {
	public:
		void run() {
			age = 20;
		}
	};
15.1.5 可以再外部类内部声明,在外部类外面进行定义

到此面向对象部份全部结束

主要以b站免费学习资源
打造同进度学习的同学
在没有up主回答的情况下
通过一起学习组队可以互相解决观看视频中自己出现的问题,通过教学相长的方式,将知识可以牢固掌握。
关于文章中的问题欢迎指正,如有其它问题也可以私信,看到会第一时间回复
我们的目标是:学习是我们终身的任务

你可能感兴趣的:(C++与汇编挖掘代码本质,c++,内存管理,编程语言,底层应用开发,c语言)