C++中的重难点看这一篇就够了

2023-05-16

sizeof()是一个运算符,不是一个函数

看程序效率的快慢,可以直接看其汇编语言程序的多少

扩展名:

c语言:c

c++:cpp

Java:先有类,再有方法

c++完全兼容c语法

getchar()等待键盘输入(如果敲回车,就会读取键盘输入)



函数重载(overload)

  1. c语言不支持函数重载

  2. 两个函数的函数名完全相同,但是函数参数类型,个数,顺序不同
    返回值类型不同,不能构成重载

  3. 隐式转换(小转大)也可在函数参数类型中适用,但要注意隐式转换有可能产生二义性。

本质:

采用了name mangling 或者叫 name decoration技术

	C++编译器默认会对符号(比如函数名)进行改编、修饰

​	重载时会生成多个不同的函数名,不同编译器(MSVC、g++)有不同的生成规则

IDA pro 逆向工程 软件


Debug模式:很多调试信息

Release模式:去除调试信息,生成的可执行文件比较精简、高效,会默认对函数进行优化

visual stdio 2019可选择不对其进行优化



默认参数

C++允许函数设置默认参数,在调用时可以根据情况省略实参。规则如下:

​ · 默认参数只能按照右到左的顺序

​ · 如果函数同时有声明、实现,默认参数只能放在函数声明中

​ · 默认参数的值可以是常量、全局符号(全局变量、函数名)

void(*p)(int) = test; //此语句的作用就是更换test函数的名称,其中void指test函数的返回类型,int指test函数中参数类型,p为将要命名的函数名称。
p(10); 
#include <iostream>
using namespace std;

void func(int v1, void(*p)(int)=test)
{
	p(v1);
}

void test(int a)
{
	cout << "test(int)-" << a << endl;
}

int main()
{
    func(30);
	func(10, test);
    return 0;
}

如果函数的实参经常是一个值,可以考虑使用默认参数

函数重载 、默认参数可能会产生冲突、二义性(建议优先选择使用默认参数)
例:

void display(int a,int b=20)
{
	cout<<"a is"<<a<<endl;
}

void display(int a)
{
	cout<<"a is"<<a<<endl;
}

int main()
{
	display(10);//该语句会产生二义性
	return 0;
}

默认参数的本质

	自动补充参数

extern “C”

被extern "C"修饰的代码会按照c语言的方式编译

#include <iostream>
using namespace std;

extern "C" void func()
{

}

extern "C" void func(int v)
{

}

int main()
{
	return 0;
}

写法

extern "C" 
{
	void func()
	{
	
	}
	void func(int v)
	{
	
	}
}


extern "C" void func()
{

}

:以上代码报错,因为C语言中不支持函数重载

如果函数同时有声明和实现,要让函数声明被extern “C”修饰,函数实现可以不修饰。

#include <iostream>
using namespace std;

extern "C" void func();

int mian()
{
	return 0;
}

extern "C" void func()
{

}


**如果相同的函数以不同的语言编译,所代表的函数不相同**


#include <iostream>
using namespace std;

void func();
extern "C" void func(int v);//除去参数V报错原因与重载无关,是二义性

int main()
{
	func();
	func(10);
	return 0;
}

void func()
{
	cout << "func()" << endl;
}

 void func(int v)
{
	cout << "func(int v)-"<<v << endl;
}

externC2-C、C++混合开发

由于C、C++编译规则的不同,再C、C++混合开发时,可能会经常出现一下操作

extern 无法在C语言中使用

每个C++文件之前都隐含着一个定义 #define __cplusplus

可根据这个特点来使头文件在不同的语言文件中判断是否添加extern ”C”

如果定义了 __cplusplus这个,则extern "C"参与编译

math.h头文件的定义

#ifdef __cplusplus   
extern "C"{
#endif

	int sum(int v1, int v2);
	int delta(int v2, int v3);
	int divide(int v1, int v2);

#ifdef __cplusplus
}
#endif

C++可以不导入头文件,但必须要写声明,头文件其实就是声明

重复包含头文件,浪费编译器工作时间

为了防止重复包含头文件**#ifndef…#endif**

#ifndef 1			//为了防止重复定义头文件

#ifdef __cplusplus
extern "C" {
#endif


	int sum(int v1, int v2);
	int delta(int v2, int v3);
	int divide(int v1, int v2);

#ifdef __cplusplus
}
#endif

#endif // !1


注意:防止重复包含头文件的宏都是唯一的,一般使用__文件名

pragma once

我们经常使用#ifndef、#define、#endif来防止头文件的内容被重复包含

#pragma once可以防止整个文件的内容被重复包含

使用:放在头文件中最前面

区别:

1.#ifndef、#define、#endif受C\C++标准的支持,不受编译器的任何限制

2.有些编译器不支持#pragma once(较老编译器不支持,如GCC3.4版本之前),兼容性不够好

3.#ifndef、#define、#endif可以针对一个文件中的部分代码,而#pragma once只能针对整个文件

内联函数(inline function)

使用inline修饰的函数的声明或者实现,可以使其变为内联函数**

将函数展开为函数体代码

#include <iostream>
using namespace std;

inline int sum(int v1, int v2);  //将函数调用展开为函数体代码

inline void func()
{
	cout << "func()" << endl;
	cout << "func()1" << endl;
}


int main()
{
	func();
	/*
	这里的func()相当于
	cout << "func()" <<endl;
	*/
	
	
	
	int c = sum(10, 20);
	/*
	这里相当于
	int c = 10 + 20;
	*/
	
	
	
	cout << c << endl;

	return 0;
}

inline int sum(int v1, int v2)
{
	return v1 + v2;
}

意义:

函数调用需要开辟栈空间,回收栈空间,内联函数则不存在栈空间的开辟,不需要回收内存

什么时候使用内联函数

​ 适用于函数体积小

​ 经常调用的函数

特点

​ 编译器会将函数调用直接展开为函数体代码

​ 可以减少函数调用的开销

​ 会增大代码体积

注意

​ 尽量不要内联超过10行代码的函数

​ 有些函数即使声明为inline,也不一定会被编译器内联,比如递归函数

内联函数-----宏

#define add(v1,v2) v1+v2

int main()
{

	cout << add(10, 20) << endl;

	return 0;
}

对比:

​ 内联函数和宏,都可以减少函数调用的开销

​ 对比宏,内联函数多了语法检测和函数特性

#define add(v) v+v

inlinde int add1(int v)
{
	return v+v;
}

int main()
{
	int a=10;
	cout<<add(++a)<<endl;
	//结果为24,相当于cout << ++a + ++a <<endl; 
	
	cout<< add1(++a)<<endl;
	//结果为22 
	return 0;

}


表达式

C++有些表达式可以被赋值

int a=1;
int b=2;
(a=b)=4;
//这里a=4,b=2

const

​ const 是常量的意思,被其修饰的变量不可修改

​ 如果修饰的是类、结构体(的指针),其成员也不可修改

#include <iostream>
using namespace std;

struct Data
{
	int year;
	int month;
	int day;
};


int main()
{
	const int age = 10;
	const Data d = { 1200, 23, 2 };
	d.year = 15;
	cout << d.year << endl;//输出错误
	return 0;
}

以下5个指针是什么含义?

int age=10;

const int *p1=&age;				//*p1是常量,p1可以改变			
int const *p2=&age;				//与p1没区别
int * const p3=&age;			//p3是常量,不能改变,*p3可以改变
const int *  const p4=&age;		//与p5没区别 p4,*p4都是常量
int const *const p5=&age;

const修饰的是其右边的内容

struct Student {int age;};

Student stu1={10};
Student stu2={20};

const Student *pStu1=&stu1;  //修饰 *pStu1
*pStu1 = stu2;				//报错 直接访问
(*pStu1).age=30;			//报错 间接访问
pStu1->age=30;				//报错 间接访问
pStu1 = &stu2;				//正确

Student * const pStu2 = &stu2; //修饰pStu2
*pStu2=stu1;				//正确
(*pStu2).age=30;			//正确
pStu2->age=30;				//正确
pStu2=&stu1;			    //报错

引用(Reference)

在c语言中,使用指针间接修改某个变量的值。

	int age = 10;

	int height = 20;
	
	//定义了一个age的引用
	int& ref = age;
	int& ref1=ref;
	int& ref2=ref1;
	ref = 20;

	age += 30;
	age = height;
	age = 11;

	cout << height << endl;

注意

引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以由引用)

对引用做计算,就是对引用所指向的变量做计算

在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”

可以利用引用初始化另一个引用,相当于某个变量的多个别名

不存在【引用的引用、指向引用的指针、引用数组】

引用的价值

比指针更安全、函数返回值可以被赋值

void swap(int  &v1, int  &v2)
{
	int tmp = v1;
	v1 = v2;
	v2 = tmp;
}


int main()
{

	int a = 10;
	int b = 20;
	swap(a, b);
	cout << "a=" << a << ",b=" << b << endl;

	return 0;
}

参数的引用是栈里的

引用的本质:

​ 引用的本质就是指针,只是编译器削弱了它的功能,所及引用就是弱化的指针

int age=10;


//*p就是age的别名
int *p=&age;
*p=30;

//ref就是age的别名
int &ref=age;
ref=40;

指针的大小和环境有关(X86,X64)

ASLR 让应用的起始地址是随机的

汇编

汇编语言的种类

​ 8086汇编(16bit)

​ X86汇编(32bit)

X64汇编(64bit)

​ ARM汇编(嵌入式、移动设备)

X64汇编根据编译器的不同,有两种书写格式

Intel

​ AT&T

汇编语言不区分大小写

学习汇编语言2大知识点:

1.汇编指令

2.寄存器

寄存器

​ 通常,CPU会先将内存中的数据存储到寄存器中,然后再对寄存器中的数据进行运算。

64bit:

通用寄存器:RAX \ RBX \ RCX \ RDX;

寄存器大小和环境有关,64位为8个字节

32bit:

通用寄存器:EAX \ EBX \ECX \EDX;

16bit:

通用寄存器:AX \ BX \ CX \DX

一般规律:

R开头的寄存器是64bit的,占8个字节

**imm:**立即数

内联汇编

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	__asm
	{
		mov eax,a
	}

	return 0;
}

mov dest,src

将src的内容赋值给dest,类似于dest=src

[地址值]

​ 1.中括号[]里面放的都是内存地址

2.word 是2字节,dword是4字节(double word),qword是8字节      (quard 	 word)

call 函数地址

