由于在CSDN上无法看到markdown的目录,有需要额小伙伴可以联系我,附送本文的 .md文件,可以再本地typora上更加方便的学习
这篇文章和 汇编入门基础(点此链接) 为想结合内容,请大家在学习时可以同时参考
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
根据继承的先后顺序,先继承的在内存的前面(和父类定义的顺序无关)
一般情况由于封装,父类的成员变量为私有,只能用构造函数去调用父类成员变量
#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;
}
如图可见:
如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表(地址)
#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
如图可见:
如果子类没有重写父类函数
如下代码:
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 的虚函数地址
#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()
#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
汇编剖析:
内存剖析:
菱形继承带来的问题:
1)最底下子类从基类继承的成员变量冗余、重复
2)最底下子类无法访问基类的成员,有二义性
二义性:
代码如下:
#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
虚继承可以解决菱形继承带来的问题
Person 类被称为虚基类
代码如下:
#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
只要有一个不是虚继承,则破坏虚继承结构,导致调用基类成员变量产生二义性
1)虚继承:父类成员变量放最后
2)继承:父类成员变量放最前面
代码片段分析:
// 父类
struct Person {
int m_age;
};
// 子类
struct Student: virtual Person {
int m_score;
};
即:4+4+4(x86下虚表地址4个字节,父类Person 的 m_age 占4个字节,子类Student 的 m_score 占4个字节)
1) 虚表指针与本类起始的偏移量(一般是0)
2) 虚基类第一成员变量与本类起始的偏移量
// 父类
struct Person {
int m_age;
};
// 子类
struct Student: virtual Person {
int m_score;
};
虚表中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
内存剖析:
静态成员:被static修饰的成员变量、函数
如何调用:
1)通过对象:对象.静态成员
2)通过对象指针:对象指针->静态成员
3)通过类访问:类型::静态成员
静态成员与全局变量、函数
唯一区别就是:增加访问限制,仅此而已
#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
如果类的声明和实现分离(在实现的 .cpp 中初始化)
#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()
this 指针只能用在非静态成员函数内部
默认在类内部调用成员变量会有this指针
由于static变量内存在全局区,所以不存在this指针,所以不能调用非静态成员
虚函数只能是非静态成员函数
(修订:图中在Car中,静态成员函数不应该有{} 主体)
对于非静态成员变量:将值写入ebp(基址指针寄存器(extended base pointer))
对于静态成员变量:将值写入 ds(全局区、数据段:data segment )
因为静态成员在全局区(代码段)所以内存是唯一的
#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
#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
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;
}
第一步,保证类永远只创建一个对象
#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;
1)利用运算符重写=,将赋值运算符=重写,为了不让对象成员赋值
2)拷贝构造函数私有化
const成员:被const修饰的成员变量、非静态成员函数
初始化方式一,声明时直接初始化
#include
using namespace std;
class Car {
public:
//初始化方式一
const int m_price=0;
Car() {}
};
int main() {
return 0;
}
初始化方式二,构造函数的初始化列表中初始化
#include
using namespace std;
class Car {
public:
//初始化方式一
const int m_price;
Car():m_price(0){}
};
int main() {
return 0;
}
如下代码:
#include
using namespace std;
class Car {
public:
//初始化方式一
static const int m_price=0;
Car() {}
};
int main() {
return 0;
}
#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;
}
const 成员函数 不能调用非static、const函数
const 成员函数 可以调用非static函数
因为static 函数不可以调用普通成员变量(只能调用 static 成员)
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成员可与
#include
using namespace std;
class Car {
public:
//初始化方式一
int m_price;
Car() {}
void test() const{
}
void run() {
test();
}
};
int main() {
return 0;
}
#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
当非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;
}
代码如下:
#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;
}
#include
using namespace std;
class Person{
int age;
int& m_price = age;
public:
Person(int& price) :m_price(price) { }
}
int main() {
return 0;
}
拷贝构造函数是构造函数的一种
构造函数:在创建完对象时(初始化)创建
拷贝构造函数:利用一个已经存在的对象,来创建一个新对象
#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)
如: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,完成拷贝
#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
拷贝构造函数中使用初始化列表
#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;
}
如果全部需要将成员变量在创建对象时初始化,则不需要使用拷贝构造函数
如果部分需要将成员变量在创建对象时初始化,则需要使用拷贝构造函数
父类和子类的成员变量应该为私有,公有只为调试使用
#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; 不满足利用一个已经存在的对象,去初始化一个新对象
所以这里只是单纯的赋值(并没有调用拷贝构造函数)
Car(const Car &car) {
cout << "Car(const Car &car)" << endl;
}
#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指针存储在栈空间
如图所示:栈空间的car指针指向了堆空间的car对象,堆空间中car对象的m_name指向了name数组
这种做法很危险:堆空间指向栈空间(栈空间无法自己控制生命周期,堆空间可以)
可见当栈空间回收后,如下图代码执行结果:
代码如下:
#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 指针没有回收,所以效果如下图:
如上图中堆空间m_name指向空,此时成为野指针
改进后将堆空间的m_name指向堆空间新开辟的一块内存存放 name 数组,如下图:
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;
}
内存剖析
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;
}
内存剖析
#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
浅拷贝:指针类型的变量只会拷贝地址值
由于第二次释放的指向为空,就会报错(导致崩溃)
代码如下:
#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;
}
如下内存剖析
原理图
深拷贝:将指针指向的内容拷贝到新的存储空间
当成员变量都是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;
}
内存剖析
1)将一个对象中所有成员变量的值拷贝到另一个对象
2)如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
3)可能会导致堆空间多次free的问题
将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
示例一:
#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张内存图
使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象
在C++中建议:不要直接使用对象类型参数(可以改为指针类型或引用类型)
#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()
#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进行了拷贝构造
所以这才会调用 拷贝构造函数
本来函数执行完会产生一个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) 变为了一次
匿名对象:没有变量名、没有被指针指向的对象,用完后马上调用析构函数
#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
匿名对象传入对象类型参数,只调用一次构造与析构
#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()
#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()
C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数
#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
隐式的调用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
#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
#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
#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
为了防止代码奇异,阅读者混淆,可读性很差
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;
}
};
C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数
默认不写构造函数,创建对象(非new Person())的方式,不会生成构造函数,如下图:
成员变量在声明的同时进行了初始化,会产生构造函数
#include
using namespace std;
class Person {
public:
int m_age =5;
};
int main() {
Person person;
return 0;
}
定义虚函数,会产生构造函数
#include
using namespace std;
class Person {
public:
int m_age;
virtual void run() {
}
};
int main() {
Person person;
return 0;
}
#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;
}
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;
}
person会生成构造函
#include
using namespace std;
class Car {
public:
int m_price=0;
};
class Person {
public:
Car m_car;
};
int main() {
Person person;
return 0;
}
#include
using namespace std;
class Person {
public:
int m_price=0;
};
class Student : public Person {
public:
};
int main() {
Student student;
return 0;
}
对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会其自动生成无参的构造函数
友元包括友元函数和友元类
友元函数和友元类不能混用
友元不受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() 里面访问类中的私有成员变量
如果将函数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)
如果将类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;
}
友元破坏了面向对象的封装性
在某些频繁访问成员变量的地方可以提高性能
定义:如果将类A按定义在类C的内部,那么类A就是一个内部类(嵌套类)
如果需要对一个类进行访问权限控制-使用内部类
#include
using namespace std;
class Person{
int m_age;
public:
class Car {
int m_price;
};
};
int main() {
Person::Car car1;
return 0;
}
可以通过权限达到对类的控制
#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;
}
#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;
}
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;
}
内部类-声明和实现分离
在意函数内部定义的类,成为局部类
#include
using namespace std;
void test() {
//局部类
class Car {
};
}
int main() {
return 0;
}
因为 static 的初始化要在类的外部,与上一条规则冲突
#include
using namespace std;
void test() {
static int age = 10;
//局部类
class Car {
void run() {
age = 20;
}
};
}
int main() {
return 0;
}
#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;
}
};
到此面向对象部份全部结束
主要以b站免费学习资源
打造同进度学习的同学
在没有up主回答的情况下
通过一起学习组队可以互相解决观看视频中自己出现的问题,通过教学相长的方式,将知识可以牢固掌握。
关于文章中的问题欢迎指正,如有其它问题也可以私信,看到会第一时间回复
我们的目标是:学习是我们终身的任务