C++总计63个关键字,C语言32个关键字
以下是C++98里面的关键字
asm | do | if | return | try | continue |
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
在C语言中我们知道他的程序中名字的作用域分为两种是 局部作用域 和 全局作用域。
1.局部变量:如果一个变量被定义在函数中,那么这个变量的作用域就是一个局部作用域,在函数体外面不能访问这个变量,在别的函数定义同名变量,编译器会给他分配新的内存,他们互补干扰,你在你的地盘,我在我的地盘。
2.静态局部变量:想要延长他的生命周期我们可以在前面加一个static关键字把他变成静态局部变量,但是他的作用跟局部变量一样,即只能在定义该变量的函数内使用该变量,只是程序仅分配一次内存,函数返回后,该变量不会消失,该变量还存在只是不能使用他。
但是他的生命周期延长到了整个工程。
3.全局变量:在函数体外定义的变量,且存放在静态存储区中,在定义变量的位置到本源文件结束后都有效,在这个作用域中全局变量,可以为程序内各个函数引用,因此如果在一个函数中改变了全局变量的值, 就能影响到其他函数中全局变量的值。
4.静态全局变量:和全局变量一样,不过加了static之后会限制他们的作用域,使他们只能在定义他们的文件内使用,加了static可以将名字限制在一个文件中,防止名字污染。
滥用全局变量可能还是造成会造成名字污染,我加static也不行。
我们在之前说了C语言从你的文本文件变成可执行文件过程中,会经过预处理,编译,汇编,链接的过程。
而在预处理里面会有 头文件的展开,宏替换 ,取消注释 ,条件编译。
而qsort是C里面的一个快排函数,我预处理头文件展开了,恰巧有个函数声明,这个函数就叫qsort,即使你加了static关键字修饰,但是还是在本文件内使用,还是会有重定义的问题。
C语言这时只能去改这个变量的名称了,万一工程很大呢?这时为了解决命名冲突,C++里面就有了命名空间。
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
1.普通的命名空间
namespace A {
int a = 10;
// 命名空间中的内容,既可以定义变量,也可以定义函数
int add(int a, int b) {
return a + b;
}
}
2. 命名空间可以嵌套
namespace B {
int b = 20;
int add(int a, int b) {
return a + b;
}
namespace C {
int c = 30;
int add(int a, int b) {
return a + b;
}
}
}
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
namespace A {
int aa = 10;
int sub(int a, int b) {
return a - b;
}
}
namespace A {
int a = 10;
int add(int a, int b) {
return a + b;
}
}
一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
举个例子这个函数打印的是那个a呢?
#include
namespace A {
int a = 10;
// 命名空间中的内容,既可以定义变量,也可以定义函数
int add(int a, int b) {
return a + b;
}
}
int main() {
int a = 1000;
printf("%d", a);
return 0;
}
肯定是1000啦,那我怎么访问A这个命名空间里的a呢?
命名空间的使用有三种方式:
2.3.1 加作用域限定符 ::#include
namespace A {
int a = 10;
// 命名空间中的内容,既可以定义变量,也可以定义函数
int add(int a, int b) {
return a + b;
}
}
int main() {
int a = 1000;
printf("%d", A::a);
return 0;
}
2.3.2 使用using关键字将命名空间引入
#include
namespace A {
int a = 10;
// 命名空间中的内容,既可以定义变量,也可以定义函数
int add(int a, int b) {
return a + b;
}
}
using A::add;
int main() {
int a = 10;
int b = 20;
printf("%d", add(a, b));
return 0;
}
2.3.3 使用using namespace 命名空间名称引入
#include
namespace A {
int c = 10;
// 命名空间中的内容,既可以定义变量,也可以定义函数
int add(int a, int b) {
return a + b;
}
}
using namespace A;
int main() {
int a = 10;
int b = 20;
printf("%d\n",c);
printf("%d\n", add(a, b));
return 0;
3.C++输入和输出
不知道你是否和我一样在刚刚开始接触C语言的时候写过以下的代码
这个问题确实现在看起来让人啼笑皆非,但是刚刚开始学习的时候确实让我很费解,看半天也不知道错误在哪里。
但是在C++里面输入和输出是不需要格式控制符的,输入也不需要取地址符了。
在C++中使用标准输入输出必须包含
#include
int main() {
int a = 0;
std::cin >> a;
std::cout << a << std::endl;
}
不过我们一般不这样用会用using关键字来使用这个命名空间。
#include
using namespace std;
int main() {
//int a = 0;
//std::cin >> a;
//std::cout << a << std::endl;
int a, b, c;
double d;
//可以连着输入
cin >> a >> b >> c;
//可以连着输入不同类型的
cin >> a >> b >> c >> d;
cout << a << endl;
//还可以连着输出不同类型的
cout << "hello world!" << 100 << 3.14 << endl;
}
4.缺省参数
4.1C和C++函数的区别
举个例子,这个代码会报错吗?
答案是不会的,但是C++的编译器比C语言编译器更严格,就会报错。
在看这个例子
这个在C语言的的编译器下也是可以通过的但是在C++下就不行,C++编译器对函数的返回值类型检测的更加的严格。
还记得在之前写栈的实现的时候我们在初始化时候默认给了栈3个空间,在C语言的时候我们只能在调用这个方法的时候穿一个3作为参数进去,但是在C++里面我们可以这样做。
#include
using namespace std;
typedef struct Stack
{
int* array;
int size;
int capacity;
}Stack;
void InitStack(Stack* ps, int initCapacity = 3)
{
cout << initCapacity << endl;
}
int main()
{
Stack s;
InitStack(&s, 100);
InitStack(&s);
return 0;
}
这个语法在C++是可以通过的,这个3相当与一个备胎你没有传参数,那我就用你了。
缺省参数是声明或定义函数时为函数的参数指定一个默认值。
在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
#include
using namespace std;
void Test(int a = 0)
{
cout << a << endl;
}
int main()
{
Test(); // 没有传参时,使用参数的默认值
Test(10); // 传参时,使用指定的实参
}
4.3 缺省参数分类
4.3.1全缺省参数
所有参数都有默认参数
#include
using namespace std;
void Test(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Test(); // 没有传参时,使用参数的默认值
Test(10); // 传参时,使用指定的实参
}
那我们在看这段代码
#include
using namespace std;
void Test(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << " b = " << b << " c = " << c << endl;
}
int main()
{
Test(); // 没有传参时,使用参数的默认值
Test(100); // 传参时,使用指定的实参
Test(100, 200);
Test(100, 200, 300);
}
这里Test(100);和Test(100, 200, 300);我们都知道第一个替换和全部替换,那为啥Test(100, 200);
结果是100 200 30 不是 100 200 10呢?
这里先卖个关子不知道大家知道 __cdecl (下面会说)
4.3.2半缺省参数从右往左依次给出默认值
#include
using namespace std;
void Test(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Test(10);
}
注意
1. 半缺省参数必须从右往左依次来给出,不能间隔着给(下面会说)
2. 缺省参数不能在函数声明和定义中同时出现
如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
个缺省值,所以声明和定义不能一起出现。
3. 缺省值必须是常量或者全局变量
4.3.4 函数调用约定给定以下代码
#include
using namespace std;
int Test(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << " b = " << b << " c = " << c << endl;
return a + b + c;
}
int main()
{
int ret =Test(100, 200, 300);
Test(100, 200, 300);
return 0;
}
__cdecl 这个其实是函数的调用约定,在msvc中C和C++函数调用约定都是__cdecl。
__cdecl调用约定又称为 C 调用约定,是 C/C++ 语言缺省的调用约定。
参数按照从右至左的方式入栈,函数本身不清理栈,此工作由调用者负责,返回值在eax中。
由于由调用者清理栈,所以允许可变参数函数存在
我们可以清晰的看到 他把把返回值放在eax寄存器里面
我们在汇编代码可以清晰到是由右向左依次入栈的 ,所以我们在给默认值的时候,必须是从右向左依次给的,如果不然那函数就不知道把谁压栈。
函数调用约定有很多在此就不一一赘述
5. 函数重载重载?啥是重载呢?就好比是一个次在不同语境下会有不同的含义,比如说 菊花,鸹貔,老司机,这一类的词,我们就说他被重载了。
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
#include
using namespace std;
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
double Add(int left, double right)
{
return left + right;
}
int main()
{
cout << Add(10, 20) << endl;
cout << Add(1.2, 3.4) << endl;
return 0;
}
调用这Add函数每次根据传入的实参的类型不同,系统找到与之相应的入口参数进行匹配从而实现一个函数多种用途,也就是我们的重载。
所以我们根据以上例子就可以得出函数重载的条件:
- 必须在同一个作用域下。
- 函数名相同。
- 参数数据类型不同,个数不同,顺序不同。
函数返回值不可以作为重载条件!
5.2函数重载的原理- 编译器在编译的时候,会对传递的实参类型进行推演,然后根据推演的结果选择合适的函数进行调用,如果是完全参数类型都是匹配的,则直接调用。
- 如果不能进行完全匹配,则编译器会进行隐式类型转换,如果转换之后还没有合适的函数进行调用则报错,反之若有合适的函数则进行调用。
这个例子就很好的说明了发生了隐式类型转换,我传了2个参数一个int 一个 double int可以转换为double double 可以转换为int 这个时候编译器就不知道该调谁了,给你报错有两个相似的转换。
回到刚刚那个问题函数返回值不能作为重载的条件,我隐式类型转换,且匹配成功但是有两个函数返回值是不一样样,翻译器也不知调用谁。
为什么C语言不可以实现函数重载而C++可以呢?
首先我们回顾一下C和C++编译器的处理方式,预处理,编译,汇编,链接。
- 预处理 :头文件展开,宏替换,去注释,条件编译。
- 编译 : 将预处理后的源文件转换为汇编语言文件,只编译源文件,不编译头文件,头文件在刚刚预处理阶段已经展开。
- 汇编 : 虽然叫做汇编,但是不是转变为汇编代码,而是将刚刚的汇编语言文件转换为机器码,也就是二进制文件。
- 链接 :将生成的二进制代码与库函数以及其他目标文件,通过链接器链接起来形成可执行文件的过程。
对于这块不熟悉可看之前的一篇文章
C语言 程序的翻译 预处理 编译 汇编 链接 #define详解
C语言编译器的处理方式
这时候光声明没定义,给你报错报的是链接错误就是你这函数没有定义,我编译器找不到他的入口地址,但是你的名字不是add吗前面这个_add 是啥。
C++编译器处理方法
我这边也没有定义, 这个?add@@YAHHH@Z ,?add@@YAHNH@Z ,?add@@YAHNN@Z 是啥意思呀?
说明编译器在把函数名修改了!这里我们在谈到名字修饰的问题。
- 编译器需保证每个函数的实体名称唯一,防止名字污染,而为每个函数名进行修饰;
- 编译器在链接时,当出现调用函数,就是通过修饰后的实体函数名来进行查找的,不同编译器不同语言的修饰规则不同。
C语言的修饰规则就是简单在下面加上下划线。
在MSVC编译器下修饰规则则是这样的
我们再在Linux g++ 下面看一下
Linux是开源的他的方式方法就很好让人读懂没有MSVC这么诡异
C
没有做啥修饰就是单纯函数名字
C++
对于C++就是_z3指的是函数名字占三个字符,add就是函数名字,后面俩就是参数类型,i就是int,d就是double。
我们在回到刚才那个问题 ,为什么有名字修饰,为什么C语言不能进行函数重载?
- .c或者.cpp文件,需要在编译器经过预处理 编译 汇编 链接 生成可执行文件,才能让计算机直接运行,而 *** 作系统会在编译环节堆程序里的函数进程 名字修饰以便识别查找;
- C语言的所有编译器对进程函数的名字修饰并不涉及参数,只能通过函数名对不同的函数进行区分,故C语言不支持函数重载;
- C++的编译器对其函数进行的名字修饰会将参数类型包括,故C++支持函数重载
- 链接阶段是通过经过修饰的函数名字找他的入口地址,而修饰规则跟他参数列表的参数类型有关,跟函数返回值没有关系,因此函数返回值不可以作为重载条件。
假设一个情景在一个公司做开发,有人熟悉C语言,有人熟悉C++那怎么办呢?
在C++中可以使用extern “ C ”修饰一个函数,则是告诉编译器修饰函数按照C语言的风格编译.
在拿刚刚那个链接错误举例子来看。
#include
using namespace std;
extern "C" int add(int left, int right);
int main() {
int ret = add(1, 2);
}
明显发现这是浓烈的C语言风格,函数名称前面加了个下划线来进行修饰。
其实extern不是这样使用的一般是我们创建库的时候使用的。
这里演示一下VS2022静态库的创建以及使用。
就可以看见是一个静态库了。
生成解决方案后就可以在目录中找到了
找到刚刚 工程那个文件把静态库拷贝进去
我们在把他用起来
#include
using namespace std;
//刚刚的静态库文件
#include"testlib.h"
//预处理命令把静态库引进来
// .\是当前目录然后\转义字一下..\是当前目录的上一级目录
#pragma comment (lib,".\\..\\Debug\\lib\\testlib.lib")
int main() {
cout << add(1, 2) << endl;
}
我现在是C++写的静态库我C++当然可以用,那我现在创建一个C语言的工程呢?
我现在在C程序里面调add函数,就发现报错了。
这时候就该用到extern “ C ”了
#pragma once
#if __cpluscplus
extern "C"
{
#endif
int add(int left, int right);
int sub(int left, int right);
int mul(int left, int right);
int div(int left, int right);
#if __cpluscplus
}
#endif
条件编译一下就好了。
引用不是新定义的一个变量,而是给已经存在的变量起了一个别名,编译器不会给他开辟内存空间,他和他的引用变量共用一个内存空间。
张三,他也可以被叫做法外狂徒。
我们来看之前C语言这个例子。
#include
using namespace std;
//void swap(int a, int b) {
// int temp = a;
// a = b;
// b = temp;
//}
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
swap(a, b);
cout << a << endl;
cout << b << endl;
}
在C++里面新增了引用类型,也可以做到!
6.2引用的语法类型& 引用变量名(对象名) = 引用实体
void Test()
{
int a = 10;
int& ra = a;
printf("%p\n", &a);
printf("%p\n", &ra);
}
6.3引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
很明显验证了编译器不会给他开辟内存空间,他和他的引用变量共用一个内存空间。
一个变量可以有多个引用,这几个全是一个货,地址都一样,我rrra一改全改了。
引用一旦引用一个实体,再不能引用其他实体,后面会说。
· 在C语言里面一个变量被const修饰之后他就是一个不可以被修改的变量,而在C++里面一个变量被const修饰之后就是一个常量了,我给个数组把a丢进去也是可行的充分说明这时候a就是一个常量。
那我现在去给他来个引用,就会发现报错了!
再来看这个例子
刚刚说过引用必须引用的是一个实体跟引用实体类型相同,但是我们加const就可以,而且按理说我把a修改了ra应该也变呀,我们来看一下。
不仅a和ra没变,而且这个ra不是a的引用,他俩地址都不一样,说明ra就不是a的引用那是谁的引用呢?
你引用整形的,编译器就是会知道要发生隐式类型转换,编译器把12放在一个临时变量里面,你不知道变量的名字,也不知道他的地址空间,他的地址空间也没有名字,你不可以读也不可以写,说明这个东西具有常性,相当于你引用的是一个常量,那你不加const咋行呢?
6.5引用的应用 6.5.1简化代码增加可读性还是那个swap的例子
#include
using namespace std;
void swap(int** a, int** b) {
int* temp = *a;
*a = *b;
*b = temp;
}
//有了引用就可以这样做
void swap(int*& a, int*& b) {
int* temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
int* pa = &a;
int* pb = &b;
swap(&pa, &pb);
swap(pa, pb);
}
这样子代码的可读性增加了,引用其实可以代替一级指针(引用的本质......),可以吧指针简化一部分。
不想通过形参改变外部实参可以加一个常引用
#include
using namespace std;
typedef struct Stack
{
int* array;
int size;
int capacity;
}Stack;
// 如果引用类型作为函数的形参,尽量传递引用
// 如果不想通过形参修改外部实参,将引用给成const类型的引用
int StackSize(const Stack& s)
{
// s.size = 100;
return s.size;
}
int main()
{
Stack s;
//....进行一系列压栈 *** 作之后,栈中有5个元素
s.size = 5;
cout << StackSize(s) << endl;
return 0;
}
6.5.3作为函数的返回值类型
#include
using namespace std;
// 3. 作为函数的返回值类型
//int& Add(int left, int right)
//{
// int temp = left + right;
// cout << &temp << endl;
// return temp;
//}
int temp = 0;
int& Add(int left, int right)
{
temp = left + right;
cout << &temp << endl;
return temp;
}
// 注意:如果以引用方式作为函数的返回值类型,不能返回函数栈上的空间
// 如果要返回,返回的实体必须要比函数的声明周期长,即函数结束了,返回的实体依然存在
int main()
{
int& ret = Add(10, 20);
Add(20, 30);
Add(30, 40);
return 0;
}
这个例子来想一下这个ret会是多少呢?
走着
他这个引用的是啥呀咋就给变了。
来上汇编!
他引用的其实是临时变量的地址。
这里就要根据汇编语言来了解函数的调用过程
当add函数结束调用后esp 和 ebp也回到原来位置,add函数对应的栈帧已经被系统回收了 ,即add对应的栈帧的空间不能使用了,但是空间还在而且add运行完成之后留下的垃圾数据也在,我的ret引用的是temp那个空间下次调用add就会把原来的垃圾数据覆盖掉,返回后空间还是在的,以此类推,编译的时候就会给你一个警告,你引用的其实是局部变量的地址,你一解引用访问的就是非法的地址!
注意:如果以引用方式作为函数的返回值类型,不能返回函数栈上的空间。
如果要返回,返回的实体必须要比函数的声明周期长,即函数结束了,返回的实体依然存在。
#include
using namespace std;
int main() {
int a = 10;
int* p = &a;
*p = 20;
int& ra = a;
ra = 30;
}
这俩好家伙一模一样,那就是说明引用的底层实现就是按照指针的方式实现的,在底层引用就是指针常量(一旦引用一个实体,就不能引用其他实体)
引用其实是有空间的,在概念层面上说, 引用就是起别名,编译器不会为引用变量开辟新的内存空间,引用的变量和引用的实体用的是同一份空间,但是其实在底层引用是有空间的,他就是指针呀,在底层跟指针没有任何区别。
1.有NULL指针但是没有NULL引用。
这个NULL就是一个宏呀,是一个常量当然不能放在引用旁边。
2.引用一旦引用一个实体就不能在引用其他实体。
打码机指针可以任何时间指向同一个类型的实体。
其实就是好比char * 跟 char * const 的区别
3.指针初始化没有任何要求,可以随便指,只不过我们一般让他指向NULL,但是对与引用在使用的时候必须初始化。
4.引用++是给引用后的实体++,而指针++是+上其所指向类型的大小。
5.有多级指针但是没有多级引用。
6.在sizeof里面含义不一样,sizeof(引用)结果是的引用类型的大小,sizeof(指针)在64位下是8,32位下是4。
#include
using namespace std;
int main() {
int a = 10;
double b = 12.34;
double* p1 = &b;
int* p2 = &a;
int& ra = a;
double& rb = b;
cout << sizeof(p1) << endl;
cout << sizeof(p2) << endl;
cout << sizeof(ra) << endl;
cout << sizeof(rb) << endl;
}
7. 访问实体不同,指针需要解引用,引用编译器自己处理。
还记得在C语言里面的宏 #define 吗?
在你的文本文件变成可执行文件编译器处理的时候,第一步就是预处理,预处理会进行,头文件展开,去注释,条件编译,和宏替换,以为是在预处理进行的替换,那他会出一些奇奇怪怪的错误。
那我们一般对于数字进行宏替换的时候要加;吗?
#define MAX 1000;
#define MAX 1000
在Linux底下看看预处理完的文件
再举个例子在上文 我们刚刚谈到了const 修饰 一个变量在C++跟 C语言不同
C语言 const修饰就是原本这个变量变成了一个不可修改的常量。
C++中const修饰就是原本这个变量变成了一个常量。
#include
using namespace std;
//在C++中 const修饰的内容已经是一个常量了
// 在C语言中,const修饰的内容是一个不可以被修改的变量
int main()
{
const int MAX_SIZE = 100;
int array[MAX_SIZE];
return 0;
}
按理说我通过指针已经把a的内容修改了,不是应该是100吗?为啥是10?
const修饰的内容不仅仅是一个常量,而且还具有宏替换的效果,并且替换发生在编译时。
#include
using namespace std;
// const修饰的内容不仅仅是一个常量,而且还具有宏替换的效果
// 并且替换发生在编译时
int main()
{
const int a = 10;
int* pa = (int*)&a;
*pa = 100;
cout << a << endl;
cout << *pa << endl;
return 0;
}
7.1.2 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏。
下面是宏的申明方式:
#define name( parament-list ) stuff其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
比如这个例子
我们原本想的是应该是11*11 结果是121 为啥是21呢,那就看看.i 文件看看到底宏替换哪块出问题了
原来是替换之后根据运算符号的先后顺序所以结果是21那么怎样去避免这个问题呢我们在宏定义的时候这样去做
#define sq(x) (x)*(x)
不是按理来说应该是110吗咋变成1210了那我们再看看.i文件
#define sq(x) ((x)*(x))
带参宏就会有很多很多的问题在替换中提现到那只能尽可能的加()来避免 *** 作顺序带来的问题
尽量不要使用这种带参数的
但是却省掉了很多函数调用的开销,参数压栈,开辟栈帧等一些列 *** 作,运行效率高,他就是简单的替换!
7.1.3 宏的替换规则 1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。
如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。
对于宏,参数名被他们的值替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。
如果是,就重复上述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的变量。
但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。
(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
但是宏的缺点还是很多的C++为了解决就提出了内联函数。
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
#include
using namespace std;
inline int Add(int left, int right)
{
return left + right;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
cout << ret << endl;
return 0;
}
那他是怎样展开的呢?让我们再来看看汇编代码。
这一看没有展开呀 我call指令把这个当函数调用了,因为我们是在Debug模式下为了调试,如果展开了就不能调试了,在Debug模式下用户要进行调试。
发现就根本没有调用函数,直接把1Eh也就是30直接给打印了,就不用调用函数,也不需要参数压栈,开辟栈帧,那一系列的 *** 作了。
那我要看展开没有那就得设置一下编译器。
就可以了
7.3注意事项
1. inline是一种以空间换时间的做法,省去调用函数额开销。
所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
#include
using namespace std;
inline int Add(int left, int right)
{
Add(left,right);
return left + right;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("&d", ret);
return 0;
}
3. inline不建议声明和定义分离,分离会导致链接错误。
因为inline被展开,就没有函数地址了,链接就会找不到。
持续更新中...........
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)