add op1,op2

​ 类似于op1=op1+op2

sub op1,op2

类似于op1=op1-op2

inc op

​ 自增,类似于op=op+1

dec op

​ 自减,类似于op=op-1

jmp 内存地址

​ 跳转到某个内存地址去执行代码

​ j开头的一般都是跳转,大多数是带条件的跳转,一般跟test、cmp等指令配合使用

指针的汇编特征

lea		eax,[ebp-0Ch]
mov 	dword ptr [ebp-18h],eax

引用的汇编特征

与指针相同

引用的补充

//结构体的引用
#include <iostream>
using namespace std;

struct Data
{
	int year;
	int month;
	int day;
};

int main()
{
	Data d = { 2021,07,20 };

	Data& ref = d;
	d.day = 2013;

	cout << d.day << endl;

	return 0;
}
//指针的引用

	int age = 10;
	int* p = &age;
	int* &ref = p;//引用的 数据类型是int*

	*ref = 30;

	int height=30;
	ref=&height;//更改ref的地址

数组名array其实是数组的地址,也是数组首元素的地址

数组名array可以看做是指向数组首元素的指针(int *)

//数组的引用

int array[]={1,2,3};
//法一:
int (&ref)[3]=array;
ref[0]=10;
//法二:
int * const &ref = array;

指针数组

数组里面可以存放3个int*	

​ *int p;

​ *int arr1[3]={p,p,p}

用于指向数组的指针

​ 指向存放3个整数的数组

int (*arr2)[3];

常引用(const Reference)

引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用

const必须写在&符号的左边,才算是常引用


int age=10;
const int &ref=age;
ref=30; //这时就不能更改其值,

意义:

​ 只读,阻止其他函数修改所引用的值

	int func(const int &a);
const int &ref=age;//与下面相同
int const &ref=age;//ref不可以修改指向,且不可以通过ref间接修改所指向的变量


int &const ref=age;//ref不能修改指向,但是可以通过ref间接修改所指向的变量  **没有意义**
相当于int * const ref=age;

const引用的特点

​ 可以指向临时数据(常量、表达式、函数返回值等)

​ 可以指向不同类型的数据

int age = 10;

int a=1;
int b=2;

const int &ref1 = a + b;
const int &ref = 30;
const double *ref2 = age;

