【初阶与进阶C++详解】C++入门知识必备
⭐️本篇博客我要给大家分享一下C++入门知识。希望对大家有所帮助。
⭐️ 博主码云gitee链接:码云主页
目录
前言
💎一、C++关键字
💎二、命名空间
🏆1.命名空间定义
🍍作用
🍍介绍::
🍍1. 普通命名空间
🍍2.命名空间的嵌套定义
🍍3.同一个工程中可以有多个相同名称命名空间,编译器最后会合成到同一个命名空间中去
🏆2.命名空间的使用
💎三、C++输入和输出
💎四、缺省函数
🏆1.缺省参数概念
🏆2.缺省参数分类
💎五、函数重载
🏆1.函数重载概念和介绍
🏆2.为什么C语言不支持函数重载,C++支持函数重载,C++是如何支持函数重载
🏆extern"C"
💎六、引用
🏆1.引用的概念和特性
🏆2.常引用
🍍做参数
🍍做返回值
🍍两者比较
🍍引用和指针的区别
💎七、内联函数
🏆1.概念
🏆2.特性
💎八、auto关键字
🏆1.介绍
🏆2.使用方法
🏆3.不能使用场景
💎九、基于范围的for循环
💎十、指针空值--nullptr
💎总结
前言
💎一、C++关键字
🧡C++又新增了31个关键字,一共63个关键字
💎二、命名空间
🧡在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的,一个命名空间就相当于定义了一个新的作用域,命名空间中的所有内容都局限于命名空间中。
🏆1.命名空间定义
🧡定义命名空间,需要使用到namespace关键字,namespace定义的是一个域,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
🍍作用
🧡解决C语言命名冲突,下面就是,输出scanf时候,会优先到局部变量去寻找,不回去头文件中寻找,前面定义scanf不会报错,但后面使用的时候就会报错
int scanf =10;int strlen = 20;scanf ("%d", &scanf);printf("%d\n", scanf);printf("%d\n", strlen);
🍍介绍::
🧡:: 域作用限定符号,指定访问域
定义一个域
namespace bit{ int scanf = 10;}
使用:
int main (){ printf("%d",bit::scanf);}
优先使用bit这一个域中的参数,并且不会与stdio.头文件中的函数发生冲突
🍍1. 普通命名空间
namespace linmanman{int a = 10;int b = 20;int Add(int x, int y){return x + y;}}
🍍2.命名空间的嵌套定义
namespace linmanman{int a = 10;int b = 20; namespace lin { int x = 10; int y = 20; int Add(int x, int y) { return x + y; } }}
🍍3.同一个工程中可以有多个相同名称命名空间,编译器最后会合成到同一个命名空间中去
namespace linmanman{int a = 10;int b = 20;int Add(int x, int y){return x + y;}}namespace linmanman{int x = 10;int y = 20;}
🏆2.命名空间的使用
1.指定命名空间,加命名空间名称及作用域限定符,每个地方都要指定命名空间
int main(){std::cout<<"hello!"<<std::endl; //这里我们可以理解为作用域限定符"::"的使用,让我们在lin这个命名空间中找到了a这个变量return 0;}
2.使用using namespace 命名空间名称,相当于库里面东西都到全局域里面了,但是如果我们库里面的东西和全局域冲突的话就没办法解决了
using namespace std;//关键字using的使用将命名空间展开到全局int main(){cout<<"hello!"<<endl;return 0;}
3.对库里面的东西部分展开,引入使用using将命名空间中成员引入
using namespace std::cout;using namespace std::endl;int main(){cout<<"hello!"<<endl;return 0;}
💎三、C++输入和输出
🧡使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含头文件以及std标准命名空间
🧡使用C++输入输出更方便,不需增加数据格式控制,自动识别类型
🧡cout和cin是函数重载和运算符重载
💎四、缺省函数
🏆1.缺省参数概念
🧡缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
🧡缺省参数在调用的时候更加灵活
void StackInit (struct Stack* ps,int InitCapacity = 4){ ps->a = (int*) malloc(sizeof(int)* InitCapacity) ; ps->size = 0; ps->capacity = InitCapacity;}int main(){ struct Stack stl; //假设我知道栈里面至少要存100个数据StackInit(&st1,100); struct Stack st2; //假设我知道栈里面最多要存10个数据StackInit(&st2,10); struct Stack st3; //假设我丕知旗x果炽服众i以StackInit(&st2); return 0;}
🧡半缺省参数必须从右往左依次来给出,不能间隔着给
🧡缺省参数不能在函数声明和定义中同时出现,所以建议声明的时候给参数//a.hvoid TestFunc(int a = 10);// a.cvoid TestFunc(int a = 20){}
void TestFunc(int a = 0){ cout<<a<<endl;}int main(){ TestFunc(); // 没有传参时,使用参数的默认值 TestFunc(10); // 传参时,使用指定的实参}
🏆2.缺省参数分类
🧡全缺省,全部参数都有默认值
void TestFunc(int a = 10, int b = 20, int c = 30){ cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl;}
🧡半缺省,缺省部分参数 ,但是缺省的参数必须从有往左,并且是连续的,半缺省传值必须至少传一个
void TestFunc(int a, int b = 10, int c = 20){ cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl;}
💎五、函数重载
🏆1.函数重载概念和介绍
🧡C语言不可以定义相同的名字的函数,C++可以定义相同名字的函数,但是要求参数类型不同,或者参数个数不同,但参数个数不一样的时候调用会出错,编译器不知道调用哪个
🧡虽然都是调用Add但是,会分别调用对应的类型
int Add(int left, int right){ return left+right;}double Add(double left, double right){ return left+right;}long Add(long left, long right){ return left+right;}int main(){ Add(10, 20); Add(10.0, 20.0); Add(10L, 20L); return 0;}
🏆2.为什么C语言不支持函数重载,C++支持函数重载,C++是如何支持函数重载
编译连接的过程:
f.h f.cpp main. cpp
🧡1、预处理--头文件展开+宏替换+去掉注释+条件编译f.i main.i
🧡2、编译--检查语法,生成汇编代码f.s main.s
🧡3、汇编―--把汇编代码转成二进制机器码f.o main.o
🧡4、链接--链接到一起生成可执行程序a.out
🧡编译后链接前,a.out的目标文件中没有Add的函数地址,因为Add是在头文件中声明的,而声明是没有创建函数栈帧,所以声明是没有地址的,但是编译器还是让程序过了。
#include using namespace std;int add(int a, int b);double add(double a, double b);
🧡之后链接的时候将上述文件链接到一起,同时到其他文件中取找到Add函数的地址,同时每一个目标文件都会生成一个符号表,其中f.o文件的符号表中有Add函数的地址,找到函数地址的时候将函数地址填入之前编译部分跳过的部分(call),如果没有找到就会报链接错误。
🧡C++为了区分第二个相同的函数名,会用函数名修饰规则支持函数重载,那么第一个函数会变成_Z3addii(地址),第二个函数会变成_Z3adddd(地址),两个函数地址不一样。函数修饰【_Z+函数长度+函数名+类型首字母】
🧡但是C语言的函数地址是直接拿函数名作为函数,没有前缀后缀 ,没有函数名修饰规则
🧡C++函数名修饰规则会把参数首字母带入,所以参数不同函数名就不同
🧡不同编译器下的修饰规则是不一样的
🏆extern"C"
🧡有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。
extern "C" int Add(int left, int right);int main(){ Add(1,2); return 0;}
🧡但是这个函数不能重载
🧡C++和C可以互相调用,但是java不行
💎六、引用
🏆1.引用的概念和特性
🧡引用不是新定义一个变量,而是给已存在变量取了一个别名,它和它引用的变量共用同一块内存空间,引用类型必须和引用实体是同种类型的
🧡引用在定义时必须初始化
🧡一个变量可以有多个引用
🧡引用一旦引用一个实体,再不能引用其他实体
类型& 引用变量名(对象名) = 引用实体;
void TestRef(){ int a = 10; int& ra = a;//<====定义引用类型 printf("%p\n", &a); printf("%p\n", &ra);}
🏆2.常引用
void TestConstRef(){const int a = 10;//int& ra = a; // a为常量,加了const不能修改,那自己和别名都不可以修改,此行为属于权限放大//要变成cosnt int& ra = a;int b = 10;int& rb = b;const int& crb = b;//此行为可以使用,由可以改变变成不可以改变,属于权限缩小int c = 10;double b = 1.1;b = c;//隐式类型转换,中间会产生临时变量,类型为couble类型const int& b = 10;double d = 12.34;//int& rd = d; // 权限放大,是可读可写的const int& rd = d;//中间产生临时变量,rd为临时变量的·别名,临时变量具有常性,不能改变,所以加const就不会报错了}
🍍做参数
void Swap(int& left, int& right){ int temp = left; left = right; right = temp;}
🍍做返回值
🧡传值返回,ret引用的不是C,是一个临时变量,临时变量具有常性,所以要加const
int Add(int a,int b){ int c = a + b; return c;}int main(){ const int& ret = Add(1,2) ; return 0;}
🧡传引用返回,返回的是对象C的引用
🧡下面打印输出的是7,函数返回的是C的别名,而C的别名是ret所以就返回
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;}
🧡下面打印的是随机值,首先我们直到栈是从高地址向低地址开辟空间,堆是从低地址向高地址开辟空间的,那么栈开辟的空间使用完后就会销毁,那么printf又重新使用了之前Add的空间,所以改变了这里面的值
int& Add(int a, int b){ int c = a + b; return c;}int main(){ int& ret = Add(1, 2); Add(3, 4); printf("hello!"); cout << "Add(1, 2) is :"<< ret <<endl; return 0;}
🧡此时函数执行完后,输出ret的值是3,因为static 存放的变量是在静态区中,就算函数销毁了也不会改变C的值,ret是静态区当中的别名,加const不会改变C的生命名周期和存储周期,只是不能修改而已。
char* p = "hello"
上面p还是一个指针变量,只有hello是存在常量区,也就是 *p 是存在常量区间
int& Add(int a, int b){ static int c = a + b; return c;}int main(){ int& ret = Add(1, 2); Add(3, 4); printf("hello!"); cout << "Add(1, 2) is :"<< ret <<endl; return 0;}
🧡传值返回是返回拷贝,传引用返回返回的是别名(临时变量),如果是以下代码,是引用,返回别名,但是函数销毁了,返回就是随机值
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;}
🍍两者比较
🧡传值返回会产生拷贝,传引用传参和传引用作返回值不会拷贝,避免深拷贝,可以提高效率
🍍引用和指针的区别
int main(){ int a = 10; //在语法上,这里给a这块空间取了一个别名,没有新开空间 int& ra = a; ra = 20; //在语法上,这里定义个pa指针变量,开了4个字节,存储a的地址 int* pa = &a; *pa = 20; return 0;}
🧡引用和指针的不同点:
1.从语法上来说,引用定义一个变量的别名,指针存储一个变量的地址,
2.引用在定义时必须初始化,指针没有要求,int& ra;(×) int* p;(√)
3.引用在初始化时引用一个实体后,就不能在引用其他实体,而指针可以在任何事之后指向任何一个同类型的实体
4.没有NULL引用,但有NULL指针
5.在sizeof中含义不同,引用结果为引用类型大小,但指针始终是地址空间所占字节个数(有占4个字节和8个字节)
6.引用自加及引用实体增加1,指针自加及指针向后便宜一个类型的大小
7.有多级指针,没有多级引用
8.访问实体方式不同,指针需要显示解引用,引用编译器自己处理
9.引用比指针使用更加安全
int* p = NULL;int*& rp = p;
🧡rp类型是int*, 是p的引用
💎七、内联函数
🏆1.概念
🧡C语言为了避免小函数建立栈帧,提供宏函数,宏函数在预处理阶段展开。
优点:提高性能,增强代码复用性
缺点:不支持调试,宏函数语法复杂,容易出错,没有类安全检查
🧡以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
🧡release版本里面,没有call来开辟函数栈帧
🧡在debug版本,按照以下配置,也可以将debug消除call
inline int Add(int left,int right){ return left + right;}int main(){ int ret = 0; ret = Add(1,2); return 0;}
🧡假设上面的函数有100条指令(一条汇编代码对应一条指令),调用上面的函数10次,函数栈帧总共会有110条指令(函数内有100条指令,十次调用用了10次call),inline调用总共就是1000条指令(每次调用都要展开)
🧡指令变多意味,编译出来可执行程序变大,内存占用越多
🏆2.特性
🧡结论:平调用小函数建议定义成inline
1.内联是空间换时间的做法,省去函数调用的开销,所以代码很长或者有循环和递归的函数不适合使用内联函数
2.如果函数太大或者有递归,编译器优化时会忽略内联
3.inline不建议声明和定义分离,分离会导致链接错误,因为inline被展开了,就没有函数地址了,链接就会找不到
// F.h#include using namespace std;inline void f(int i);// F.cpp#include "F.h"void f(int i){ cout << i << endl;}// main.cpp#include "F.h"int main(){ f(10); return 0;}
🧡一个在头文件用inline,一个在主函数不用inline,会导致链接错误。
🧡C++替换宏的方法:
1.函数定义用内联函数
2.常量定义用const
💎八、auto关键字
🏆1.介绍
🧡auto修饰的变量,是具有自动存储器的局部变量,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&,auto看的是“ = ”之后变量的类型。
int main(){ int a = 10; auto b = a;//类型声明成auto,可根据a的类型自动推导b}
🧡当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
void TestAuto(){ auto a = 1, b = 2; auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同}
🏆2.使用方法
🧡与指针结合使用
int main(){ int x = 10; auto a = &x;//int* auto* b = &x;//int* auto& c = x;//int}
🏆3.不能使用场景
🧡auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导void TestAuto(auto a){}
🧡auto不能直接用来声明数组
void TestAuto(){ int a[] = {1,2,3}; auto b[] = {4,5,6};}
💎九、基于范围的for循环
🧡for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor(){ int array[] = { 1, 2, 3, 4, 5 }; for(auto& e : array) //auto&e是引用array数组中的元素,效果是将元素乘2 e *= 2; for(auto e : array) //自动遍历依次取出array中的元素赋值给e,直到结束 cout << e << " "; return 0;}
🧡注意:可以用continue来结束本次循环,也可以用break来跳出整个循环
🧡条件:
1.for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围
2.迭代的对象要实现++和==的操作
💎十、指针空值--nullptr
🧡C++中最好nullptr,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同,在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的,定义为
#define NULL ((void *)0)
🧡NULL,宏定义为0,所以后时候在使用时,就默认八NULL当作数字0,如果要使用作为指针的话,必须对其进行强转(void *)0
#define NULL 0
💎总结
🧡断断续续的看了一个星期左右才搞懂了这些知识,知识很多很杂,不仅现在学了,以后还要再次体会。