𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary_walk
⸝⋆ ━━━┓
- 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━ ➴ ⷯ本人座右铭 : 欲达高峰,必忍其痛;欲戴王冠,必承其重。
👑💎💎👑💎💎👑
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑 希望在看完我的此篇博客后可以对你有帮助哟👑👑💎💎💎👑👑 此外,希望各位大佬们在看完后,可以互相支持,蟹蟹!
👑👑👑💎👑👑👑
WeChat_20240731222859
目录:
一:关键字
二:命名空间
三:C++输入和输出
四:缺省参数
五:函数重载
六:引用
七:内联函数
八:auto关键字(C++11)
九:基于范围的for循环(C++11)
十:指针空值:nullptr(C++)
思维导图:
1:关键字
学过C语言应该对关键字这一个概念都不陌生吧,在C语言里面,有32个关键字,而我们的C++里面有63个关键字
以下是C++中的关键字,对于初期学习C++的小白而言(比如我),不太建议大家全部背下来,一个是不太理解,其次是容易忘记,所以建议大家可以在学习的过程边学边记
2:命名空间
2.1前言
在C中,自定义的变量……是不许和标准库里面重名的
具体见下:
1)
此时就是自定义的变量rand 与库里面的函数rand()发生冲突
2)
这时候发生了冲突(命名冲突)
C++中用关键字:namespace来解决这个问题
具体使用形式:
namespace 名称 { }
2.1命名空间的定义
namespace + 名字 +{ }
注意:{ }后面是没有分号的,不同于结构体的定义
命名空间也是可以支持嵌套定义的
2.2命名空间的使用
第一种方法:指定的命名空间的名字+域作用限定符(::)
第二种方法:部分展开(授权)
第三种方法:全部展开( 使用using namespace +命名空间名称 )
此时我们发现Add()这个函数频繁使用到,每次调用此函数就需要写ysx :: 比较麻烦,此时可以指定ysx这个命名空间进行全部授权
但是全部授权也是存在一些问题的,可能ysx 这个命名空间全部展开后的内容有的与库里面发生冲突,所以一般在大型的工程中多数采用指定部分展开(授权)
总的来说:对于日常的练习,可以进行全部展开:using namespace std ;
3:C++输入和输出
对于这两个运算符,姑且一笔带过,后期会详细讲解。
3.1 流插入运算符 <<
当我们想在输出设备(终端)进行打印的时候,就需要用到流插入运算符(注:endl 是换行的意思等价于C语言的 ‘\n’)
3.2流提取运算符>>
同理,有输出,自然就有输入
4:缺省参数
4.1概念
4.2分类以及使用
全缺省参数:(可以理解为形参全部有对应的数值)
第二次就是使用的一个全缺省参数 。
半缺省参数:
对于半缺省参数(部分缺省参数) 是否支持下面的写法???
显然是编译不过的。
对于半缺省参数 ,只支持从右向左依次给出,不能间隔着的给出。
1. 半缺省参数 (也就是函数的形参的数值)必须 从右往左依次 来给出,不能间隔着给2. 缺省参数 不能在函数声明和定义中同时出现
5:函数重载
5.1 概念
5.2 使用
5.2.1:形参的类型不一样
5.2.2参数的个数不同
5.2.3参数的顺序类型不一样
注意一下哈:这个顺序类型不一样,可不是指的形参名不一样
也就是我所说的下面这种写法:
此时这2个Add()函数是同一个函数
5.2.4 函数重载必须是在同一个作用域里面
5.2.5
通过这个举例,想要说明一下,判断一个函数是否是重载函数,就看函数的参数列表是否不同就OK。
注意:
1)当 同名函数 的返回类型不一致的时候,是不支持函数重载的
2)函数重载并不影响运行的效率(在编译的阶段完成函数重载的)
3)C++支持函数重载的本质:编译器的底层逻辑,是通过对调用的函数名字进行修饰来明确
调用哪一个函数 ,不同的编译器对函数名字修饰是不一样的。
4) 函数重载的大前提必须是在同一个作用域里面
5.3 函数重载本质
函数重载的本质:对函数名字进行修饰。
在不同的机器上,修饰的规则是不一样的。
接下里以Linux 操作系统来进行解释哈~~~
首先打开服务器,使用vim 编辑器进行代码的编写,退出vim后;
就是对当前所在的文件test.cpp进行编译,链接: 对应指令 g++ test.cpp
之后会默认生产对应的a.out 文件,使用指令:objdump -S a.out 打开生成的汇编文件
分析:
C 语言的编译器而言,调用函数,可以说是不涉及到对函数名修饰的,所以也就不存在函数重载这一说法。
思考一个问题当函数返回值类型不同,除此之外,函数名,以及参数类型都是一样的,是否可以
构成函数重载。
此时编译不过。
答案是:不行滴。
6:引用
6.1概念
6.2使用
类型+&+别名 = 实体
1)一个变量可以有多个引用
2)使用引用必须进行初始化
3)场景3:做参数
此时 x, y 分别是对 a, b 的一个引用
其实在这个场景使用下,对于引用的使用不是比较明显。
看看下面的代码:
接下里里对单链表为例,具体分析过程:为什么传2级指针,单链表这个结构体如何定义等等一系列问题就不多多赘述了,链接在此,各位自取哈~~~
版本一:使用指针传参
typedef struct ListNode
{
int val;
struct ListNode* next;
}ListNode,*PListNode;
void PushBack(ListNode** phead, int x)//此时必须串二级指针,可能会是进行头插第一个节点
{
//先为插入x 开辟节点
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror(" PushBack(ListNode** phead, int x)::malloc");
return;
}
newnode->val = x;
newnode->next = NULL;
if (*phead == NULL)
{
//此时第一次进行数据插入,需要改变的是结构体的指针
*phead = newnode;
}
else
{
//先找到最后一个节点,在进行插入
ListNode* tail = *phead;
while (tail->next != NULL)
{
tail = tail->next;
}
//找到尾结点
tail->next = newnode;
}
}
int main()
{
ListNode* head = nullptr;
PushBack(&head, 1);
PushBack(&head, 2);
PushBack(&head, 3);
PushBack(&head, 4);
PushBack(&head, 5);
return 0;
}
运行结果:
版本二:使用引用 传参
1)
typedef struct ListNode
{
int val;
struct ListNode* next;
}ListNode,*PListNode;
void PushBack(ListNode*& phead, int x)//引用传参,对于用户避免了对指针的使用
{
//先为插入x 开辟节点
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("PushBack(ListNode*& phead, int x)::malloc");
return;
}
newnode->val = x;
newnode->next = NULL;
if (phead == NULL)
{
phead = newnode;
}
else
{
ListNode* tail = phead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
int main()
{
ListNode* head = nullptr;
PushBack(head, 1);
PushBack(head, 2);
PushBack(head, 3);
PushBack(head, 4);
PushBack(head, 5);
return 0;
}
运行结果:
对于PushBack()这个尾插函数的参数形式,可能有些友友们会在一些书籍看到以下的写法:
接下来,我慢慢给各位进行分析。
4) 引用做返回值
注意虽然此时程序输出1,但是这个程序是存在问题的。
分析:
Count()函数里面的n 是一个局部变量,当此函数调用结束的时候,n对应的内存空间就会归回系
统,编译器会生成一个临时对象,把n 这个变量拷贝给给这个临时对象,之后以引用的方式 返回
到调用Count 函数的起始地方,ret = 1。
我要是以引用的方式接收函数的返回值就会出现下面问题:
当Count()函数调用结束后以引用方式返回 1,第93 又是以引用方式接受, ret仍然是1,
printf() 是一个函数调用,在调用此函数之前先进行传参,参数是1,之后进行函数栈帧创建,调用
结束后,printf()栈帧结束,此时输出 1
图解:
对于第95行代码分析一样:调用printf()函数之前先进行传参 ,只不过此时的ret 对应的数值不再是
1(空间已经归还系统了),调用结束后,栈帧销毁,这也就是为什么打印的是随机值了
再康康以下代码:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;//输出啥???
return 0;
}
其实这个问题的分析和上面是一样滴
分析见下:
对于顺序表进行指定位置读取和写入使用引用的方式:
相信不少老铁看到这,觉得引用 简直不要太爽。
大家有兴趣的话,可以看看之前顺序表的增删查改是如何操作滴,链接在此详解动态顺序表
使用引用做返回值要点
关键是判断出了当前作用域返回的对象是否还存在(空间是否已经归还操作系统);若存在,可以使用引用,否则不可以
关于实参与形参的关系:形参是对实参的一份临时拷贝(内存的申请),对形参的临时修改不会
影响实参的变化
当我们用引用来作为参数,减少了空间的开辟
总结:
1)
如果函数以引用返回并且出了函数作用域,对应的空间并没有归还给系统,那就可以以引用
返回,比如:malloc,静态区的,上一层函数栈帧的……
其实在底层逻辑上,引用也是以指针方式来实现的
2)函数返回类型以引用和数值返回的时候,差距很大
3)传值和传引用效率的比较
当参数或者函数返回类型是数值的时候,这时候并不是真正把实参或者是返回值本身传过去
或者是返回来,此时会对实参或者返回值进行一份临时的对象拷贝,效率是比较低,尤其
是当对象非常大的时候
6.3引与指针对比:
6.4 引用涉及到的权限问题
1)权限不能放大
2)权限可以平移
3)权限可以缩小
7:内联函数
7.1定义
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
注意对内联函数的展开与一个对引入一个头文件的展开所指向功能是不一样的
对引入头文件的展开指的是:把头文件所涉及到的内容进行拷贝到指定的文件;而内联函数的展开
不是这样的
对Add()函数的调用并不会进行函数栈帧的建立与销毁,此时查看汇编代码即可知道
发现此时并没有 call 这条指令,说明没有为此函数调用建立栈帧。
在查看汇编指令前需要对VS编译器进行一些配置(2个地方)
以上配置更改好,即可进入调试模式,鼠标右击,点击转到反汇编即可看到汇编指令
7.2 内联函数特点
1)使用内联函数是以空间换时间,注意这个空间不是指的内存,编译器在编译链接的时候,目标
文件会变大,使用内联函数最大的好处之一就是避免了函数栈帧的创建与销毁
2)内联函数是否展开只能由编译器说的算;一般对于那种非递归调用的并且代码行数极少的(一
般10行)编译器会进行展开
3)一般不要把内联函数的声明和定义分开,会出现链接错误
看看以下代码为什么出现链接错误(注释:有3个文件,Func.h, Func.cpp, test.cpp)
Func.h 文件内容
Func.cpp 文件内容
test.cpp 文件内容
分析:
分析:
通过这个例子可以理解为:内联函数是不会出现在链接过程中的符号里面的
一般出现链接错误多是 函数只有声明没有定义造成的;内联函数在编译,链接的过程中不会生成对应的指令
C++ 里的内联函数其实是为了补足C 里面宏的缺点。
宏的缺点:
1)不方便调试
2)没有类型检查
3)对于操作符优先级问题,可能发生副作用
4)当宏定义的代码比较长的时候,展开会造成代码的膨胀
5) 可读性,可维护性差
8:auto关键字(C++11)
8.1 使用
作为一 个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导(换言之
就是可以自动识别变量的类型)
使用场景:
1)对引用和指针:
2) 同一行声明多个变量当在同一行声明多个变量时,必须保证所有变量都是同一种类型3) 使用auto 对应的变量必须进行初始化
可能有的人会说,为啥不使用 typedef 进行类型的重命名呢???
看看以下代码是否可以编译通过???
typedef char* pstring;
int main()
{
const pstring p1;
const pstring* p2;// 编译成功还是失败?
return 0;
}
第一个失败,第二个成功。
分析:
8.2 注意事项
auto 不能使用的场景:
1)不能出现在形参里面
2)不能用于数组
9:基于范围的for循环(C++11)
9.1 书写
for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被
迭代的范围。
9.2 范围for 的引用
使用范围for 的条件:
1)要有结束和起始 的范围
2)迭代的对象要实现++和==的操作
10:指针空值:nullptr(C++)
还是以问题直接进入话题吧
在 C++98 中,字面常量 0既可以是一个整形数字,也可以是无类型的 指针(void*)常量但是编译器 默认情况下将其看成是一个整形常量 ,所以此时就会调用第一个函数;如果要将其按照指针方式来使用,必须对其进行强转(void *)0。也可以使用nullptr
1)在 C++11 中, sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。2)nullptr 不需要引入对应的头文件,直接使用即可
结语:
以上就是我要share的内容。对于刚刚学习C++的小白而言自然会有点摸不到头脑,有时也会觉得为啥要这样写呢,有什么可以应用的场景吗,别急,这只是为后面的迭代器,类和对象……打基础呢,等到后期自然就觉得非常爽了,各位大佬要是觉得还不错的话,支持一下呗!