​ 作为函数参数时(此规则也适用于const指针

​ 可以接受const和非const实参(非const引用,只能接受非const实参)

​ 可以跟非const引用构成重载

int sum(const int &v1,const int &v2)
{
	return v1 + v2;
}

int sum(int &v1,int &v2)
{
    return v1 + v2;
}

int main()
{
	int a = 10;
    int b = 20;
    sum(a,b);
    
    const int c = 10;
    const int d = 20;
    sum(c,d);
    sum(10,20);//重要作用
    
}

当常引用指向了不同类型的数据时,就会产生临时变量,即引用指向的并不是初始化时的那个变量

int age = 10;
const int &rAge = age;
age = 30;

cout << " age is " << age <<endl;     30
cout << " rAge is " << rAge <<endl;   30
int age = 10;
const double &rAge = age;
age = 30;

cout << " age is " << age <<endl;     30
cout << " rAge is " << rAge <<endl;   10

面向对象

类和对象

​ C++可以使用struct、class来定义一个类

使用struct定义类

#include <iostream>
using namespace std;

struct  Person
{

	int age; //成员变量
	
	void fun()//成员函数(方法)
	{
		cout << "Person :: run()"<<age << endl;
	}
};

int main()
{
	//利用类创建对象
	Person person;	//这个对象的内存在栈空间

	person.age = 10;
	person.fun();


	return 0;
}

struct 和 class的区别

​ struct的默认成员权限是public

​ class的默认诚邀您权限是private

命名规范

1.全局变量:g_
2.成员变量:m_
3.静态变量:s_
4.常量:c_

通过指针间接访问person对象

#include <iostream>
using namespace std;

struct  Person
{

	int age; //成员变量
	
	void fun()//成员函数(方法)
	{
		cout << "Person :: run()"<<age << endl;
	}
};


int main()
{

	Person* p = &person;
	p->age = 20;
	p->fun();

	return 0;
}

上面代码中的person 对象、p指针的内存都是在函数的栈空间,自动分配和回收的

64位环境下,person占用4个字节(Person类中只有一个int变量),p占用8个字节(64位环境下,地址占用8个字节)

实际开发中,使用class比较多

this this就是指针

​ this指针存储着函数调用者的地址

​ this指向了函数的调用者

#include <iostream>
using namespace std;

struct Person
{
	int m_age;

	void run()
	{
		cout << "Person::run()" << this->m_age << endl;//this是隐式参数
	}
};

int main()
{
	Person person;
	person.m_age = 21;
	person.run();

	return 0;
}

作用:可以使不同的对象,调用同一个函数。

指针访问的本质

lea		eax,[ebp-14h]
mov		dword ptr [ebp-20h],eax

mov		eax,dword ptr[ebp-20h]
mov 	dword ptr [eax],0Ah

mov		eax,dword ptr [ebp-20h]
mov		dword ptr [eax],0Ah

原理:如何利用指针间接访问所指向对象的成员变量?

​ 1.从指针中取出对象的地址

​ 2.利用对象的地址 + 成员变量的偏移量计算出成员变量的地址

​ 3.根据成员变量的地址访问成员变量的存储空间

思考:最后打印出来的 每个成员变量值是多少?将person.display()换成p->display()成员值会变成多少?

struct Person
{
    int m_id;
    int m_age;
    int m_height;
    
    void display()
    {
        //eax == &person.m_age == &person+4
        //mov eax,dword ptr [this]
        //[eax],[eax + 4],[eax + 8]
        //[&person + 4],[&person +4 + 4],[&person + 4 + 8]
        cout<<" id= "<< m_id
            <<" ,age ="<<m_age
            <<",height = "<<m_height<<endl;
    }
}


Person person;
person.m_id=10;
person.m_age=20;
person.m_height=30;

Person *p=(Person *) &person.m_age;
//eax == &person.m_age == &person + 4
//mov eax,dword ptr[p]
//mov dword ptr [eax + 0],40
//mov dword ptr [&person + 4 + 0],40
p->m_id=40;
//mov dword ptr [eax + 4],50
//mov dword ptr [&person + 4 + 4],50
p->m_age=50;

//将person对象的地址传递给display函数的this
person.display();

//会将指针p里面存储的地址传递给display函数的this
//将&person.m_age传递给display函数的this
p->display();

答案:10,40,50;

40,50,xxx

0xcc

函数所在栈空间在使用之前存在垃圾数据,需要 用cc来填充

cc->对应 int3(中断):起到断点的作用

当其它语句不小心跳到函数栈空间时,就会在cc的作用下停止执行。

内存

封装

成员变量私有化,提供公共的getter和setter给外界区访问成员变量

#include <iostream>
using namespace std;

struct Person
{
private:
	int m_age;
public:
	void setAge(int age)
	{
		if (age <= 0)	return;
		m_age = age;
	}
	int getAge()
	{
		return m_age;
	}

};

int main()
{
	Person person;
	person.setAge(4);
	
	cout << person.getAge() << endl;

	return 0;
}

内存空间的布局

​ 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大部分

代码段(代码区)

​ 用于存放代码,只读

数据段(全局区)

​ 用于存放全局变量等

栈空间

​ 每调用一个函数就会给它分配一段连续的栈空间,等函数调用 完毕后会自动回收这段栈空间

​ 自动分配和回收

堆空间

​ 需要主动申请和释放

​ 在程序运行过程中,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存。

堆空间的申请和释放

malloc/free

#include <iostream>
using namespace std;

void test()
{
	int* p = (int*)malloc(4);
	*p = 10;
	free(p);
}

void test2()
{
    char *p=(char *)malloc(4);
    p[0]=1;
    p[1]=2;
    p[2]=3;
    p[3]=4;
    /*
    *p=10;
    *(p+1)=11;
    *(p+2)=12;
    *(p+3)=13;
    */
    free(p);//回收所开辟所有空间
}

int main()
{
	test();

	return 0;
}

在X86环境中

int *p =(int *)malloc(4);
*p=10;

指针p在栈空间中,其占四个字节,存储的是堆空间所开辟的4个字节空间的首地址。

函数调用完毕后栈空间会自动消失,但是堆空间还在,需要free去释放

new/delete

int* p = new int;
*p = 10;
delete p;

new[]/delete[]

char* p = new char[4];
delete[] p;

注意

​ 申请堆空间成功后,会返回那一段内存空间的地址

​ 申请和释放必须是1对1的关系,不然可能会存在内存泄漏

现在很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在

​ 利:提高开发效率,避免内存使用不当或泄漏

​ 弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手

**堆空间的初始化 **

memory set

int size = sizeof(int) * 10;
int* p = (int*)malloc(size);
memset(p, 0, 40);
//从p地址开始,将40个字节中每一个字节设置为0;
int *p0 = new int;		//没有初始化
int *p1 = new int();	//初始化为0
int *p2 = new int(5);	//初始化为5
int *p3 = new int[3];	//数组元素未被初始化
int *p4 = new int[3]();	//3个数组元素都被初始化为0
int *p5 = new int[3]{};	//3个数组元素都被初始化为0
int *p6 = new int[3]{5};//数组的首元素被初始化为5,其他为0

memset函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法

Person person;
person.m_id=1;
person.m_age=20;
person.m_height=180;
memset(&person,0,sizeof(person));

Person persons[]={{1,20,180},{2,25,165},{3,27,170}};
memset(persons,0,sizeofz(persons));

对象的内存

​ 对象的内存可以存在于3种地方

全局区(数据段):全局变量

栈空间:函数里面的局部变量

堆空间:动态申请内存(malloc、new等)

//全局区
Person g_person;

int main()
{
	//栈空间
	Person person;
	
	//堆空间
	Person *p=new Person;
	return 0;
}

构造函数(Constructor)

构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作。

#include <iostream>
using namespace std;

struct Person 
{
	int m_age;

	Person()
	{
		m_age = 0;
		cout << "Person()" << endl;
	}
	Person(int age)
	{
		m_age = age;
		cout << "Person(int age)" <<age<< endl;
	}

};

int main()
{
	Person person;
	
	Person person2(20);

	Person person3(30);
    
    Person *p=(Person *)malloc(sizeof(Person));
    p->m_age=10;
    
    Person *p1 = new Person; 

	return 0;
}

特点

​ 1.函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数

​ 2.一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象

注意

​ 栈空间和堆空间的创建对象,都会调用构造函数

通过malloc分配的对象,不会调用构造函数(malloc只会申请堆空间)

默认情况下,编译器会为每一个类生成空的无参的构造函数 错误的

在某些特定的情况下,编译器才会为类生成空的无参的构造函数

构造函数的调用

#include <iostream>
using namespace std;

struct Person 
{
	int m_age;
	int m_money;

	Person()
	{
		m_age = 0;
		cout << "Person()" << endl;
	}
	Person(int age)
	{
		m_age = age;
		cout << "Person(int age)" <<age<< endl;
	}

};

Person g_person0;									//Person()调用了无参
Person g_perosn1();									//函数声明
Person g_person2(20);								//有参

int main()
{
	Person person;									//无参
	Person person1();								//函数声明
	Person person2(20);								//有参

	Person* p0 = new Person;						//无参
	Person* p1 = new Person();						//无参
	Person* p2 = new Person(20);					//有参
	/*
	4个无参,3个有参
	*/
	return 0;
}

成员变量的初始化

#include <iostream>
using namespace std;

struct Person
{
	int m_age;

};
//全局区:成员变量初始化为0
Person g_person;

int main()
{
	Person person;//栈空间:没有初始化成员变量

	//堆空间:没有初始化成员变量
	Person* p0 = new Person;
	//堆空间:成员变量初始化为0(有构造函数时不会初始化,编译器认为构造函数会初始化变量)
	Person* p1 = new Person();
    
    
    Person *p4 = new Person[3];			//成员变量不会被初始化
    Person *p5 = new Person[3]();		//3个Person对象的成员变量都初始化为0(没有自定义构造函数)
    Person *p6 = new Person[3]{};		//3个Person对象的成员变量都初始化为0(没有自定义构造函数)
	
	cout << g_person.m_age << endl;
	//cout << person.m_age << endl;
	cout << p0->m_age << endl;
	cout << p1->m_age << endl;

	return 0;
}

如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化

初始化方法

struct Person
{
	int m_age;
	Person()
	{
		memset(this,0,sizeof(Person));
	}
}

析构函数(Destructor)

析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作。

#include <iostream>
using namespace std;

struct Person
{
	int m_age;

	//新的Person对象诞生的过程
	Person()
	{
		cout << "Person::Person()" << endl;
	}

	//一个Person销毁的象征
	~Person()
	{
		cout << "~Person()" << endl;
	}
};

int main()
{
	{//作用域
		Person person;
	}
	return 0;
}

特点

函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个

注意

通过malloc分配的对象free的时候不会调用析构函数

Person *p = (Person*)malloc(sizeof(Person));
free(p);//此时不会调用析构函数

构造函数、析构函数要声明为public,才能被外界正常使用

内存管理:

#include <iostream>
using namespace std;

struct Car
{
	int m_price;
	Car()
	{
		m_price = 0;
		cout << "Car::Car()" << endl;
	}
	~Car()
	{
		cout << "Car::~Car()" << endl;
	}
};

struct Person
{
	int m_age;
	Car* m_car;
	//用来做初始化工作
	Person()
	{
		m_age = 0;
		m_car = new Car();//在堆空间,不会自动回收
		cout << "Person::person()" << endl;
	}

	//用来做内存清理工作
	~Person()
	{
		delete m_car;//这个位置适合
		cout << "Person::~person()" << endl;
	}
};


int main()
{
	//内存泄漏:该释放的内存没有释放
	{
		Person person;

		//delete person.m_car;//释放内存,但这样写太麻烦
	}
	return 0;
}

//当栈空间person被回收之后,堆空间中的car不会被回收,所以在person中的析构函数中回收car

对象内部申请的堆空间,由对象内部回收

声明和实现分离

具体参见实验

#include <iostream>
using namespace std;

class Person
{
private:
	int m_age;
public:
	void setAge(int age);
	int getAge();
	Person();
	~Person();
};

void Person::setAge(int age)
{
	m_age = age;
}

int Person::getAge()
{
	return m_age;
}

Person::Person()
{
	m_age = 0;
}

Person::~Person()
{

}

int main()
{
	getchar();
	return 0;
}

命名空间

​ 可以避免命名冲突

#include <iostream>
using namespace  std;

namespace MJ {
    int g_age;//它是个全局变量
	class Person
	{
		int m_money;
		int m_age;
	};
}


class Person
{
	int m_height;
};

int main()
{
    //法一
    MJ::g_age=10;
	MJ::Person person;
	cout << sizeof(person) << endl;
	
    
    //法二
    using namespace MJ;
    g_age=10;
    Person person;
    
    using MJ::g_age;//只使用某一个
    
	getchar();
	return 0;
}

命名空间不影响布局

思考:

namespace MJ
{
	int g_age;
}

namespace FX
{
	int g_age;
}

using namespace MJ;
using namespace FX;

//这句代码能通过编译么?
g_age=20;
//不能,具有二义性

命名空间的嵌套

namespace MJ
{
	namespace SS
	{
		int g_age;
	}
}

int main()
{
	MJ::SS::g_age = 10;
	
	using namespace MJ::SS;
	g_age = 20;
	
	using namespace MJ::SS::g_age;
	g_age = 30;
	
	return 0;
}

有个默认的全局命名空间,我们创建的空间默认嵌套在它里面

void func()
{
	cout<<"func()"<<endl;
}

namespace MJ
{
	void func()
	{
		cout<<"MJ::func()"<<endl;
	}
};

int main()
{
	using namespace MJ;
	
	MJ::func();
	
	::func();//使用全局命名空间
	return 0;
}

命名空间的合并

namespace MJ
{
	int g_age;
}

namespace MJ
{
	int g_no;
}

//等价于

namespce MJ
{
	int g_age;
	int g_no;
}

实验 声明实现和分离

继承

可以让子类拥有父类所有成员函数。

#include <iostream>
using namespace std;

struct Person
{
	int m_age;
	void run(){}
};

struct Student:Person 
{
	int m_age;
	int m_score;

	void run() {}

	void study() {}
};

struct Worker:Person
{
	int m_age;
	int m_salary;
	void run(){}
};
int main()
{
	return 0;
}

C++中没有基类

继承之后对象的内存布局

struct Person
{
	int m_age;
};

struct Student:Person
{
	int m_no;
};

struct GoodStudent:Student
{
	int m_money;
}

int main()
{
    Person person;//4个字节
    Strdent stu;  //8个字节
    GoodStudent gs;//12个字节 
    
}

在内存中父类的成员变量会排布在前面

成员访问权限:

成员访问权限、继承方式有3种:

​ public:公共的,任何地方都可以访问(struct默认)

​ protected:子类内部、当前类内部可以访问

​ private:私有的,只有当前类内部可以访问(class默认)

#include <iostream>
using namespace std;

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

struct Student :protected Person
{
	void study()
	{
		m_age = 10;
	}
};

struct GoodStudent :public Person
{
	
};

int main()
{
	Person person;
	person.m_age = 10;
}

子类内部访问父类成员的权限,是以下2项中权限最小的那个

​ 成员本身的访问权限

​ 上一级父类的继承方式

开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限

访问权限不影响对象的内存布局

初始化列表

#include <iostream>
using namespace std;

struct Person
{
	int m_age;
	int m_height;
	/*Person(int age,int height)
	{
		m_age=age;
		m_height=height;
	}*/ 
	//等价于
	Person(int age,int height):m_age(age),m_height(height)//初始化列表
	{
	
	}
};

int main()
{
	Person person(18,180);
	
	getchar();
	return 0;
}

特点

​ 一种便捷的初始化成员变量的方式

​ 只能用在构造函数中

​ 初始化顺序只跟成员变量的声明顺序有关

初始化列表和默认参数的配合使用

struct Person
{
	int m_age;
	int m_height;
	Person(int age=0,int height=0):m_age(age),m_height(height){}
};

int main()
{
	Person person1;
	Person person2(17);
	Person person(18,180);
}

如果函数声明和实现是分离的,初始化列表只能写在函数的实现中

struct Person
{
	int m_age;
	int m_height;
	Person(int age=0,int height=0)
};

Person(int age=0,int height=0):m_age(age),m_height(height){}

int main()
{
	Person person1;
	Person person2(17);
	Person person(18,180);
}

构造函数的互相调用:

构造函数调用构造函数必须写在初始化列表当中,普通函数则不需要

struct Person
{
	int m_age;
	int m_height;
	
	Person():Person(10,20){}
	Person(int age,int height);
	{
		m_age = age;
		m_height = height;
	}
}

**注意:**下面的写法是错误的,初始化的是一个临时对象

struct Person
{
	int m_age;
	int m_height;
	Person()
	{
		Person(0,0);
	}
	Person(int age,int height):m_age(age),m_height(height){}
}

父类的构造函数:

如果父类没有构造函数,子类不调用,如果有,子类则必须调用

#include <iostream>
using namespace std;

struct Person
{
	int m_age;
	Person()
	{
		cout<<"Person::person()"<<end;
	}
    Person(int age)
    {
        cout<<"Person::person(int age)"<<endl;
    }
};

struct Student:Person
{
	int m_no;
	
	Student()
	{
		cout<<"Student::Student()"<<endl;
	}
};

int main()
{
	Student student;
	
	return 0;
}

子类的构造函数会默认调用父类无参的构造函数

如果子类的构造函数显示的调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数

如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数

class Person
{
	int m_age;
public:
	Person(int age):m_age(age){}
};

class Student:Person
{
	int m_no;
public:
	Student(int age,int no):m_no(no),Person(age){}
}

int main()
{
	Student student(18,34);
}

析构函数的调用

class Person
{
	int m_age;
public:
	Person()
	{
		cout<<"Person::Person()"<<endl;
	}
	~Person()
	{
		cout<<"Person::~Person()"<<endl;
	}
};

class Student:Person
{
	int m_no;
public:
	Student()
	{
		//call Person::Person调用父类构造函数
		cout<<"Student::Student()"<<endl;
	}
	~Student()
	{
		cout<<"Student::~student()"<<endl;
		//call Person::~Person() 调用父类析构函数
	}
}

int main()
{
	{
		Student student;
	}
    return 0;
}

多态

父类指针、子类指针

​ 父类指针可以指向子类对象、是安全的,开发中经常用到(继承方式必须是public)

​ 子类指针指向父类对象是不安全的

#include <iostream>
using namespace std;

struct Person
{
	int m_age;
};

struct Student :Person
{
	int m_score;
};

int main()
{
	//父类子针指向子类对象,父类指针只能访问父类定义的变量,所以它是安全的
	Person* p = new Student();

    //而子类指针指向父类对象是不安全的,子类中的变量在父类中是不包含的
	return 0;
}

默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态

多态是面向对象非常重要的一个特性

​ 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果

​ 在运行时,可以识别出真正的对象类型,调用对应子类中的函数

多态的要素

​ 子类重写父类的成员函数(override),成员函数必须是虚函数

​ 父类指针指向子类对象

​ 利用父类指针调用重写的成员函数

struct Animal
{
	void speak()
	{
		cout<<"Animal::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"Animal::run()"<<endl;
	}
}

struct dog:Animal
{
	//重写(覆写、覆盖、orrivade):要求返回值,函数名,参数要与父类相同
	void speak()
	{
		cout<<"dog::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"Animal::run()"<<endl;
	}
}

struct cat:Animal
{
	void speak()
	{
		cout<<"cat::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"cat::run()"<<endl;
	}
}

void liu(Animal *p)
{
    p->speak();
    p->run();
}

int main()
{
    liu(new dog());//无法实现多态
    
    cat *p=(cat *) new Dog();
    p->speak();//call cat::speak
    p->run();//call cat::run
    
    return 0;
}


虚函数

C++中的多态通过虚函数(virtual function)来实现

虚函数:被virtual修饰的成员函数

只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类中可以省略virtual)

struct Animal
{
	virtual void speak()
	{
		cout<<"Animal::speak()"<<endl;
	}
	
	virtual void run()
	{
		cout<<"Animal::run()"<<endl;
	}
}

struct dog:Animal
{
	//重写(覆写、覆盖、orrivade):要求返回值,函数名,参数要与父类相同
	void speak()
	{
		cout<<"dog::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"Animal::run()"<<endl;
	}
}

struct cat:Animal
{
	void speak()
	{
		cout<<"cat::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"cat::run()"<<endl;
	}
}

void liu(Animal *p)
{
    p->speak();
    p->run();
}

int main()
{
    Animal *p=new cat();
    p->speak();
    p->run();
    
    liu(new cat());
    liu(new dog());
    
    return 0;
}

虚表

​ 虚函数的实现原理是虚表,这个虚表里面存储着最终要调用的虚函数地址,这个虚表也叫作虚函数表

class Animal
{
public:
	int m_age;
    virtual void speak()
    {
        cout<<"Animal::speak()"<<endl;
    }
    virtual void run()
    {
        cout<<"Animal::run()"<<endl;
    }
};

class Cat:public Animal()
{
public:
    int m_life;
    void speak()
    {
        cout<<"Cat::speak()"<<endl;
    }
    void run()
    {
        cout<<"Cat::run()"<<endl;
    }
};

int main()
{
    Animal *cat = new Cat();
    cat->m_age=20;
    cat->speak();
    cat->run();
    
    return 0;
}

所有的Cat对象(不管在全局区、栈、堆)共用一份虚表

如果类中有虚函数,则定义该类对象时以右边new的对象类型为标准,如没有,则以左边对象类型为标准

多态使用的场合

一个父类下面包含了好几个子类,每个子类都重写了 父类中的方法

调用父类的成员函数

stuct Animal
{
    virtual void speak()
    {
        cout<<"Animal::Speak()"<<endl;
    }
    virtual void run()
    {
        cout<<"Animal::run()"<<endl;
    }
}

struct Cat::Animal
{
    void speak()
    {
        Animal::speak();//调用父类的成员函数
        
    }
}

虚析构函数

​ 如果存在父类指针指向子类对象的时候,应该将析构函数声明为虚函数(虚析构函数)

​ delete父类 指针时,才会调用子类的析构函数,保证 析构的完整性

stuct Animal
{
    virtual void speak()
    {
        cout<<"Animal::Speak()"<<endl;
    }
    virtual void run()
    {
        cout<<"Animal::run()"<<endl;
    }
    virtual ~Animal()
    {
        cout<<"Animal::~Animal"<<endl;
    }
}

struct Cat::Animal
{
    virtual void speak()
    {
        cout<<"Cat::Speak()"<<endl;
    }
    virtual void run()
    {
        cout<<"Cat::run()"<<endl;
    }
    ~Cat()
    {
        cout<<"Cat::~Cat()"<<endl;
    }
}

int main()
{
    Animal *cat0=new Cat();
    cat0->speak;
    delete cat0;
}

纯虚函数

​ 纯虚函数:没有函数体且初始化为0 的虚函数,用来定义接口规范(类似于Java中的抽象类接口)

struct Animal
{//动物的这些方法有实现是不合理的,所以将其设为纯虚函数
	virtual void speak()=0;	
	virtual void run()=0;
}

struct dog:Animal
{
	void speak()
	{
		cout<<"dog::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"Animal::run()"<<endl;
	}
}

struct cat:Animal
{
	void speak()
	{
		cout<<"cat::speak()"<<endl;
	}
	
	void run()
	{
		cout<<"cat::run()"<<endl;
	}
}

void liu(Animal *p)
{
    p->speak();
    p->run();
}

int main()
{
    Animal *p=new cat();
    p->speak();
    p->run();
    
    liu(new cat());
    liu(new dog());
    
    return 0;
}

抽象类(Abstract class)

​ 含有纯虚函数的类,不可以实例化(不可以创建对象)

​ 抽象类可以包含非纯虚函数、成员变量

​ 如果父类是抽象类,子类没有完全重写纯虚函数,那么这个子类依然是抽象类

多继承

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

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

class Worker
{
public:
    int m_salary;
    void work()
    {
        cout<<"Worker::work()"<<endl;
    }
};

class Undergraduate:public Student,public Worker
{
public:
	int m_grade;
    void play()
    {
        cout<<"Undergraduate::play()"<<endl;
    }
};


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

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

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

struct Undergraduate:Student,Worker
{
    int m_grade;
  //  Undergraduate(int score,int salary,int grade)
  //      :m_grade(grade),m_salary(salary),m_score(score){}
    Undergraduate(int score,int salary,int grade)//多继承体系下的构造函数调用
        :m_grade(grade),
    	Student(score),
    	Worker(salary){}
    
    void play()
    {
        cout<<"Undergraduate::play()"<<m_grade<<m_salary<<m_score<<endl;
    }
}

虚函数

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

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

struct Worker
{
    virtual void work()
    {
        cout<<"Worker::work()"<<endl;
    }
};

struct Undergraduate:Student,Worker
{
	void study()
	{
		cout<<"Undergraduate::study()"<<endl;
	}
    void work()
    {
        cout<<"Undergraduate::work()"<<endl;
    }
    void play()
    {
        cout<<"Undergraduate::play()"<<endl;
    }
}

同名函数

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::eat()"<<endl;
	}
};

int main()
{
    Undergraduate ug;
    ug.eat();
    ug.Student::eat();//调用父类中的eat()
    
}

同名成员变量

class Student
{
public:
	int m_age;
};

class Worker
{
public:
	int m_age;
};

class Undergraduate:public Student,public Worker
{
public:
	int m_age;
};

int main()
{
	Undergraduate ug;
	ug.m_age=10;
	ug.Student::m_age=12;
	
	return 0;
}

菱形继承

#include <iostream>
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;//结果为20
    return 0;
}

菱形继承带来的问题:

​ 最底下子类从基类继承的成员变量冗余、重复

​ 最底下子类无法访问基类的成员,有二义性

虚继承

​ 虚继承可以解决菱形继承带来的问题

class Person							//Person 被称为虚基类
{
    int m_age=1;
};

class Student:virtual public Person
{
    int m_score=2;
};

class Worker:virtual public Person
{
    int m_salary =3;
};

class Undergraduate:public Studetn,public Worker
{
    int m_grade=4;
};

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

多继承的应用

#include <iostream>
using namespace std;

class JobBaomu
{
public:
    virtual void clean()=0;
	virtual void cook()=0;
};

class JobTeacher
{
public:
    virtual void playFootball()=0;
    virtual void playBaseball()=0;
};

class Student:public JobBaomu,public JobTeacher
{
    int m_score;
public:
    void clean()
    {
        
    }
    void cook()
    {
        
    }
};

class Worker
{
    int m_salary;
};

int main()
{
    /*
    兼职中心,招聘兼职,岗位如下:
    	1.保姆:扫地、做饭
    	2.老师:踢足球、打棒球
    	
    应聘的角色
    	1.学生
    	2.上班族
    	*/
    
}

静态成员(static)

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

可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态变量名)

静态成员变量

​ 存储在数据段(全局区、类似于全局变量),整个程序运行过程中只有一份内存,不需要创建对象就存在

​ 对比全局变量,它可以设定访问权限(public、protected、private),达到局部共享的目的

​ 必须初始化,必须在类外面初始化,初始化时 不能带static,如果类的声明和实现分离(在实现.cpp中初始化)

静态成员函数

​ 内部不能使用this指针(this指针只能用在非静态成员函数内部)

​ 不能是虚函数(虚函数只能是非静态成员函数)PS:虚函数是用于多态,通过父类指针调用虚函数

​ 内部不能访问非静态成员变量、函数,只能访问静态成员变量、函数。

​ 非静态成员函数内部可以访问静态成员变量、函数。

​ 静态成员函数之间可以相互访问。

​ 构造函数和析构函数不能是静态的。

​ 当声明和实现分离时,实现部分不能带static。

#include <iostream>
using namespace std;

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

int Car::m_price=0;//初始化

int main()
{
    Car car1;
    car1.m_price=100;
    
    Car car2;
    car2.m_price=200;
    
    Car::m_price=400;
    
    Car*p = new Car();
    p->m_price=500;
    
    //静态成员函数访问方式
    Car.run();
    Car->run();
    Car::run();
    
    return 0;
}

应用

#include <iostream>
using namespace std;

class Car
{
private:
    static int ms_count;	//统计创造Car对象的多少
public:
	
    Car()
    {
        //严格来说,这里需要考虑多线程安全问题
        ms_count++;
    }
    
    static int getCount()		//可以不创建对象,通过类名直接访问,获得ms_count的值
    {
        return ms_count;
    }
    ~Car()
    {
        ms_count--;
    }
};

int Car::ms_count=0;

int main()
{
    Car car;
    
    Car *p = new Car();
    
    return 0;
}

静态成员经典应用----单例模式

单例模式:设计模式的一种,保证某个类永远只创建一个对象

