做创意美食的视频网站有哪些万网域名注册查询网
这一章会介绍C++入门必须掌握的一些基础概念。
一、函数重载
1、什么是函数重载?
函数重载是C++相比于C语言的一个重大改进。
即C++允许在同一作用域内声明多个功能类似的同名函数,这些函数的参数类型 / 个数 / 类型顺序不同。(注:返回值不同不能构成重载,这是由后文会介绍的函数名修饰规则所决定)
2、C++如何支持函数重载的?(Linux g++编译器环境下)
在C/C++(一)中介绍了,在C / C++中,想要形成一个可执行程序,必须经历 预处理 --> 编译 --> 汇编 --> 链接 四个阶段。
在编译阶段,g++ 编译器会根据其函数名修饰规则(_Z + 函数长度 + 函数名 + 函数参数类型的首字母)(可以发现,修饰规则中没有返回值,因此返回值不同,不能构成函数重载)对函数进行修饰,根据其规则可以看出,函数参数不同,实际上在编译阶段会被修饰成不同的函数。这也就是C++支持函数重载的根基。
示例代码:
#include <iostream> using namespace std;int add(int x, int y) {return x + y; } int add(int x, int y, int z) {return x + y + z; } int main() {int res1 = add(1, 2);int res2 = add(1, 2, 3); }
3、为什么C语言不支持函数重载,C++支持?
因为C语言的编译器的函数名修饰规则并不会修饰函数名,而是按照原先函数名。
二、引用和指针
1、什么是引用?
引用是给已经存在的变量取别名, 编译器不会为引用开辟空间,引用和其引用的变量共用同一块内存空间。
使用格式:
类型& 引用变量名(对象名) = 引用实体
1.1 有关引用的一些注意事项
1、& 跟在类型和变量名之间,叫做引用。否则,叫做取地址。
2、引用必须在定义时就初始化。
3、一个变量可以有多个引用,引用本身也可以有引用。
4、引用一旦引用了一个实体,就不能再引用其它实体。
5、非常量应用不能绑定到常量对象 / 临时常量对象 /不同类型的对象;常量引用可以绑定到 常量对象、临时常量对象或不同类型的对象(需要显式类型转换)。
1.2 常引用(常量引用 / const 引用)
常引用是引用的一种特殊形态,允许引用一个对象,可以绑定到常量对象 / 临时常量对象 / 不同类型的对象(需要显式类型转换),但是不能通过该引用修改对象的值,主要用途是提高代码的安全性和可读性。
示例代码:
void TestConstRef() {const int a = 10;// int& ra = a; // 该语句编译时会出错,a为常量,普通引用不能绑定常量对象const int& ra = a; // 正确方法// int& b = 10; // 该语句编译时会出错,10为临时常量,const int& b = 10; // 正确方法double d = 12.34;//int& rd = d; // 该语句编译时会出错,类型不匹配(具体原因是由于类型转换时会产生临时变 // 量,而临时变量具有常性造成无法引用)const int& rd = static_cast<int>(d); // 正确方法1double& rd = d // 正确方法2 }
2、指针和引用的区别(用法 + 底层原理)
1、引用是定义一个变量的别名,与这个变量共用同一内存空间,大小也与这个变量的大小 相同;而指针存储的是变量的地址,需要额外占用一小段空间储存,大小也始终是指针 的大小。(32位下是4字节、64位下是8字节)
2、引用必须在声明的时候就进行初始化,不能为空,并且一旦初始化就不能改变引用的对 象(因为要让编译器知道,引用总是指向这个有效的对象,以便生成更高效的代码, 直接访问目标变量的内存地址,而不需要额外的解引用操作);指针由于通过解引用显 式访问变量,所以可以在声明的时候不初始化,也可以随时改变指向的对象。
3、引用++就相当于其引用的变量++;指针则是指针指向下一个内存地址。
4、引用访问绑定的实体,直接访问,由编译器来优化;而指针需要解引用。
5、因为引用不能为空 + 编译器优化,引用比指针更安全。
3、引用的两大使用场景
三、宏的替代方案
宏作为从C语言继承而来的概念(C / C++(一)中有详细介绍),其有着一定的优点(代码复用性强;可以提高性能),但是其缺点更多(由于预编译阶段进行了替换,宏不方便调试;代码的可读性较差,可维护性也不好,还容易误用;也没有类型安全的检查)
所以C++提供了三种用来替代宏的方案
替代宏定义标识符:const变量 / enum枚举
替代宏函数:inline内联函数
1、const变量替代宏定义标识符
// 宏定义标识符 #define PI 3.14159// const变量定义 const double PI = 3.14159;
优点:
1、类型安全:const 变量具有明确的类型,编译器可以进行类型检查,避免类型错误。
2、调试友好:
const
变量在调试器中可以显示其值和类型,便于调试。3、作用域控制:
const
变量可以有局部作用域,而宏定义在整个文件范围内有效,容易引起 命名冲突。
2、enum枚举替代宏定义标识符
// 宏定义标识符 #define RED 0 #define GREEN 1 #define BLUE 2// 枚举定义标识符 enum Color {RED,GREEN,BLUE };
优点:
1、类型安全:
enum
类型的变量只能取枚举值,方便编译器进行类型检查。2、语义清晰:
enum
明确表示一组相关的常量,代码更具可读性。3、范围限制:枚举值在编译时有固定的范围,避免了非法值的赋值。
3、inline 内联函数替代宏函数
3.1 什么是内联函数?
以 inline 关键字修饰的函数叫做内联函数,
特性:在编译的时候,C++编译器会在调用内联函数的地方直接用函数体替换,没有调用普 通函数需要建立栈帧的额外开销,可以提升程序运行的效率。
缺点:以空间换时间,可能会导致目标文件变大。(因此,内联 inline 关键字实际上只是向 编译器发出的一个内联请求,编译器会自己决定是否把这个函数设置为内联函数,如 果函数过长,编译器将忽略内联请求)
使用建议:
1、函数规模较小、不是递归,调用不频繁的函数,可以采用 inline 内联修饰;其它 的不建议;
2、同时也不建议内联函数的声明与定义分离,因为分离了,每个cpp文件里都会有内联函数 的内联展开,链接器会发现不同的对象文件中都有这个内联函数的定义。无法确定应该 使用哪个定义,导致多重定义错误。(如果一定要使用,内联函数的声明和定义必须放 在同一个地方,通常是头文件中,确保每个编译单元都能看到完整的内联函数定义)
3.2 以内联函数替代宏函数
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// #define 定义宏 #define MAX(a, b) ((a) > (b) ? (a) : (b))// inline内联函数代替宏函数 inline int max(int a, int b) {return (a > b) ? a : b; }
优点:
1、类型安全:
inline
函数具有明确的参数类型和返回类型,编译器可以进行类型检查。2、副作用避免:宏定义在参数计算上有潜在的副作用,而
inline
函数没有这个问题。3、调试友好:
inline
函数在调试器中可以显示调用栈和局部变量,而宏定义则不行。4、代码可读性:
inline
函数的代码更具可读性和可维护性。
四、新的空指针表示方式:nullptr
nullptr是C++11引入的一种新的空指针表示方式。在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
在传统的C语言 / C++98标准中,空指针的表示方式一般是NULL,而NULL的定义存在缺陷。
NULL的缺陷
NULL在在 C++ 中,通常定义为
0
或(void*)0
,这可能导致一些意外的行为:void f(int) {cout<<"f(int)"<<endl; } void f(int*) {cout<<"f(int*)"<<endl; } int main() {f(0);f(NULL);f((int*)NULL);return 0; }
这段代码的本意是希望想通过 f(NULL) 调用指针版本的 f(int*) 函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,还必须对其进行强转 (void *)0。
nullptr的优势
1、类型安全:
nullptr
是一个类型,避免了与整数类型的隐式转换。2、更好的可读性:
nullptr
明确表示这是一个空指针,且不需要包含其它头文件。3、兼容性更好:
nullptr
可以更好地与模板一起使用。4、避免宏定义的问题:
nullptr
是一个关键字,不会受到宏定义的影响。5、支持
nullptr
类型的检查:可以使用std::nullptr_t
类型进行类型检查。因此为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr。