【C++重要知识】从C到C++彻底理解C++重要机制

目录

C++的重要特质讲解

一、C++和C的区别

1、面向过程和面向对象

2、const

3、引用

4、inline

二、面向对象模型探索

三、面向过程向面向对象的转变

四、对象的构造和析构

五、深拷贝和浅拷贝问题

1、拷贝构造函数

2、等号赋值操作符

3、move构造函数

六、运算符重载

七、继承和派生

八、多态

1、实现多态的三个条件

2、多态理论基础

3、虚析构函数

4、重载、重写和重定义

5、多态原理探究

九、模板即泛型编程

1、函数模板

2、类模板

十、C++类型转换

十一、异常处理的实现


C++的重要特质讲解

一、C++和C的区别

1、面向过程和面向对象

面向过程:加工的是一个个的函数

面向对象:加工的是一个个的(而类:实现了数据和函数的封装)

2、const

C语言中:通过指针的解引用可以更改原const对象的值,是一个变量

C++:const存放在一个符号表中,是一个符号表达,只有当取地址时,才会另外分配一个内存空间

3、引用

C++中引入了引用的概念,在编译器底层中,引用的实质是const的指针:Type& name------Type* const name。所以(1)引用有占内存,大小和指针大小一样。(2)引用不能改变。(3)引用不是对象(对象:内存空间的别名)

4、inline

C:宏函数,没有语法检查和作用域检查

C++:inline函数,有语法检查和作用域检查

5、C中没有重载,C++中有重载

二、面向对象模型探索

1、类对象的成员变量和成员函数是分开存储的。

2、静态成员变量和静态成员函数都属于类。

3、静态成员函数不包含this指针。

4、普通成员函数包含一个指向具体对象的this指针(即会把当前对象取地址传入,从而当很多对象使用同一块代码时,代码可以区分哪个对象的调用)。

存储:

(1)普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式。

(2)静态成员变量:存储于全局数据区中。

(3)成员函数:存储于代码段。

三、面向过程向面向对象的转变

       很重要的一个转变是全局函数\rightarrow成员函数的转变,对于成员函数的参数,隐藏了一个this指针,所以在把全局函数改写成成员函数时,可以省掉传入自身对象即可,在函数中若要使用自身对象的变量,直接使用this即可。

面向对象三大概念:

1、封装:将对象属性和对象方法封装成一个类。

2、继承:实现代码复用

3、多态:可以扩展功能

四、对象的构造和析构

关于匿名对象的讲解:(假设Test是一个类,有一个带参的构造函数)

1、Test t1=Test(1,2)   

解释:会产生一个匿名对象,即Test(1,2)调用带参构造函数产生一个匿名对象,然后直接将匿名对象转变为t1。注意这里的”=“没有调用构造函数。

2、

Test gg()
{
    Test A(1,2);  //调用Test的带参构造函数
    return A;     //采用拷贝构造函数根据A创建一个匿名对象,返回出去
}//析构对象A

解释:执行顺序:带参构造函数\rightarrow拷贝构造函数\rightarrow析构函数

3、

void objplay()
{
    gg();
}

void main()
{
    objplay();
}

解释:执行顺序:带参构造函数(创建A)\rightarrow拷贝构造函数(创建匿名对象)\rightarrow析构函数(A)\rightarrow析构函数(匿名对象)

4、匿名对象的去和留

void objplay()
{
    Test bb=gg();   //匿名对象没有调用析构函数
}

如果用匿名对象直接初始化同一个类型的对象,则匿名对象直接转正,不会被析构,若是采用等号赋值操作,则还是会被析构

五、深拷贝和浅拷贝问题

浅拷贝与深拷贝示意图:

对于浅拷贝问题:当obj1析构时,堆中对象已不存在,再对obj2对象进行析构,则会造成对同一块内存进行两次析构,所以会报错。解决办法就是采用深拷贝,当拷贝指针时,还得把指针所指向的数据也拷贝一份。

应用场景:

1、拷贝构造函数

     当类中有指针变量时,再采用拷贝构造函数时,则会出现浅拷贝问题,所以需要重写拷贝构造函数,即手动为自身对象的指针变量分配内存,再进行拷贝值。例如:Test Obj2(Obj1);  (1)根据Obj1的大小分配内存,(2)把Obj1数据复制到Obj2中。

2、等号赋值操作符

     同样当类中有指针变量时,再进行等号赋值操作时,同样进行的是浅拷贝操作,需要显式重载等号赋值操作,例如obj2=obj1,(1)先释放obj2的旧内存,(2)根据obj1分配内存大小,(3)把obj1复制到obj2中。

3、move构造函数

     move构造函数中传入的是右值,例如:在容器vector中,是以两倍速度成长,即当内存不够时,会重新找块两倍大的内存,进行把旧内存数据拷贝到新内存中,在这里有大量的拷贝操作,速度很慢。而如果采用move操作,则速度会提高很多。move操作即是浅拷贝操作,只是对指针进行拷贝,但拷贝完指针后,必须断掉旧指针,即旧指针赋为null。