​ 第一步:构造函数、析构函数私有化

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

​ 第三步:提供一个公共的访问单例对象的接口

#include <iostream>
using namespace std;

Class Rocket
{
private:
	Rocket(){};//创建对象必须调用构造函数,一旦私有化就不能调用构造函数
    ~Rocket(){};
	static Rocket *ms_rocket;//设置为静态是为了保证只有一个Rocket类
public:
	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依然有值,不然有可能产生野指针。
            ms_rocket=NULL;
        }
    }
	
	void run()
	{
		cout<<"run()"<<endl;
	}
	
};



int main()
{
	Rocket *p1=Rocket::shareRocket();//通过调用静态函数来创建类
	Rocket *p2=Rocket::shareRocket();
	Rocket *p3=Rocket::shareRocket();
	Rocket *p4=Rocket::shareRocket();
	
    Rocket::deleteRocket();
    
	cout<<p1<<endl;
	cout<<p2<<endl;
	cout<<p3<<endl;
	cout<<p4<<endl;
	
	return 0;
}

new、delete误区

int *p = new int;
*p = 10;
delete p;  //回收堆空间4个字节,里面的内容还会在,不会被清除

回收堆空间内存:这块堆空间内存可以重新被别人使用

const成员

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

const成员变量:

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

​ 非static的const成员变量还可以在初始化列表中初始化

const成员函数(非静态):

​ const关键字写在参数列表后面,函数的声明和实现必须带const

​ 内部不能修改非静态成员变量

​ 内部只能调用const成员函数、static成员函数

​ 非const成员函数可以调用const成员函数

​ const成员函数和const成员函数构成重载

​ 非const对象(指针)优先调用非const成员函数

​ const对象(指针)只能调用const成员函数、static成员函数

#include <iostream>
using namespce std;

class Car
{
public:
    const int mc_price=0;//直接赋值
    Car():m_price(0){} //初始化列表中初始化
    void run() const
    {
        cout<<"run()"<<endl;
    }
};

int main()
{
    return 0;
}

引用类型成员

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

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

​ 通过初始化列表初始化

class Car
{
	int age;
	int &m_price = age;
public:
	Car(int &price):m_price(price){}
};

拷贝构造函数(Copy Constructor)

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

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

​ 拷贝构造函数的格式是固定的,接收一个const引用作为参数

#include <iostream>
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 car1;
    Car car2(100);
    Car car3(100,5);
    
    //利用已经存在的car3对象创建了一个car4新对象
    //car4初始化时会调用拷贝构造函数
    Car car4(car3);
    /*
    	具体拷贝过程
    	car4.m_price=car3.m_price;
    	car4.m_length=car4.m_length;
    */
    getchar();
    return 0;
}

​ 如果没有拷贝构造函数,在默认情况下,也会将原先对象中所有的值拷贝到新的对象中

调用父类的拷贝构造函数

class Person
{
    int m_age;
public:
    Person(int age):m_age(age){}
    Person(const Person &person):m_age(person.m_age){}
};

class Student:public Person
{
    int m_score;
public:
    Student(int age,int score):Person(age),m_score(score){}//调用父类的构造函数
    Student(const Student &student):Person(student),m_score(student,m_score){}//调用父类的拷贝构造函数
}
int main()
{
	Car car1(100,5);//调用 构造函数
    Car car2(car1);//调用 拷贝构造函数
    Car car3=car2;//等价于第二种写法 调用拷贝构造函数
    Car car4;//调用 构造函数
  
    car4=car3;//这里并不会调用拷贝构造函数,仅仅是简单的赋值操作
}

如果子类没有调用父类的拷贝构造函数会默认调用父类的构造函数,如果子类显式的调用了父类的拷贝构造函数,则不会调用父类的构造函数

深拷贝、浅拷贝

浅拷贝(shallow copy)

​ 指针类型的变量只会拷贝地址值,地址拷贝

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

​ 将一个对象中所有成员变量的值拷贝到另一个对象

​ 如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间

​ 可能会导致堆空间多次free的问题

#include <iostream>
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 dispaly()
    {
        cout<<"price is"<<m_price<<",name is"<<m_name<<endl;
    }
};

int main()
{
    
    const char *name="bmw";
    char name2[]={"b","m","w","\0"};//没写new就是在栈空间
    
    /*char name2[]={"b","m","w","\0"};// "\0"标志着字符串已经结束,必须要写
    cout<<strlen(name)<<endl;//求字符串长度,"\0"并不被计算,但存储时会将其一起存储
    cout<<name2<<endl;
    */
 	
    Car *car=new Car(100,name2);//此时m_name存储的是name2的地址,堆空间m_name指向栈空间name2
    
    /*堆空间指向栈空间是非常危险的,因为栈空间会被回收,那么堆空间所指向的位置就没有内容,此时堆空间就成为了空指针*/
    
    return 0;
}

深拷贝(deep copy)

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

​ 在浅拷贝的基础上,在堆空间继续申请空间,赋值原先堆空间中的内容,内容拷贝

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

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

#include <iostream>
using namespace std;

class Car
{
    int m_price;
    char *m_name;
    void copy(const char *name = NULL)
    {
        if(name==NULL) return;
        //申请新的堆空间
    	m_name = new char[strlen(name)+1]{};//大括号会将申请堆空间数据清零
        //拷贝字符串数据到新的堆空间
        strcpy(m_name;name);
    }
public:
    Car(int price=0,const char *name=NULL):m_price(price)
    {
        /*if(name==NULL) return;
        //申请新的堆空间
    	m_name = new char[strlen(name)+1]{};//大括号会将申请堆空间数据清零
        
        //拷贝字符串数据到新的堆空间
        strcpy(m_name;name);*/
        
        copy(name);
    }
    
    Car(const Car &car):m_price(car.price)
    {
        /*if(car.m_name==NULL) return;
        //申请新的堆空间
        m_name=new char[strlen(name)+1]{};
        //拷贝字符串数据到新的堆空间
        strcpy(m_name,car.m_name);*/
        
        copy(car.m_name);
    }
    
    void dispaly()
    {
        cout<<"price is"<<m_price<<",name is"<<m_name<<endl;
    }
    
    ~Car()
    {
        if(m_name == NULL) return;
        delete[] m_name;
        m_name=NULL;
    }
};

int main()
{
    Car car1(100,"bmw");
    Car car2=car1;//默认为浅拷贝
    car2.display();
    
    /*char name[]={'b','m','w','\0'};
    Car *car = new Car(100,name);
    car->display();
    */
    
 	return 0;   
}

对象型参数和返回值

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

class Car
{
    int m_price;
public:
    Car(){}
    Car(int price):m_price(price){}
    Car(const Car &car):m_price(car.m_price){}
};

void test1(Car car)//当传入值时相当于 Car car = car1,相当于拷贝了一个对象,变为引用或指针即可 Car &car
{
    
}

Car test2()
{
    Car car(20);//此处构造一个对象
    return car;//相当于拷贝构造
};

int main()
{
    Car car1(10);
    test1(car1);
    
    Car car2=test2();//默认构造一个对象,此种调用了一次拷贝构造(编译器做了优化,原来进行了两次),一次普通构造
    
    Car car3(30);//构造一个函数,此种调用了一次拷贝构造,两次普通构造
    car3 = test2();//并没有创建一个新对象,只是简单的复制
}

匿名对象(临时对象)

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

class Car
{
    
};

int main()
{
    Car();//匿名对象,创建结束后就会被回收             
    return 0;
}

隐式构造、explicit

隐式构造(转换构造)

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

可以通过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"<<this->m_age<<endl;
    }
};

void test1(Person person=30)
{
    
}

Person test2()
{
    return 40;
}

int main()
{
    /*
    Person p1;
    Person p2(10);
    Person p3=p2;
    */
    
    Person p1=20;//调用单参数构造函数,Person(int age),等价于Person p1(20)
    
    test1(30);
    
 	test2();
    
    Person p1;//先构造一个对象
    p1=40;//隐式构造一个对象
    
    
    
    return 0;
}

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

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

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

​ 2.有定义虚函数

​ 3.虚继承其它类

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

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

总结:

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

/*
很多教程都说:编译器会为每一个类都生成空的无参的构造函数       错误的
*/

class Person
{
public:    
    int m_price=5;//成员变量在声明的同时进行了初始化
	  //等价于
    /*int m_price;
    Person(int m_price=5){}*/
    
    virtual void run(){}
    
}

int main()
{
    Person person;
    return 0;
}


友元

​ 友元包括友元函数和友元类

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

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

class Point
{
    friend Point add(Point,Point);//友元函数
    friend class Math;//友元类
	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);//定义为友元函数之后就可以被允许直接访问私有成员变量
}

class Math
{
public:
    void display(Point p1,Point p2)
    {
        cout<<p1.m_x<<","<<p2.m_y<<endl;
    }
};
int main()
{
    Point p1(10,20);
    Point p2(20,30);
    
    return 0;
}

内部类

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

内部类的特点:

​ 支持public、protected、private权限

​ 成员函数可以直接访问其外部类对象的所有 成员(反过来则不行)

​ 成员函数可以直接不带类名、对象名访问其外部类的static成员

​ 不会影响外部类的内存布局

​ 可以在外部类内部声明,在外部类外面进行定义

#include <iostream>
using namespace std;

class Person
{
    static int ms_price;
    int m_age;
public:			//protected只有类内部以及子类能够使用,private只有类内部能够使用
    class Car
    {
        int m_price;
        ms_price=0;
        void run()
        {
            cout<<m_age<<endl;
        }
    };
};

int main()
{
    Person::Car car1;//定义Car对象,
    
    return 0;
}

内部类-声明和实现分离

class Point
{
	class Math
	{
		void test();
	};
};

void Poinnt::Math::test()
{

}
class Point
{
    class Math;
};

class Point::Math
{
    voit test(){}
};
class Point
{
    class Math;
};

class Point::Math
{
	voit test();
};

voit Point::Math::test()
{
    
}

局部类

在一个函数内部定义的类,称为局部类

局部类的特点

​ 作用域仅限于所在的函数内部

​ 其所有成员必须定义在类内部,不允许定义static成员变量

​ 成员函数不能直接访问函数的局部变量(static变量除外)

局部变量加上static修饰,其作用范围相当于全局变量

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

int main()
{
    return 0;
}

运算符重载(operator overload)

运算符重载(操作符重载):可以为运算符增加一些新的功能

全局函数、成员函数都支持运算符重载

