C++初阶:入门基础
1.C++的第一个程序
C++兼容C语言绝大多数的语法,所以C语言实现的hello world依旧可以运行,C++中需要把定义文件代码后缀改为.cpp
//C++兼容C语言#include<stdio.h>intmain(){printf("hello world\n");return0;}当然,C++也有一套自己的输入输出,有自己的语法,严格来说C++版本的hello world是这样写的:
//C++#include<iostream>usingnamespacestd;intmain(){cout<<"hello world"<<endl;return0;}那这里的iostream是什么呢?using namespace std又是什么呢?下面我们一一讲解。
2.命名空间
2.1 namespace的价值
在C和C++中,变量,函数,和类都是大量存在的,这些变量,函数,类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
C语言项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入namespace就是为了更好的解决这样的问题。
#include<stdio.h>#include<stdlib>intran=10;intmain(){// 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”printf("%d\n",rand);return0;}2.2 namespace的定义
1.定义命名空间,需要用到namespace关键字,后面跟着命名空间的名字,这个名字是要自己定义的,名字后面接上一对{}即可,{}中即为命名空间的成员,命名空间的成员可以是变量,函数,类型等。
2.namespace本质是定义出一个域,这个域跟全局域,局部域各自独立,谁都不影响谁,也就是说,我在全局域中定义一个rand函数,在自己定义的namespace域中定义一个rand函数,这两个函数是独立的,谁都不影响谁。
3.C++中域有函数全局域,局部域,命名空间域,类域。域影响的是编译时语法查找一个变量/函数/类型的出处的逻辑所以有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不会影响变量的生命周期。
4.namespace只能定义在全局,也可以嵌套定义。
5.项目工程中多文件定义同名的namespace会认为是一个namespace,不会冲突。
6.C++标准库都放在一个叫std的命名空间中。
下面举一些例子:
#include<iostream>usingnamespacestd;//定义一个命名空间域namespacehbw{//定义变量intrand=10;//定义函数intAdd(inta,intb){returna+b;}//定义类型structhan{inta;structhan*next;}intmain(){//这里默认访问的是全局的rand函数指针(标准库里有一个rand函数)printf("%p\n",rand);//这里指定hbw命名空间的randptintf("%d\n",hbw::rand);return0;}2.3命名空间的使用
我们先看一下下面这个程序会不会报错.
#include<iostream>usingnamespacestd;namespacehbw{inta=1;intb=0;}intmain(){printf("%d\n",a);return0;}上面这个程序是有问题的,我们会疑惑,上面我定义了a变量,为什么还会报错?
原因是编译查找一个变量的声明/定义时,默认只会在局部域或者在全局域查找,不会到命名空间里面去查找。
所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:
1.指定命名空间访问,项⽬中推荐这种⽅式。
2.using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
3.展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。
3.C++输入和输出
1.是input output stream的缩写,是标准的输入,输出流库,定义了标准的输入,输出对象。
2.std::cin是istream类的对象,它主要面向窄字符的标准输入流。
3.std::cout是ostream类的对象,它主要面向窄字符的标准输出流。
4.std::endl是一个函数,流插入输出时,相当于插入一个换行加刷新缓冲区。
5.<<是流插入运算符,>>是流提取运算符。
6.cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们。如,using namespace std这样去使用。
4.缺省参数
1.缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把缺省参数也叫默认参数)。
2.全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
3.带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
4.函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
#include<iostream>usingnamespacestd;voidfunc(inta=0){cout<<a<<endl;}intmain(){func();//没有传参时,使用参数的默认值,就是0func(1);//传参时,使用指定的实参,就是1return0;}5.函数重载
C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。
#include<iostream>usingnamespacestd;//1.参数类型不同intAdd(intleft,intright){cout<<"int Add(int left, int right)"<<endl;returnleft+right;}doubleAdd(doubleleft,doubleright){cout<<"double Add(double left, double right)"<<endl;returnleft+right;}//2.参数个数不同voidf(){cout<<"f()"<<endl;}voidf(inta){cout<<"f(int a)"<<endl;}//3.参数类型顺序不同voidf(inta,charb){cout<<"f(int a,char b)"<<endl;}voidf(charb,inta){cout<<"f(char b, int a)"<<endl;}下面两个函数也构成重载,但是调用时会报错,因为存在歧义,编译器不知道调用谁:
voidf1(){cout<<"f1()"<<endl;}voidf1(inta=1){cout<<"f1(int a = 1)"<<endl;}//会报错,编译器不知到要调用谁6.引用
6.1引用的概念和定义
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。
类型& 引用别名 = 引用对象;
C++中为了避免引⼊太多的运算符,会复⽤C语⾔的⼀些符号,⽐如前⾯的<<和>>,这⾥引⽤也和取地址使⽤了同⼀个符号&,⼤家注意使⽤⽅法⻆度区分就可以。
#include<iostream>usingnamespacestd;intmain(){inta=0;// 引⽤:b和c是a的别名int&b=a;int&c=a;// 也可以给别名b取别名,d相当于还是a的别名int&d=b;++d;// 这⾥取地址我们看到是⼀样的cout<<&a<<endl;cout<<&b<<endl;cout<<&c<<endl;cout<<&d<<endl;return0;}6.2引用的特性
1.引用在定义时必须初始化。
2.一个变量可以有多个引用。
3.引用一旦引用一个实体,就再不能引用其他实体。
#include<iostream>usingnamespacestd;intmain(){inta=10;// 编译报错:“ra”: 必须初始化引⽤//int& ra;int&b=a;intc=20;// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,// 这⾥是⼀个赋值b=c;cout<<&a<<endl;cout<<&b<<endl;cout<<&c<<endl;return0;}6.3const引用
1.可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。
2.不需要注意的是类似 int& rb = a3; double d = 12.34; int& rd = d; 这样⼀些场景下a3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以。
3.所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
intmain(){constinta=10;// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”// 这⾥的引⽤是对a访问权限的放⼤//int& ra = a;// 这样才可以constint&ra=a;// 编译报错:error C3892: “ra”: 不能给常量赋值//ra++;// 这⾥的引⽤是对b访问权限的缩⼩intb=20;constint&rb=b;// 编译报错:error C3892: “rb”: 不能给常量赋值//rb++;return0;}#include<iostream>usingnamespacestd;intmain(){inta=10;constint&ra=30;// 编译报错: “初始化”: ⽆法从“int”转换为“int &”// int& rb = a * 3;constint&rb=a*3;doubled=12.34;// 编译报错:“初始化”: ⽆法从“double”转换为“int &”// int& rd = d;constint&rd=d;return0;}6.4指针和引用的关系
C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。
1.语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
2.引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
3.引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
4.引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
5.sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)。
6.指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
7.inline
1.⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就需要建⽴栈帧了,就可以提⾼效率。
2.inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
3.C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调试,C++设计了inline⽬的就是替代C的宏函数。
4.inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
8.nullptr
1.C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的
f(int*)函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。f((void*)NULL);调⽤会报错。
2.C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型。