三/五原则:拷贝构造函数、拷贝赋值运算符、析构函数、移动构造函数、移动赋值运算符。

六、运算符重载

实质:函数调用

步骤:(1)写出函数名:operator 运算符

           (2)写函数参数:根据操作数来写

           (3)写函数返回值:根据业务,完善函数返回值(还需注意返回引用还是值)返回引用:可以实现链式编程

1、<<和>>只能进行友元函数重载

2、=  先销毁旧内存,再分配内存,最后copy

3、() 使类像一个函数被调用

4、不要重载&&和|| :实现不了短路规则

七、继承和派生

1、类型兼容原则

(1)基类指针(引用)可以指向子类

(2)可用子类对象初始化父类

2、继承和组合混搭

原则:构造顺序:祖父\rightarrow父类\rightarrow组合\rightarrow自己;析构顺序:相反

3、多继承

(1)有共同祖父

则会出现二义性,可以在父类继承祖父时采用虚继承,则子类只从祖父那继承了一份下来。

(2)两个父类没有共同祖父,而两个父类有一样的成员变量,这时子类在调用这个变量时会产生二义性,这时采用虚继承也没用,只能加作用域解决。

八、多态

1、实现多态的三个条件

(1)有继承

(2)要有虚函数重写

(3)用父类指针(引用)指向子类对象

2、多态理论基础

       对函数加上virtual关键字,则会在编译时是动态联编,根据具体对象,执行不同对象的函数,表现多态。如果不加则是静态联编,编译器在编译阶段就决定了函数的调用。

3、虚析构函数

    想通过父类指针把子类析构函数都执行一遍,即通过父类指针释放子类资源,但由于析构函数默认是非虚函数,所以不会产生这种效果,需自己把父类析构函数写成虚析构函数。

注意:当父类中有虚函数时,析构函数一般都应该写成虚析构函数。

4、重载、重写和重定义

重载:必须在同一个类中进行

重写:必须发生在子类与父类之间:(1)多态:加virtual;(2)重定义:不加virtual

通过子类调用一个函数时,首先看下子类中是不是有,有则只会在子类中进行查找合适的函数执行,若子类没有则才会在父类中查找,若是通过父类调用一个函数,则先看下是不是虚函数,若是虚函数,可能发生多态,若不是虚函数,直接调用父类中的函数。

5、多态原理探究

1、C++编译器会在对象中添加一个vptr指针,指向虚函数表(虚函数表:存放虚函数指针的数组),通过vptr指针去找各自对应的虚函数表,再找对应的虚函数入口地址,进行迟绑定

2、三个动手脚地方:(1)提前布局:即添加vptr指针;(2)定义虚函数时:创建虚函数表;(3)调用虚函数时:发生多态,vptr执行的过程。

3、vptr指针是分步初始化

        即在父类的构造函数中调用虚函数,不会发生多态。在执行父类构造函数时,vptr指针先指向父类的虚函数表,等父类构造函数执行完毕后,vptr指针才指向子类的虚函数表。

九、模板即泛型编程

1、函数模板

(1)C++编译器模板机制:编译器从函数模板通过具体类型产生不同的函数,编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的对方对参数替换后的代码进行二次编译。

(2)函数模板不会进行自动类型转换,而普通函数会进行隐式的类型转换。

2、类模板

继承中的类模板:模板类派生时,需要具体化模板类;在子类调用构造函数时也需要具体类。

类模板中使用友元函数:

//在类中声明
frinend ostream& operator<< <T> (ostream& out,Test& t1);

//在类外定义
typedef <typename T>
ostream& operator<<(ostream &out,Test<T>& t1)
{

}

当重载<<和>>只能用友元函数,此时在类中声明时,函数名后面需要加上<T>。

当类模板遇上static时,每个不同类型的类都有属于自己的static成员,编译器会构造多个具体的类

十、C++类型转换

1、static_cast<>():编译时会做类型检查,只有当可以隐式转换时,则用这个。

2、reinterpret_cast<>():强制类型转换。

3、dynamic_cast<>():用于继承中父类向子类的转换。

4、const_cast<*>p:要确保p所指向的内存空间能被修改,去掉const属性。

十一、异常处理的实现

C++的异常处理机制使得异常的发生异常的处理不必在同一个函数中。

抛出类类型异常:

throw Class();  //创建一个对象

try
{

}
catch(Class e)   //拷贝给e,会析构两次,先析构异常变量
{
}

catch(Class &e)  //不会拷贝,只析构一次,就是之前创建的对象,最好
{
}

catch(Class *e)  //接受不到异常,类型不同
{
}

//若是这样:
throw new Class;
catch(Class *e)  //这个e需要自己手动析构
{
    delete e;
}

总结:最好在捕获异常类时采用引用的方法。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烊萌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值