class Point
{
    friend Point add(Point,Point);
    friend void operator+(Point,Point);
    friend ostream &operator<<(ostream &,const const Point &);
    friend istream &operator>>(istream &,Point &);
    int m_x;
    int m_y;
public:
    Point(int x,int y):m_x(x),m_y(y){}
    
    void display()
    {
        cout<<"("<<m_x<<","<<m_y<<")"<<endl;
    }
    
    Point(const Point &point)
    {
        m_x=point.m_x;
        m_y=point.m_y;
    }
    
    const void operator+(const Point &point) const//第一个const是用来限制返回值是个常量对象,不能给它赋值,第二个const是用来限制此函数为const函数,保证返回值能够再次调用此函数
	{
 	   return Point(this->m_x+point.m_x,this->m_y+point.m_y);
	}
    
    Point &operator+=(const Point &point)
    {
        m_x+=point.m_x;
        m_y+=point.m_y;
        
        return *this;
    }
    
    bool operator==(const Point &point)
    {
        return (m_x==point.m_x)&&(m_y==point.m_y);
    }
    
    const Point operator-() const
    {
        return Point(-m_x,-m_y);
    }
    
    Point &operator++()//前置
    {
        m_x++;
        m_y++;
        return *this;
    }
    
    Point operator++(int)//后置
    {
        Point old(m_x,m_y);
        m_x++;
        m_y++;
        return old;
    }
    
};

/*Point add(Point p1,Point p2)
{
    return Point(p1.m_x+p2.m_x,p1.m_y+p2.m_y);
}*/

/*void operator+(const Point &p1,const Point &p2)
{
    return Point(p1.m_x+p2.m_x,p1.m_y+p2.m_y);
}*/


//output stream -> ostream    
ostream &operator<<(ostream &cout,const Point &point)
{
    cout<<"("<<point.m_x<<","<<point.m_y<<")";
    return cout;
}

//input stream -> istream
istream &operator>>(istream &cin,Point &point)
{
    cin>>point.m_x;
    cin>>point.m_y;
	return cin;
}

int main()
{
    Point p1(10,30);
    Point p2(20,30);
    Point p3(30,30);
    
    //Point p3=add(p1,p2);
    //
    
    Point p3=p1+p2+p3;
   
    p3.display();
   
    p1+p2;//当重载写到类中时,相当于p1.operator+(p2);
    
    p1+=p2;
    (p1+=p2)=Point(40,50);
    
    -p1;
    Point p3=-(-p1);
    
    cout<<p1;//等价于operator(cout,p1)
    
    cin>>p1;
    cin>>p1>>p2;
    return 0;
}

调用父类的运算符重载函数

#include <iostream>
using namespace std;

class Person
{
public:
    int m_age;   
    Person &operator=(const Person &person)
    {
        m_age=person.m_age;
    }
};

class Student:public Person
{
public:
    int m_score;
    Student &operator=(const Student &student)
    {
        Person::operator=(student);//调用父类的运算符重载函数
        m_score=student.m_score;
    }
}

int main()
{
    Student stu1;
    stu1.m_age=20;
    stu1.m_score=100;
    
    return 0;
}

仿函数

将一个对象当作一个函数一样来使用

对比普通函数,它作为对象可以保存状态;

#include <iostream>
using namespace std;

class Sum
{
    int m_age;
public:
    int operator()(int a,int b)
    {
        return a+b;
    }
    
    void func()
    {
        
    }
    
};

int main()
{
    Sum sum;
    cout<<sum(10,20)<<endl;
    
    return 0; 
}

运算符重载注意点

有些运算符重载不可以被重载,比如

​ 对象成员访问运算符: .

​ 域运算符:::

​ 三目运算符:?:

sizeof

有些运算符只能重载为成员函数,比如

​ 赋值运算符:=

​ 下标运算符:[]

​ 函数运算符:()

​ 指针访问运算符:->

模板(template)

泛型,是一种将类型参数化以达到代码复用的技术,C++中使用模板来实现泛型

模板的使用格式

​ template <typename\class T>

​ typename和class是等价的

模板没有被使用时,是不会被实例化出来的

#include <iostream>
using namespace std;

class Point
{
	friend ostream& operator<<(ostream& , const Point&);
	int m_x;
	int m_y;
public:
	Point(int x, int y) :m_x(x), m_y(y) {}
	Point operator+(const Point& point)
	{
		return Point(m_x + point.m_x, m_y + point.m_y);
	}
};

ostream& operator<<(ostream& cout, const Point& point)
{
	return cout << "(" << point.m_x << "," << point.m_y << ")";
}

//int add(int a, int b)
//{
//	return a + b;
//}
//
//double add(double a, double b)
//{
//	return a + b;
//}
//
//Point add(Point a, Point b)
//{
//	return a + b;
//}

template <typename T> T add(T a, T b)//泛型
{
	return a + b;
}

int main()
{
	add<int>(10, 20);
	/*cout << add(10, 20) << endl;
	cout << add(1.5, 1.6) << endl;*/

	return 0;
}

模板的声明和实现如果分离到.h和.cpp中,会导致链接错误

一般将模板的声明和实现统一放到一个.hpp文件中

参数模板

template<class T>
void swapValues(T &v1,T &v2)
{
    T temp = v1;
    v1 = v2;
    v2 = temp;
}

int main()
{
    int a=10;
    int b=20;
    swapValues<int>(a,b);
    swapValues(a,b);
}

多参数模板

template<class T1,class T2>
void display(const T1 &v1,const T2 &v2)
{
    cout<<v1<<endl;
    cout<<v2<<endl;
}

动态数组、类模板

具体内容请见 实验 类模板

#include <iostream>
using  namespace std;

class Point
{
	int m_x;
	int m_y;
public:
	Point(int x, int y) :m_x(x), m_y(y) {}
};

template<typename Element>
class Array
{
	//用于指向首元素
	Element* m_data;
	//元素个数
	int m_size;
	//容量
	int m_capacity;

public:
	Array(int capacity=0)
	{
		m_capacity = (capacity > 0) ? capacity : 10;
		
		//申请堆空间
		m_data = new Element[m_capacity];
	}
	~Array()
	{
		if (m_data == NULL) return;
		delete[] m_data;
	}

	void add(Element value)
	{
		if (m_size == m_capacity)
		{
			//扩容
			/*
			* 1.申请一块更大的存储空间
			* 2.将就空间的数据拷贝到新空间
			* 3.释放旧空间
			*/
			cout << "空间不够" << endl;
			return;
		}

		m_data[m_size++] = value;
	}
	
	Element get(int index)
	{
		if (index < 0 || index >= m_size)
		{
			//报错,抛出异常
			throw "数组下标越界";
		}

		return m_data[index];
	}

	int size()
	{
		return m_size;
	}

	Element operator[](int index)
	{
		return get(index);
	}
};

int main()
{
	Array<int> array(3);
	array.add(10);
	array.add(20);
	array.add(30);
	array.add(40);
	array.add(50);

	cout << array.get(0) << endl;
	cout << array[1] << endl;
	cout << array.size() << endl;

	Array<Point> array1(2);
	Point point(1, 2);
	array1.add(Point(1, 2));
	array1.add(Point(3, 4));

	return 0;
}

类型转换

c语言风格的类型转换符

​ (type)expression

​ type(expression)

int a=10;
double d=(double) a;
double d2=double(a);

C++中有4个类型转换符

​ static_cast

​ dynamic_cast

​ reinterpret_cast

​ const_cast

使用格式:XX_cast(expression)

int a = 10;
double d = static_cast<double>(a);

const_cast

一般用于去除const属性,将const转换成非const

const Person *p1 = new Person();
p1->m_age = 10;

Person *p2 = const_cast<Person *>(p1); //将const转换为非const,p1和p2的值相同
Person *p3=(Person *)p1;	//与上面没区别,这是c语言的写法
p2->m_age = 20;

dynamic_cast

一般用于多态类型的转换,有运行时安全检测,不安全时将指针赋值为空指针

class Person
{
    virtual void run(){}
};

class Student:public Person{};

class Car{};

int main()
{
    Person *p1 = new Person();
    Person *p2 = new Student();
    
    Student *stu1 = (Student *)p1;//不安全,子类指针指向父类,不会进行安全检测
    Student *stu2 = dynamic_cast<Studnet *>(p2);//安全
    
    Car *c1 = (Car*)p1;//没有检测
    Car *c2 = dynamic_cast<Car *>(p2);//不安全,没有赋值
    
    return 0;
}

static_cast

对比dynamic_cast,缺乏运行时安全检测

不能交叉转换(不是同一继承体系的,无法转换)

​ 交叉转换:没有任何联系的两个类之间进行的转换

常用于基本数据类型的转换、非const转成const

reinterpret_cast

属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝

Person *p1 = new Person();
Person *p2 = new Student();
Student *stu1 = reinterpret_cast<Student *>(p1);
Student *stu2 = reinterpret_cast<Student *>(p2);
Car *car = reinterpret_cast<Car *>(p1);

int *p = reinterpret_cast<int *>(100);
int num = reinterpret_cast<int>(p);

int i = 10;
double d1 = reinterpret_cast<double &>(i);//结果并不相等

C++11新特性

auto

可以从初始化表达式中推断出变量的类型,大大简化编程工作

属于编译器特性,不影响最终的机器码质量,不影响运行效率

auto i = 10;//int
auto p = new Person 

decltype

可以获取变量类型

int a = 10;
decltype(a) b = 20;//int

nullptr

可以解决NULL二义性的问题

int *p1 = nullptr;//空指针 

快速遍历

int array[]={1,2,3,4};
int array[]{1,2,3,4};//更简洁的初始化数组方式
for(int item:array)
{
    cout<<item<<endl;
}

Lambda表达式

有点类似于JavaScript中的闭包、iOS中的Block,本质就是函数

完整结构:

[capture list](params list)mutable exception->return type{function body}

capture list:捕获外部变量列表

params list:形参列表,不能使用默认 参数,不能省略参数名

mutable:用来使用是否可以修改捕获的变量

exception:异常设定

return type:返回值类型

function body:函数体

有时可以省略部分结构

[capture list](params list)->return type{function body}
[capture list](params list){function body}
[capture list]{function body}
int add(int v1,int v2)
{
    return v1 + v2;
}

int sub(int v1,int v2)
{
    return v1 - v2;
}

int multiple(int v1,int v2)
{
    return v1 * v2;
}

int divide(int v1,int v2)
{
    return v1 / v2;
}

