C++入门基础知识
C++关键字(C++98)
C++总计63个关键字,C语言32个关键字,用到了再详细学习。
命名空间
为什么要引入命名空间?用来解决名字冲突,使得代码模块化、可读性更高
什么是命名空间?
命名空间就是给代码加的「专属文件夹」,用来避免名字冲突,同时区分不同模块 / 库的代码,是 C++ 组织代码的核心机制。
命名空间的作用是什么?
解决名字冲突,代码模块化、可读性更高;
命名空间的定义?
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
// bit是命名空间的名字,一般开发中是用项目名字做命名空间名。namespace bit{// 命名空间中可以定义变量/函数/类型intrand=10;intAdd(intleft,intright){returnleft+right;}structNode{structNode*next;intval;};}//2. 命名空间可以嵌套//test.cppnamespaceN1{inta;intb;intAdd(intleft,intright){returnleft+right;}namespaceN2{intc;intd;intSub(intleft,intright){returnleft-right;}}}//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。// ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个// test.hnamespaceN1{intMul(intleft,intright){returnleft*right;}}注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中命名空间怎么使用?
核心符号::: 是作用域解析符,用来指定命名空间成员;
三种使用方法:1.直接使用::号,如:命名空间名::变量名(在命名空间中存在的)2.在使用前使用:using 命名空间名::变量名,将变量解析出来就可以直接使用了3.将命名空间中的内容全部解析:using namespace 命名空间名;注意:大型项目不要直接用 using namespace,避免冲突;优先用 命名空间::解析空间中的成员
#include<iostream>using namespace std;//c++库中所以东西都是放在std命名空间中的intmain(){cout<<" hello world "<<endl;return0;}法二: #include<iostream>//using namespace std;//c++库中所以东西都是放在std命名空间中的intmain(){std::cout<<" hello world "<<std::endl;return0;}//<<是流插入运算符,>>是流提取运算符缺省参数
什么是缺省参数?
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。所以简称备胎参数。如下图运行结果:
缺省参数的分类?
全缺省参数:
注意:传参调用时必须从左往右依次传参调用,如下图中的操作会报错。
半缺省参数:
注意:缺省参数必须从左往右依次连续缺省,否则会报如下错误。
函数重载
什么是函数重载?
函数重载是在同一作用域中声明几个功能类似的同名函数,且函数参数不同,其中参数不同又包含:参数的类型不同,参数个数不同和参数类型的顺序不同【如void Func1(int i, char ch); 和void Func(char ch, int i ); 构成函数重载】。对有没有返回值没有影响,所以只有返回值不同时,不能构成函数重载。
c++是如何支持函数重载的?为什么c语言不支持函数重载?
C++ 支持函数重载而 C 语言不支持,核心原因在于编译链接阶段的符号处理机制存在本质区别:
程序从源码到可执行文件需要经过4个阶段:list.h list.c test.c
| 预处理 生成:list.i test.i | 头文件的展开/宏替换/条件编译/去掉注释 |
|---|---|
| 编译 生成:list.s test.s | 检查语法,生成汇编代码 |
| 汇编 生成:list.o test.o | 汇编代码转成二进制的机器码 |
| 链接 | 将两个目标文件链接到一起 |
编译时,C 编译器在符号表生成的函数名就是原来的函数名,同一函数链接时会发生冲突。所以c语言中无法支持重载.
为支持重载,C++ 编译器在编译时执行函数名修饰规则:将函数名、参数类型、个数、顺序等信息编码为唯一的内部符号名。如 Linux g++ 下,void add(int)会被修饰为_Z3addi,void add(double)会被修饰为_Z3addd,修饰后的符号名完全不同,链接器可精准区分。【同时:不同编译器规则不同:Linux g++ 采用_Z+函数长度+函数名+类型首字母】
引用
什么是引用?
引用是给已经存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
引用的定义
类型& 引用变量名(对象名) = 引用实体;
inta=1;int&ra=a;//ra是a的引用,引用就是给已经存在的变量取一个别名,此时a再取一个名称raint&b=a;int&c=b;注意:引用类型必须和引用实体是同种类型
引用特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体,如果引用了,其地址不变,若引用的变量有数值,其原来的值会发生改变。
地址变量,值变为了2。
常引用
一:constinta=10;//int& ra = a; // 该语句编译时会出错,a为常量,就是说a是只读的;b的类型是int,是可读可写的,所以不行。constint&ra=a;// int& b = 10; // 该语句编译时会出错,b为常量constint&b=10;二:intc=1;int&d=c;constint&e=c;//ok的,c是可读可写的,e只可读总结:引用取别名时,变量访问的权限可以缩小,不能放大//指针:constint*cp1=&a;//int* p1 = cp1;//不能,属于权限的放大int*p2=&c;constint*cp2=p2;//可以,属于权限的缩小三:inti=1;doubledi=i;//隐式类型转换//double& ri = i;//不可行//floatt& rf =i;//不可行,但是加const就可以,如下图注意:在变量之间,没有权限缩小和放大的关系,只有引用和指针才有。在实际静态/动态库中只会提供少量函数给程序用,这些函数+extern “C”,但其实一般提供tcmalloc和tcfree给外面用
引用有哪些使用场景呢?
1.做参数
作用:做输出型参数;提高运行效率
voidSwap_c(int*p1,int*p2){inttmp=*p1;*p1=*p2;*p2=tmp;}//c++中使用引用做参数voidSwap_cpp(int&r1,int&r2){//r1对应a的别名,r2对应b的别名inttmp=r1;r1=r2;r2=tmp;}intmain(){inta=1,b=2;Swap_c(&a,&b);Swap_cpp(a,b);return0;}2.做返回值
作用:少创建一个临时对象,提高程序效率
//传值返回:把函数里的变量拷贝一份给调用者,这份拷贝就是临时变量,用完就销毁。intCount1(){staticintn=0;n++;returnn;//会产生一个临时变量,具有常性,会多产生空间}//传引用返回:把变量n的别名给调用者,不用拷贝,没有临时变量,不会产生空间int&Count2(){staticintn=0;n++;returnn;//直接返回n}intmain(){//int& r1 = Count1();//编译不通过constint&r1=Count1();int&r2=Count2();return0;}没有static时
int&Add(inta,intb){intc=a+b;returnc;}intmain(){constint&ret=Add(1,2);Add(3,4);cout<<"Add(1, 2) = "<<ret<<endl;return0;}//结果如下:运行结果有误原因是:c是一个局部变量,出了Add函数,c就不在栈帧里了,解决办法就是在c前加static修饰解决问题之后:
static修饰的变量在函数调用时值执行一次,所以运行结果为3不变。
总结:一个函数要使用引用返回时,返回变量出了函数的作用域还存在,就可以使用引用返回,否则,不可以使用。
内联函数
为什么引入内联函数?
当我们频繁调用一个函数时,需要创建栈帧,是有消耗的,因此,为解决这个问题:1,c语言中使用宏函数,但是其可读性比较差,2,c++使用内联函数,编译时C++编译器会在调用内联函数的地方展开,目的是以空间换时间,但是函数没有地址。
什么是内联函数?
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,提升程序运行的效率。
//内联函数inlinevoidSwap(int&x1,int&x2){inttmp=x1;x1=x2;x2=tmp;}intmain(){inta=1,b=2;Swap(a,b);return0;}如下图:在反汇编中,已经在调用函数的地方将函数展开
使用内联函数不展开情况:
//内联函数inlinevoidSwap(int&x1,int&x2){inttmp=x1;x1=x2;x2=tmp;//当内联函数中代码较多时,就不展开了tmp=x1;x1=x2;x2=tmp;tmp=x1;x1=x2;x2=tmp;tmp=x1;x1=x2;x2=tmp;tmp=x1;x1=x2;x2=tmp;tmp=x1;x1=x2;x2=tmp;}intmain(){inta=1,b=2;Swap(a,b);return0;}总结:一般内联适用于小函数,其次递归或者比较长的都不适合内联
内联函数的特性:
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
一般内联适用于小函数,其次递归或者比较长的都不适合内联
inline不能声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
auto关键字(C++11)
auto的作用:推导出变量的数据类型,由编译器在编译时期推导而得。
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。
注意:
1.用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
2.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
3.auto不能做参数,数组也不能用auto
范围for(c++11)
特点:写起来比较简洁
使用案例:
//范围forintmain(){intarr[]={1,1,1,1,1,1};//要求将数据中的每一个值乘2,再打印一遍//c++11for(auto e:arr){//第二空只能是数组名,不能是数组指针e*=2;//并不改变数组中的值}for(auto e:arr){cout<<e<<" ";}cout<<endl;//改变数组中的值,使用引用for(auto&e:arr){e*=2;}for(auto e:arr){cout<<e<<" ";}cout<<endl;}指针空值nullptr(C++11)
使用案例:
//指针空值nullptr(C++11)voidfun(intn){cout<<"整形"<<endl;}voidfun(int*p){cout<<"整形指针"<<endl;}intmain(){//cint*p1=NULL;//c++int*p2=nullptr;fun(0);fun(NULL);//预处理后fun(0)fun(nullptr);}