一 .C++入门
1.C++关键字
2.命名空间
3.C++输入&输出
4.缺省参数
5.函数重载
6.引用
7.内联函数
8.auto关键字
9.基于范围的for循环
10.指针空值nullptr&nullptr_t
二. 正文
1.C++关键字(C++98)
C++98中的关键字总共用63个,如下表:
在这这做简单介绍,感兴趣的朋友可以参考相关资料作进一步了解。
2.命名空间
由于在编写程序的过程中,很容易出现变量、函数和类重名的现象,这些变量、函数和类都在全局作用域中,因此会导致很多冲突。使用命名空间的目的是:对标识符的名称进行本地化,以避免命名冲突或者名字污染,命名空间的关键字就是:namespace
2.1命名空间的定义
定义命名空间,需要用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中的即为命名空间的内容,命名空间的定义方法有三种,下面一一列出:
a.普通的命名空间:
1 namespace N1 2 { 3 //命名空间中的内容,既可以定义变量,也可以定义函数 4 5 int a; 6 int Add(int left, int right) 7 { 8 return left + right; 9 } 10 }
b.命名空间可以嵌套
1 namespace N2 2 { 3 int a; 4 int b; 5 int Add(int left, int right) 6 { 7 return left + right; 8 } 9 namespace N3 10 { 11 int c; 12 int d; 13 int Sub(int left, int right) 14 { 15 return left - right;16 } 17 } 18 }
c.同一个工程中允许出现多个命名相同的命名空间,编译器最后会合成同一个命名空间中
1 namespace N1 2 { 3 int Mul(int left, int right) 4 { 5 return left * right;6 } 7 }
2.2命名空间的使用
命名空间中的成员的三种使用方法:
注意:"::"称为作用域限定符
1.加命名空间名称及作用域限定符
1 int main() 2 { 3 printf("%d ",N1:a); 4 return 0; 5 }
2.使用using将命名空间中的成员引入
1 using N1::b; 2 int main() 3 { 4 printf("%d ",b); 5 return 0; 6 }
3.使用using namespace 命名空间名称引入
1 using namespace N1 2 int main() 3 { 4 printf("%d ",b); 5 printf("%d ",a); 6 return 0; 7 }
注意:第一种方法与第二种方法只能将作用域限定符后的变量从它所在的命名空间引入,而第三种方法将命名空间中的所有的成员都引入了。
3.C++输入&输出
先用一段程序来直观的说说C++中的输入输出的使用方法:
1 #include<iostream> 2 using namespace std; 3 int main() 4 { 5 int a=0; 6 cin>>a>>endl; 7 cout<<"hello World!"<<endl; 8 return 0; 9 }
说明:
1.使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含<iostream>头文件,以及std标准命名空间。
2.使用C++输入输出更方便,不需要增加数据格式控制,比如:整形--%d,字符--%c
1 #include<iostream> 2 using namespace std; 3 int main() 4 { 5 int a; 6 char c; 7 double d; 8 cin>>a; 9 cin>>c>>d; 10 cout<<a<<endl; 11 cout<<d<<" "<<c<<endl; //endl在C++中的意思是换行 12 return 0; 13 }
4.缺省参数
4.1缺省参数的概念
缺省参数是申明或者定义函数的时候为函数的参数指定默认值的操作。在调用该函数时,如果没有指定参数就使用该默认值,否则使用指定的实参。
1 #include<iostream> 2 3 void TestFun(int a=0) 4 { 5 cout<<a<<endl; 6 } 7 8 int main() 9 { 10 TestFun(); //没有传参时,使用参数的默认值 11 TestFun(2); //指定了参数时,使用指定的实参的值 12 return 0; 13 }
4.2缺省参数的分类
a.全缺省参数:函数的参数列表中的形参的值都指定了默认值
1 void TestFun(int a=10, double b=0.0, char c=' ') 2 { 3 cout<<a<<endl; 4 cout<<b<<endl; 5 cout<<c<<endl; 6 }
b.半缺省参数:函数的参数列表中的形参的值只有一部分指定了默认值,而且默认值只能从右往左依次给出
1 void TestFun(int a, int b=1, double c=0.0) 2 { 3 cout<<a<<endl; 4 cout<<b<<endl; 5 cout<<c<<endl; 6 }
注意:
1.半缺省参数必须从右往左依次给出
2.缺省参数不能在声明和定义中同时给出(建议在申明中给出)
3.缺省参数必须是常量或者是全局变量
4.C语言不支持
//a.h void TestFun(int a=10); //a.c void TestFun(int a=20) {} //如果申明和定义中同时出现,恰巧两个位置的默认值如上边代码中的一样 //则编译器就无法确定到底该用哪个缺省值。
5.函数重载
5.1函数重载的概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数类型、参数个数、参数次序)必须不相同,常用来处理功能相似但类型不同的问题。
1 int Add(int left, int right) 2 { 3 return left + right; 4 } 5 int Add(double left, double right) 6 { 7 return left + right; 8 } 9 long Add(long left, long right) 10 { 11 return left + right; 12 } 13 14 int main() 15 { 16 Add(12,13); 17 Add(12.12,13.13); 18 Add(10L,20L); 19 reutrn 0; 20 }
注意:函数的返回值类型对于函数是否重载没有影响
5.2名字修饰
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
名字修饰是一种在编译阶段,将函数、变量的名称重新改编的机制,简单来说,就是编译器为了区分各个函数,将各个函数通过某种算法,重新修饰为一个全局唯一的名称。
C语言中的名字修饰规则非常简单,仅仅只是在函数或者变量的名字前面加了下划线。读者朋友可以通过以下代码查看编译后的函数名(即通过只申明不定义,编译时编译器报错时会显示未找到<某某函数>,某某函数就是编译后的新名字。
1 int Add(int left, int right); 2 3 int main() 4 { 5 int a=0; 6 int b=2; 7 Add(a,b); 8 return 0; 9 }
上代码运行时报错:error LNK2019:无法解析的外部符号_Add,该函数在_main函数中被引用。
上述Add函数只给了申明,没有给定义,因此在链接时就会报错,提示:在main函数中引用的Add函数找不到函数体。报错结果中可以看到,C语言知识简单的在函数明前添加下划线。因此当工程中存在相同的函数名的时候,就会产生冲突。读者有兴趣可以自己验证。
由于C++要支持函数重载,命名空间等,使得其的修饰规则比较复杂,不同的编译器的实现方式可能都有差异,同样,用上述代码在VS编译环境下运行,可以看到VS编译器对C++的修饰方式:
error LINK2019:无法解析的外部符号"double cdecl Add(int,int)"(?Add@YAHHH@Z)
如果定义的函数的参数类型是double类型的,则可以看到下面的报错信息:
error LINK2019:无法解析的外部符号"double cdecl Add(int,int)"(?Add@YANNN@Z)
通过上述的报错信息可以看出,编译器实际上在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名字,被修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名的函数要求它的参数列表必须不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在最终的名字中,就可以保证名字在底层的全局唯一性。这就不会导致Link时的多重定义。
5.3 extern "C"
有时候在C++工程中可能需要将某些函数按照C的分格来编译,在函数前面加extern "C",意思是告诉编译器将该函数按照C语言的分格进行编译。
1 extern "C" int Add(int left, int right) 2 { 3 return left + right; 4 } 5 6 int main() 7 { 8 Add(2,3); 9 return 0; 10 }
6.引用
6.1引用概念
引用不是定义一个新变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟新的内存空间,它和它引用的变量公用同一块内存空间。
类型&引用变量(对象名)=引用实体
1 void TestRef() 2 { 3 int a=10; 4 int & ra=a;//定义引用变量 5 printf("%d ",a); 6 printf("%d ",ra); 7 }
注意:引用类型必须和引用实体是同种类型的。
6.2引用特性
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用一个实体,再不能引用其他的实体
1 void TestFun() 2 { 3 int a=10; 4 //int& ra; 此语句编译时会出错 5 int& ra=a; 6 int& rra=a; 7 printf("%p %p %p",&a,&ra,&rra); 8 }
6.3常引用
1 void TestFun() 2 { 3 int a=10; 4 //int& ra=a;编译时出错,因为a为常量 5 const int & ra=a; 6 //int& rb= 10;编译时出错,因为10为常量 7 const int&rb =10; 8 double d=12.34; 9 int& rd=d;//编译时出错,类型不同 10 const int& rd=d; 11 }
6.4使用场景
1.做参数
1 void reverse(int& a, int&b) 2 { 3 int tmp=a; 4 a=b; 5 b=tmp; 6 } 7 //交换两个实参的值
2.做返回值
int& TestFun(int left, int right) { int tmp=left; left=right; right=tmp; }
注意:如果函数返回时,离开函数作用域后,其栈上空间已经返还给操作系统,因此不能用栈上的空间作为引用类型返回。如果用引用类型返回,返回值的使用周期必须不受函数的限制(即生命周期比函数长)。
6.5传值、传引用、传地址效率比较
以值作为参数或者函数的返回值类型,在传参和返回期间,函数不会直接传递实参或者返回值类型,而是传递实参或者返回值的一份临时拷贝,因此用值作为参数或返回值传递效率非常低,尤其是当参数或者返回值的类型非常大的时候。
传地址时不存在拷贝,开辟临时变量的时间开销,所以效率是比较高的。但是传地址的缺点是函数的副作用会改变实参的值,数据不安全。
引用通过代码测试效率和传地址差不多。(有兴趣的读者朋友可以用clock()函数记时,测量传地址和引用的时间,进而判断它们的效率)
6.6引用和指针的区别
在语法概念上引用就是一个别名,没有独立的空间,和其引用实体公用同一块地址空间。但在底层实现上是有空间的,因为引用是按照指针的方式来实现的,读者可以将引用和传地址的函数代码转到汇编了解其汇编代码的实现方式,其实是一样的。
引用和指针的区别:
1.引用在定义是必须初始化(实例化),但指针不用
2.引用被一个实体初始化后不能引用其他的实体,但指针却可以(int& ra=a;<===>int * const p=&a;)
3.没用NULL引用,但有NULL指针
4.在sizeof中的含义不同,引用结果为引用类型大小,但指针的始终是地址空间所占的字节个数(32位平台下占4个字节)
5.引用加加是引用实体加一,但指针加加会让指针跳过一个类型的大小
6.有多级指针,但没有多级引用
7.访问实体不同,引用编译器自己处理,指针需要显示解引用
8.引用比指针更安全。(对空指针解引用会出错)
7.内联函数
7.1概念
以inline修饰的函数叫做内联函数,编译时C++会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序的运行效率。
7.2特性
1.inline是一种以空间换时间的做法,省去调用函数的额外开销。所以代码很长,或者有循环/递归的函数不适宜用作内联函数。
2.inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会自动忽略掉内联。
3.宏的优缺点:优点:增强代码的复用性,提高性能。缺点:不方便调试(预处理期间替换)、没有类型的安全检测、代码的可读性差、可维护性差、容易误用。
4.C++中替代宏的技术:
常量定义换用const、函数定义换用内联函数
8.auto关键字
8.1auto简介:
早期的C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是没有人去使用。C++11中给了全新的含义:auto不再是存储类型的指示符,而是一个全新的类型指示符来指示编译器,auto申明的变量必须右编译器时期推演而得。
1 int TestFun() 2 { 3 return 0; 4 } 5 6 int main() 7 { 8 int a=0; 9 auto b=a; 10 auto c='a'; 11 auto d=TestFun(); 12 cout<<typeid(b).name()<<endl;//typeid().name()的作用是打印比变量的类型 13 cout<<typeid(c).name()<<endl; 14 cout<<typeid(d).name()<<endl; 15 //auto e;无法通过哦编译,使用auto定义变量必须对其进行初始化,这样编译器才能 16 //推演出变量的类型; 17 return 0; 18 }
注意:使用auto关键字定义变量时必须对其初始化,在编译阶段编译器需要根据初始化的表达式来推导auto的实体类型。因此auto并不是一种类型声明,而是类型声明时的占位符,编译器在编译时会将auto替换为变量的实际的类型。
8.2auto的使用细则
1.auto与指针和引用结合起来使用:用auto声明指针类型时,用auto与auto*没有任何区别,但用auto声明引用时必须加上&
2.在同一行声明多个变量时,这些变量必须是相同类型的,否则编译器会报错,因为实际上编译器只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
8.3auto不能推导的场景
1.auto不能作为函数的参数
2.auto不能用来直接声明数组
3.为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
4.auto常用与范围for循环中
5.auto不能定义类的非静态成员变量
6.实例化模版时不能使用auto作为模板参数
9.基于范围的for循环
1 void TestFun() 2 { 3 int array[]={1,2,3,4,5}; 4 for(auto& e:array) 5 e*=2; 6 for(auto e:array) 7 cout<<e<<endl; 8 }
for循环后的括号由冒号":"分为两部分,前面的部分是范围内用于迭代的变量,后边的部分是迭代的范围。与普通循环类似,可以用continue跳出本次循环,break跳出循环。
范围for的使用条件:for循环的迭代范围必须是确定的,对于数组而言,就是数组中的第一个元素和最后一个元素,对于类而言,应该是提供begin和end的方法,begin和end就是for循环的迭代的范围。迭代的对象要实现++或者==的操作。
10.指针空值nullptr&nullptr_t
NULL其实是一个宏,通常被定义为0,因此在C++中有了函数重载之后,对于NULL而言就有了歧义性,为了解决这个问题,C++11中给出了全新的nullptr表示空指针,代表一个空值指针。nullptr是有类型的,其类型是nullptr_t,仅仅可以被隐式转化为指针类型。
注意:在使用nullptr时不需要包含头文件,因为nullptr是C++11作为新的关键字引入的。在C++11中sizeof(nullptr)与sizeof(void *)所占的字节数相同,为了提高代码的健壮性,在后续的表示空指针的地方建议使用nullptr。