int exec(int v1,int v2,int (*func)(int ,int ))
{
   return func(v1,v2);
}



int main()
{
    
    
    
    ///
    
    cout<<exec(10,20,add)<<endl;
    cout<<exec(10,20,sub)<<endl;
    cout<<exec(10,20,multiple)<<endl;
    cout<<exec(10,20,divide)<<endl;
    //等价于
    cout<<exec(20,10,[](int v1,int v2){return v1+v2;})<<endl;
    cout<<exec(20,10,[](int v1,int v2){return v1-v2;})<<endl;
    cout<<exec(20,10,[](int v1,int v2){return v1*v2;})<<endl;
    cout<<exec(20,10,[](int v1,int v2){return v1/v2;})<<endl;   
    
    
    
    ///
    
	([]					//最简单的lambda表达式
    {
        cout<<"func"<<endl;
    })();			//调用lambda表达式
    
    ///
    void (*P)()=[]
    {
        cout<<"func"<<endl;
    }				//存储lambda表达式
    
    auto p=[]
    {
        cout<<"func"<<endl;//等价于上面
    }
    
    p();
    p();
    
    
    ///
    
    auto p = [](int a,int b)->int//带有返回值的lambda表达式
    {
        return a+b;
    }
    
    cout<<p(10,20)<<endl;
    
    ///
    return 0;
}

变量捕获

int main()
{
    int a=10;
  
    //默认都是值捕获
    auto func = [a]          //变量捕获
    {
        cout<<a<<endl;
    };
    
 
    //地址捕获
    auto func = [&a]          //变量捕获
    {
        cout<<a<<endl;
    };
    
    //隐式捕获(值捕获)
    auto func = [=]
    {
        cout<<a<<endl;
    };
    
    //隐式捕获(地址捕获)
    auto func = [&]
    {
        cout<<a<<endl;
    }
    
    a=20;
    
    func();
    
    return 0;
}

mutable

int a=10;
/*auto func = [&a]
{
    a++;
};*/
//等价于上面
auto func = [a]()mutable
{
    a++;//11
};

func()
cout<<a<<endl;//10
return 0;

C++14

泛型Lambda表达式

auto func = [](auto v1,auto v2){return v1+v1;};
cout<<func(10,20.5)<<endl;

对捕获的变量进行初始化

int a;
auto func = [a = 10]()
{
    cout<<a<<endl;
};
func();

cout<<a<<endl;

C++17

可以进行初始化的if、switch语句

//变量a,b的作用域使它所在的if语句、以及其后面的if-else语句
if(int a=10;a>10)
{
    a=1;
}
else if(int b=20;a>5&&b>10)
{
    b=2;
    a=2;
}
else if(0)
{
    b=3;
    a=3;
}
else
{
    b=4;
    a=4;
}

//变量a的作用域是它所在的switch语句
switch(int a=10,a)
{
    case 1:
        break;
    case 2:
        break;
    default:
        break;
}

异常

编程过程中的常见错误类型

​ 语法错误

​ 逻辑错误

​ 异常

异常时一种在程序运行过程中可能会发生的错误(比如内存不够)

异常没有被处理,会导致程序终止

int main()
{
    cout<<1<<endl;
    
    for(int i=0;i<9999;i++)
    {
        //这句代码可能会产生异常(抛出异常)、系统抛出异常
        try
        {
            int *p=new int[999999];
        }
        catch()
        {
            cout<<"产生异常:内存不够用";
            break;
        }
    }
}

抛出异常

throw异常后,会在当前函数中查找匹配的catch,找不到就终止当前函数代码,去上一层函数中查找。如果最终都找不到匹配的catch,整个程序就会终止。

int divide(int v1,int v2)
{
	if(v2==0)
	{
        throw "不能除以0"
    }
}

int main()c
{
    int a=10;
    int b=0;
    try
    {
        cout<<divide(a,b)<<endl;
    }
    catch(const char * exception)
    {
        cout<<"产生异常"<<exception<<endl;
    }
    catch(int exception)
    {
        cout<<"产生异常"<<exception<<endl;
    }
}

异常的抛出声明

为了增强可读性和方便团队协作,如果函数内部可能会抛出异常,建议函数声明一下异常类型

//抛出任意可能的异常
void func1()
{
    
}

//不抛出任何异常
void func2() throw()
{
    
}

//只抛出int、double类型的异常
void func3() throw(int,double)
{
    
}

自定义异常类型

//所有异常的基类
class Exception
{
public:
 	virtual const char *what() const=0;   
};

class DivideException:public Exception
{
public:
    const char *what() const
    {
        return "不能除以0";
    }
};

int divide(int v1,int v2)
{
    if(v2==0)
    {
        //抛出异常
        throw DivideException();
    }
    return v1/v2;
}

void test()
{
    try
    {
        int a = 10;
        int b = 0;
        cout<<divide(a,b)<<endl;
    }
    catch(const DivideException &exception)
    {
        cout<<"产生了异常(DivideException)"<<exceptioin.what()<<endl;
    }
}

拦截所有类型的异常

try
{
    int a=10;
    int b=0;
    int c = dibide(a,b);
}
catch(...)//拦截所有类型的异常
{
    cout<<"出现异常"<<endl;
}

标准异常(std)

系统自带的异常

智能指针(Smart Pointer)

传统指针存在的问题

​ 需要手动管理内存

​ 容易发生内存泄漏(忘记释放、出现异常等)

​ 释放之后产生野指针

智能指针就是为了解决传统指针存在的问题

​ auto_ptr:属于C++98标准,在C++11中已经不推荐使用(有缺陷,不如不能用于数组)

​ shared_ptr:属于C++11标准

​ unique_ptr:属于C++11标准

#include <iostream>
using namespace std;

class Person
{
public:
	int m_age;
	Person()
	{
		cout << "Person()" << endl;
	}
	Person(int age):m_age(age){}
	~Person()
	{
		cout << "~Person()" << endl;
	}
	void run()
	{
		cout << "run()-" << m_age << endl;
	}
};

void test()
{
	//Person* p = new Person(20);
	//可以理解为:智能指针p指向了堆空间的Person对象
	auto_ptr<Person> p(new Person(20));
	p->run();
}

int main()
{
	test();
    
    {//会报错,auto_ptr不能指向数组
    	auto_ptr<Person> p(new Person[10]);
    	p->run()
    }
    
    {
        shared_ptr<Person[]> p(new Person[30]);//share_ptr 这样使用
		cout<<p1.use_count()<<endl;//查看强引用计数
        
        shared_ptr<Person[]> p2=p;
        shared_ptr<Person[]> p3=p2;
    	p->run();              
    }
	return 0;
}

智能指针的简单自实现

template<class T>
class SmartPointer
{
    T *m_pointer;
public:
    SmartPointer(T* pointer):m_pointer(pointer){}
    
    ~SmartPointer()
    {
        if(m_pointer == nullptr) return;
        delete m_pointer;
    }
    
    T *operator->()
    {
        return m_pointer;
    }
};

​ 智能指针就是对传统指针的再度封装

shared_ptr

shared_ptr的设计理念

​ 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会释放

可以通过一个已存在的智能指针初始化一个新的智能指针

shared_ptr<Person> p1(new Person());
shared_ptr<Person> p2(p1);

针对数组的用法

shared_ptr<Person> ptr1(new Person[5]{},[](Person  *p){delete[] p;})

原理

一个shared_ptr会对一个对象产生强引用(strong reference)

每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着

当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1

当有一个shared_ptr销毁时(比如作用域结束),强引用计数就会-1

当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构函数)

shared_ptr的循环引用

只针对智能指针

​ weak_ptr 会对一个对象产生弱引用

​ weak_ptr 可以指向对象解决shared_ptr的循环引用问题

class Person;

class Car
{
public:
    shared_ptr<Person> m_person = nullptr;//将其变为weak_ptr<Person> m_person = nullptr,不会使强引用加1
    Car()
    {
        cout<<"Car()"<<endl;
    }
    ~Car()
    {
        cout<<"~Car()"<<endl;
    }
};

class Person
{
public:
    shared_ptr<Car> m_car = nullptr;
    Person()
    {
        cout<<"Person()"<<endl;
    }
    ~Person()
    {
        cout<<"~Person()"<<endl;
    }
};

int main()
{
    {//会导致内存泄漏
    	shared_ptr<Person> Person(new Person());
    	shared_ptr<Car> car(new Car());
    	
        person->m_car = car;
        car->m_person = person;
    }
    
    return 0;
}

unique_ptr

unique_ptr也会对一个对象产生强引用,它可以确保统一时间只有一个指针指向对象

当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了

可以使用std::move函数转移unique_ptr的所有权

class Person;

class Car
{
public:
    shared_ptr<Person> m_person = nullptr;
    Car()
    {
        cout<<"Car()"<<endl;
    }
    ~Car()
    {
        cout<<"~Car()"<<endl;
    }
};

class Person
{
public:
    shared_ptr<Car> m_car = nullptr;
    Person()
    {
        cout<<"Person()"<<endl;
    }
    ~Person()
    {
        cout<<"~Person()"<<endl;
    }
};

