1、什么是默认形参
在C++中可以给函数的参数设置默认值,当函数被调用时如果调用者提供了实参则使用实参,如果没有提供则使用默认值
2、默认形参要靠右
如果函数有多个参数,设置默认形参要从右到左连续设置
3、只在函数声明中设置默认形参
当函数的声明与定义分开实现时,只能在函数声明时设置默认形参,否则会出现语法错误
4、默认形参可能影响重载的效果
如果对函数进行重载,又设置了默认形参,调用时可能会造成冲突,因此对重载过的函数设置默认形参时一定要慎重
1、普通函数
普通函数会被翻译成二进制指令存储在代码段中,调用语句会生成一句跳转指令,当程序运行到跳转语句时,就会跳转到函数所在的代码段中的位置执行,执行完后会跳转返回。
2、什么是内联函数
内联函数也会被翻译成二进制指令,调用语句不会生成跳转指令,而是直接把内联函数的二进制指令替换调用语句,这样就没有发生跳转和返回,而是直接执行被调用函数,我们把这种函数称为内联函数
3、显式内联和隐式内联
显式内联:在函数的返回值之前添加 inline 该函数就以内联机制调用,但并不是所有的编译器都支持,我们现在使用的gcc以及g++就不支持
隐式内联:结构、联合、类的成员函数会自动被当作内联函数处理
4、内联适用的条件
内联的优点:
节约了函数传参、跳转、返回的时间,提高代码的运行速度
内联的缺点:
当内联函数在多个位置调用时,那么该函数的二进制指令就会形成多份,造成代码冗余,因此最终的可执行文件大小增加
适用条件:
1、适合内容简单且频繁调用的函数(一次调用执行多次),内容多且调用次数少,内容多且多次出现调用语句的函数就不适合内联,因为它节约的时间弥补不了牺牲的内存
2、带有递归属性的函数无法内联,编译器会自动忽略inline关键字
5、内联函数与宏函数的相同点和不同点
相同点:
都是采用以空间换时间的策略来提高程序的执行速度
不同点:
1、宏函数不是真正的函数,而是语句的替换,不会传参、没有类型检查、没有返回值、安全性低
2、内联函数是真正的函数,会传参,又严格的类型检查、有返回值、比宏函数安全
什么是引用:引用是一种取别名的机制
为什么要使用指针:
1、跨函数共享变量(输出型参数),可以用引用代替
2、提高传参效率,引用可代替
3、配合字符串,string可代替
4、配合堆内存,继续使用指针
什么情况下使用引用:
1、跨函数共享变量,比指针安全(不存在空引用,也极少出现野引用),比指针方便(不需要取地址、解引用)
2、提高传参效率,引用的传参效率比指针还高,因为指针还需要4/8字节存储地址编号,而引用一个字节都不需要,但是引用也有被修改的风险,因此为了保护目标需要增加const
使用引用需要注意的问题:
1、引用必须初始化,所以不存在空的引用
2、可以引用右值,但是必须使用const修饰
3、引用不能更改目标
4、函数返回引用类型数据时,不要返回局部变量的引用
指针与引用的相同点、不同点:
相同点:
都可以跨函数共享变量,都可以提高函数的传参效率,也需要const保护
不同点:
1、引用时一种取别名的机制,而指针是一种数据类型
2、引用不需要额外的存储内存,而指针需要4/8字节存储地址
3、引用必须初始化,指针可以不初始化
4、没有空引用,但有空指针
5、引用不可以更目标,但指针可以指向其他目标
6、引用不可以配合堆内存,而指针可以
7、可以定义指针数组,但是不能定义引用数组(可以定义数组指针,也可以定义数组引用,可以定义函数指针,也可以定义函数引用)
由于C语言中的强制类型转换的方式还能继续使用,所以C++中的强制类型转换就显得很鸡肋
为什么C++重新设计强制类型转换?
因为C语言的强制类型转换太危险
为什么C++的强制类型转换设计得很复杂、使用很麻烦?
因为C++之父认为只有程序设计得不合理时,才需要强制类型转换,之所以设计得那么复杂就是不想让程序员使用,从而去反思、重新设计自己的代码
1、静态类型转换 static_cast<目标类型>(原类型) 目标类型与原类型必须有一个方向可以进行自动类型转换,否则会出错 2、动态类型转换 dynamic_cast<目标类型>(原类型) 目标类型与原类型之间必须存在继承关系,否则会出错 3、去常类型转换 const_cast<目标类型>(原类型) 原类型或目标类型必须是指针或引用,且除了const属性不一样外,其它都相同,否则会出现错误 4、重解释类型转换 reinterpret_cast<目标类型>(原类型) 只能把整数转换成指针或者把指针转换成整数,否则都会出错(指针可以转指针)
面向过程:
关注的是如何解决问题,以及解决问题的步骤
面向对象:
抽象:找出(想象)出能解决问题的“对象”,分析该对象解决问题所需要的属性(成员变量)和行为(成员函数)
封装:把抽象的结果封装成一个类(结构)并给类的成员变量、成员函数设置相应的访问权限(public\private\protected)
继承:
1、在封装类之前先考虑现有的类是否能够解决一部分问题,如果有则可以把现有的类继承过来,在此基础上进行扩展,以此缩短解决问题的时间
2、可以把一份复杂的问题拆分成若干个小问题,每个小问题设计一个类去解决,最后把这些类通过继承合并成一个解决复杂大问题的类
多态:
发出一个指令,系统会根据实际情况执行相应的操作,这种特性就叫做多态(同一个命令有多种形态)
例如重载过的函数,就是多态的实现,当函数调用时,系统会根据参数,调用相应的函数版本,就是多态的一种实现
具体调用哪个版本的函数是在编译器在编译时就能够确定的,因此调用重载函数也叫做编译时多态
注意:对象的行为的实现依然是要面向过程,面向对象只是以更高的维度去思考问题,而不是寻求解决问题的捷径
什么是类和对象
类是由程序员自己设计的一种数据类型,它里面包含了成员变量、成员函数。
对象是类的实例化,可以理解成是用类创建的变量
类的设计和实例化:
class 类名 { 成员变量; // 类中的默认属性是private私有的 public: 成员函数; }; 实例化: 1、类名 类对象名; 2、类名* p = new 类名;
类的声明、实现和使用:
在头文件中声明:
class 类名 { 成员变量; public: 返回值 成员函数名(参数列表); }
在源文件中实现:
返回值 成员函数名(参数列表); { // 在成员函数中可以直接使用类中的成员变量,调用其他成员变量、函数都不需要 . 或 -> }
注意:如果类的内容不多,也可以考虑在头文件中全部实现
private:
私有的,被它修饰的成员只能在类内访问,也是类的默认访问属性
public:
公开的,被它修饰的成员可以在任何位置访问,一般把类的成员函数设置为公开的
protected:
保护的,被它修饰的成员可以在类内和子类中访问,但是不能在类外访问
构造函数是类的同名成员函数,当创建类时他会自动创建,当创建对象时会自动执行,一般构造函数负责对类进行初始化、分配资源。
class 类名 { int* p; public: 类名(参数) { p = new int; } };
1、构造函数必须是public,否则无法创建对象
2、构造函数可以重载,可以有多个版本
3、带参数的构造函数的调用
Test t(实参); Test* p = new Test(实参);
4、默认情况下编译器会自动生成一个隐藏的无参构造函数,而且什么都不做,但是一定会自动调用,一旦我们显式地实现构造函数,就不会再自动生成无参构造
Test t;// 调用无参构造函数,如果只有有参构造,该语句出错
5、也可以通过设置默认形参达到无参构造的效果,但本质上还是调用有参构造
6、 构造函数没有返回值
7、不要使用malloc为类对象分配内存,因为它不会调用类的构造函数
析构函数负责对类对象进行收尾,例如:释放类中的资源,保存数据等,当类对象被delete销毁时会自动执行析构函数
class 类名 { int* p; public: 类名(参数) { p = new int; } ~类名() { delete p; } }
1、析构函数也必须是public
2、析构函数没有参数,没有返回值,不能重载
3、当类对象的生命周期完结,或者使用delete语句销毁类对象时,会自动调用析构函数
4、如果不显式地实现析构函数编译器也会自动生成一个隐藏的什么都不做的析构函数
5、构造函数(malloc)、析构函数(free)都不一定会执行
6、不要使用free来销毁类对象,因为不会执行析构函数
初始化列表是构造函数的一种特殊语法,只能在构造函数中使用
class 类名 { 成员变量1; 成员变量2; public: 类名():成员1(初始化数据),成员2(初始化数据)... { // 初始化的数据可以是常量、变量 } }
1、类成员变量不可以赋初始值,而在构造函数执行之前,类成员变量已经定义完毕,因此带有const属性的成员变量无法在构造函数中进行初始化
2、初始化列表先于构造函数执行,初始化列表执行时成员变量还没有定义完成,所以它是唯一一种给带有const属性的成员变量赋值的方法
3、当参数名与成员变量名相同时,初始化列表会自动分辨成员和参数,不需要起不同的参数名
4、如果成员中有类类型,他的类类型成员的无参、有参构造函数都可以在初始化列表中调用