int main()
{
    //ptr1强引用着Person对象
    unique_ptr<Person> ptr1(new Person());
    //转移之后,ptr2强引用着person对象
    unique_ptr<Person> ptr2 = std::move(ptr1);
    
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中的重难点看这一篇就够了 的相关文章

  • 安装Docker详细步骤总结

    一 安装Docker 1 下载关于Docker的依赖环境 在Xterm中输入以下代码安装依赖环境 回车 yum y install yum utils device mapper persistent datalvm2 使用yum工具下载
  • MobaXterm连接报错Network error: Connection timed out

    今天打开MobaXterm远程连接我VMware虚拟机的时候出现以下界面 xff0c 问题详情如下 xff1a Network error Connection timed out Session stopped Press lt retu
  • 拉丁超立方抽样

    拉丁超立方 xff08 LHS xff09 适用于样本数量少的情况 xff0c 主要思想是概率分布的分层 xff0c 一层一个样本 xff08 每层的样本是随机的 当且仅当每一行和每一列只有一个样本时 xff0c 才能称为拉丁方格 LHS可
  • 银河麒麟用x11vnc实现远程桌面

    1 安装x11vnc 插入x11vnc的命令 sudo apt span class token operator span get update sudo apt span class token operator span get in
  • Vins-mono细节描述--相机imu外参标定

    vins mono能够在初始化的时候 自动标定相机和imu的外参 这个强大的功能其实并没有想象中那么复杂 一起来看看 1 在vins mono的配置文件中 就可以找到参数 来选择 是否需要估计参数 Extrinsic parameter b
  • lvi-sam的深度匹配策略

    在lvi sam中 xff0c 作者通过视觉处理的时候 xff0c 利用lidar的深度信息 xff0c 得到了更稳定的估计 那在代码里是怎么实现的呢 xff1f 一起来看看看呗 1 在lvi sam的feature tracker nod
  • 路由器硬件组成

    和其他计算机一样 xff0c 运行着baiIOS的路由器也包含了一个 中央处理器 CPU 不同系列和型号的路由器 xff0c CPU也不尽相同 路由器的处理器负责执行处理数据包所需的工作 xff0c 比如维护路由和桥接所需的各种表格以及作出
  • 系统调用与库函数的关系

    系统调用与库函数 1 系统调用 操作系统负责管理和分配所有的计算机资源 为了更好地服务于应用程序 xff0c 操作系统提供了一组特殊接口 系统调用 通过这组接口用户程序可以使用操作系统内核提供的各种功能 例如分配内存 创建进程 实现进程之间
  • FPGA中关于“后仿真正常,但上板测试出错”的问题

    FPGA中关于 后仿真正常 xff0c 但上板测试出错 的问题 求助帖 做的是矩阵键盘的扫描程序 xff0c 后仿真的扫描波形正常 xff1a 但上板子测试的时候用chipscope看扫描的波形却是这个样子的 xff1a 有带佬分析一下是什
  • 一张图搞懂数据结构体系——数据结构脑图

    基本概念 xff1a 数据 xff1a 数据是信息的载体 xff0c 是计算机程序加工的原料 数据元素 xff1a 是数据的基本单位 xff0c 也称元素 结点 数据结构 xff1a 是数据之间的相互关系 xff0c 是数据的组织形式 xf
  • 【入门篇】ESP8266直连智能音箱(天猫精灵)控制智能灯

    本系列博客学习由非官方人员 刘一周 潜心所力所写 xff0c 仅仅做个人技术交流分享 xff0c 不做任何商业用途 如有不对之处 xff0c 请留言 xff0c 本人及时更改 本系列博客内容是通过乐鑫ESP8266直连天猫精灵 xff0c
  • 如何进行CAN总线高效测试?

    CAN总线自BOSCH公司发明以来 xff0c 在汽车通信网络中的应用得到了广泛认可 随着汽车电子技术的发展 xff0c 车上的电子模块越来越多 xff0c 汽车内部的CAN总线节点也随之增多 一般汽车内部CAN节点少则10个 xff0c
  • keil5 中文注释

    keil5 中文注释 点击 Edit gt configuration 如下图 xff1a 在Editor界面中选择Encoding xff0c 如下图 xff0c 这三个均可显示中文注释 xff0c 推荐第一个 xff0c 保持Keil代
  • 【CMake学习】list使用

    添加链接描述 一 介绍 cmake的list命令即对列表的一系列操作 xff0c cmake中的列表变量是用分号 分隔的一组字符串 xff0c 创建列表可以使用set命令 xff08 参考set命令 xff09 xff0c 例如 xff1a
  • 【ros+movros安装与飞控连接测试,pixhawk2.4.8】

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 ros安装二 mavros总结 前言 记一下装ros和movros的过程顺便把一些重要的链接保存一些 提示 xff1a
  • 使用docker发布.net应用

    步骤 xff1a 创建 NET应用样例 创建包含生成 NET镜像所需引导的Dockerfile 构建一个镜像并基于此创建一个容器 设置容器数据卷和网络设置 使用Docker Compose编排容器 使用容器构建开发坏境 创建镜像 先决条件
  • python计算机视觉--全景图像拼接

    目录 一 RANSAC算法 1 1 RANSAC算法简介 1 2 算法基本思想和流程 1 3 RANSAC求解单应性矩阵 二 图像映射与全景拼接 2 1 简介 2 2 计算第二张图像与第一张图像之间的变换关系 2 3 将第二张图像叠加到第一
  • px4自带教程offboard下的gazebo多无人机编队仿真

    px4自带教程offboard下的gazebo多无人机编队仿真 主要教程参考这篇文章 xff0c offboard代码也源自下面链接 xff0c 增加了其他文件的配置细节 xff0c 链接如下 xff1a https blog csdn n
  • rotors_simulator与sitl_gazebo冲突导致报错“gzserver....”

    rotors simulator与sitl gazebo冲突导致报错 gzserver 创建时间2021 04 14 报错图片详见2021 04 14屏幕截图 总是出现gzserver symbol lookup error home zy

随机推荐

  • 安装ROS、gazebo、PX4基础细节及offboard控制

    新手参考教程安装ROS gazebo PX4基础细节及offboard控制 1 安装ROS 参考教程 2 安装PX4 参考教程 注 xff1a 1 在编译px4 Firmware前会经过安装步骤 xff0c 安装需要去github上git
  • 无人机模型记录

    今天看了这篇知乎 xff0c 收获非常大 xff0c 实现了一个非常基础的无人机动力学以及运动学模型 xff0c 包括公式推导等 xff0c 也解决了困扰我很久的问题 xff0c 在此基础上就可以加入控制算法 xff0c 设置轨迹等 htt
  • VMware安装Ubuntu20.04.5常见问题及解决方案

    文章目录 使用Xftp连接ubuntu系统ubuntu上安装指定版本nodejsubuntu设置连网ubuntu安装Java8ubuntu安装 deb格式软件ubuntu卸载 deb格式软件ubuntu中electron框架安装的缓存在如下
  • 无人机控制输入、PID控制

    无人机控制输入 PID控制 最近思路比较乱 xff0c 看到很多东西 xff0c 因为有各种控制 xff0c 需要在这里记录总结 控制输入 结合以下两个链接理解虚拟控制输入U1 U2 U3 U4 1 https blog csdn net
  • FreeRTOS学习(3)——任务创建和删除(静态)

    本代码是基于正点原子的STM32Mini板子 xff0c 结合其FreeRTOS课程进行学习 实验一 xff1a 设计4个任务 xff1a start task task1 task2 task3 start task任务 xff1a 用来
  • 华三交换机配置定时重启任务

    组网及说明 1 配置需求或说明 1 1 适用产品系列 本案例适用于如S7006 S7503E S7506E S7606 S10510 S10508等S7000 S7500E S10500系列 xff0c 且软件版本是V7的交换机 1 2 配
  • RTK差分通讯链路---Ntrip DTU(支持千寻位置,CORS站、自建站)

    在之前的博客中提到RTK差分通讯链路 电台 RTK技术的关键在于其获取了载波相位的观测量 xff0c 通过架设基准站和移动站 xff0c 利用电台的通讯方式 xff0c 使得移动站通过差分方式消除观测数据误差实现高精度 还有一种通讯方式 x
  • 北斗/GPS如何处理定位漂移?

    漂移是北斗 GPS导航时需要处理的问题之一 xff0c 漂移主要有两个方面 xff0c 第一 xff0c 速度过快 xff0c 以至于北斗 GPS的响应时间短于当前运行速度 xff0c 出现漂移 xff1b 第二 xff0c 在高大建筑密集
  • Jetson nx批量复制

    Jetson NX 批量克隆教程 文章目录 Jetson NX 批量克隆教程一 批量克隆是什么 xff1f 二 克隆步骤1 准备材料2 备份镜像3 克隆新SD卡 解压失败问题参考 一 批量克隆是什么 xff1f 辛辛苦苦在Jetson Xa
  • FreeRTOS一些常识笔记之快速上手

    一 为啥要用实时多任务操作系统 real time Operate System 简称有 xff1a RTOS xff0c 有如下的好处 用户无需关心时间信息 内核负责计时 xff0c 并由相关的API完成 xff0c 从而使得用户的应用程
  • Prometheus+node_exporter+grafana监控部署(上)

    目录 一 部署Prometheus 二 部署node exporter 三 把node exporter加载进Prometheus 四 部署grafana 一 部署Prometheus 1 从官网下载好Prometheus的安装包 2 解压
  • Zookeeper入门篇

    Zookeeper特性 xff1a Zookeeper xff1a 一个领导者 xff08 Leader xff09 xff0c 多个跟随者 xff08 Follower xff09 组成的集群 集群中只要有半数以上 xff08 不包括半数
  • 源码编译ROS的导航包navigation、编译navigation、也有安装navigation二进制包方式

    1 创建工作空间 span class token function mkdir span nav ws src p span class token function cd span nav ws src catkin init work
  • 深度无人机的视觉检测与跟踪神经网络:性能基准

    Unmanned Aerial Vehicle Visual Detection and Tracking using DeepNeural Networks A Performance Benchmark 深度无人机的视觉检测与跟踪神经网
  • 基于yolov5+fastreid+deepsort的TensorRT目标跟踪(C++版)复现过程

    基于yolov5 43 fastreid 43 deepsort的TensorRT目标跟踪 大部分的多目标跟踪项目都是用Python写的 xff0c 但是C 43 43 版本能够用TensorRT进行加速 xff0c 适合在边缘端部署 xf
  • JavaScript之定时器

    定时器 一 setTimeout 定时器二 停止 setTimeout 定时器三 setInterval 定时器四 清除setInterval 定时器五 电子时钟案例 在很多页面中 xff0c 我们都可以看到一些倒计时或者和时间相关的效果
  • JavaScript - 防抖与节流 基础代码

    防抖 无论触发了多少次函数 只执行最后一次函数 多次触发合并为一次 span class token tag span class token tag span class token punctuation lt span body sp
  • 一、QGC源码下载以及配置

    QGC V3 4版本 源码存放地址 https github com mavlink qgroundcontrol 下载方式 xff1a 1 git xff1a git clone https github com mavlink qgro
  • 无法被检测到的Linux恶意软件

    网络安全研究人员今天发现了一种完全无法被检测到的Linux恶意软件 xff0c 该恶意软件利用未公开的技术来监视并瞄准以流行的云平台 xff08 包括AWS xff0c Azure和阿里云 xff09 托管的可公开访问的Docker服务器
  • C++中的重难点看这一篇就够了

    sizeof 是一个运算符 xff0c 不是一个函数 看程序效率的快慢 xff0c 可以直接看其汇编语言程序的多少 扩展名 xff1a c语言 xff1a c c 43 43 xff1a cpp Java xff1a 先有类 xff0c 再