目录
前言:什么是C++
- C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(objectoriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。
- 1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。
- 因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
1.第一个关键字:namespace
(1)namespace的用处
- 我们知道C语言有32个关键字,而c++则有63个关键字,我们不用一开始就把所有的关键字的用法搞清楚,首先我们先看看常见的关键字。
- 在编写c++程序时我们经常可以看到这一条语句
using namespace std;
- 这一条语句中就包含了c++常用的一个关键字namespace,有许多人并没有仔细了解过这个关键字是什么含义,老师说写程序的时候加上就可以了
- 其实这个关键字很有用处,它是c++为了避免命名冲突设计出来的
- 怎么避免命名冲突呢,可以看看下面的例子
- 我们都知道c语言中scanf和printf是库函数,我们定义变量如果使用他们作为变量名的,会导致printf和scanf使用不了,但是有了namespace就不一样了
- namespace会声明一个命名空间,一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
- -可以看到,在main函数中定义printf会让printf无法使用,但是在命名空间中定义就不同了,这是因为我们指定了打印的printf属于dhdw这个域,而不是库函数的printf
- 这就有效的防止了命名污染
注意:
- 命名空间中可以定义变量,也可以定义函数
- 命名空间内部支持嵌套命名空间
- 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
(2)如果使用命名空间中的变量和函数
- 一共有三种方式:
- 指定命名空间,就是在成员变量前面加上命名空间的名字加域作用限定符::
- 将命名空间整个展开,相当于命名空间的东西到了全局
- 对部分常用的命名空间里的函数或成员展开
- 这个三种方法各有优缺点
- 麻烦,每次使用都要指定,但也是最规范的使用方式
- 使用方便,但是如果我们自己定义的命名空间中的成员与库中冲突了就一朝回到解放前,在规范的工程项目中不推荐使用
- 对于前两种方式的折中方案,项目中也经常使用
- 下面是三种使用方式的案例
- 看到这里,我们就能理解前面的语句
using namespace std
的用处了 - C++库为了防止命名冲突,将自己库中的东西都定义在一个std的命名空间中,这条语句相当于将std这个命名空间展开,方便我们使用里面的函数或变量,比如cout,cin等
- 而std这个命名空间在库<(iostream)>中,所以我们编写C++程序时一般都是先引用库iostream加上using namespace std
2.C++的输入和输出函数
- 任何语言都要从输出Hello World开始,来看看C++怎么输出吧
#include<iostream>
using namespace std;
int main()
{
cout << "Hello World!"<<endl;
return 0;
}
- 好了你已经会输出Hello World了,你已经是C++编程高手了(手动滑稽)
- cin和scanf类似,都是从控制台上录入数据
- cout和printf类似,都是输出数据到控制台
- 不同的是,cin和cout能自动识别类型,我们可以摆脱写%d,%f,%c等格式化字符的折磨了
- 使用时注意:
cout后面是<<,cout<<
cin后面是>>,cin>>
3.缺省参数
- 这也是C++的新玩法,我们在定义函数的时候能给它一个默认的参数,如果我们不传参,调用它时它会使用这个默认参数
- 上述例子中的是全缺省参数,就是所有的参数都给缺省值,我们还可以半缺省参数,不是只给一半的变量缺省值,是只给一部分变量缺省值
注意:
- 缺省时必须是从右往左缺省,并且是连续的
- 缺省参数不能在函数声明和定义中同时出现
- 没有从右往左缺省:
- 没有连续缺省:
- 在声明和定义中均写缺省参数:
4.函数重载
- 我们知道C语言中不能定义两个同名函数,但是c++中支持定义两个同名函数,这种方式我们叫做函数重载
函数重载的条件:
- 参数不同(参数类型不同,或者参数个数不同)
- 返回类型同和不同根函数重载无关,即如果参数都相同,返回类型不同也不构成函数重载。参数不同,返回类型同与不同都构成函数重载
- 参数类型不同构成重载:
- 参数个数不同构成重载:
- 参数类型相同,返回类型不同不支持重载
- 还有一种很坑的情况!
- 上面两个Add构成重载,但是其中有一个有缺省参数,所以Add(a,b)编译不知道改调用哪个函数,会报错
5.引用
(1)初识引用
-
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
-
比如我们熟悉的齐天大圣孙悟空,又称斗战圣佛。
-
如何使用也十分的简单,就是在变量名后加符号&,如下图,b是a的引用,也就是说b是a的别名,编译器不会给b开空间,b和a使用的是同一块空间
-
那么引用有什么用呢,先来看一个简单的例子理解一下引用
-
原本我们需要使用指针才能够完成两个变量的交换,现在我们使用引用同样可以完成交换,因为我们传的引用其实就是a,b两个变量,不会在传参的时候创建出两个临时变量交换,所以最终也能完成交换
(2)引用的类型
- 这个时候,你可能会有疑问,引用的类型是是什么,如果是整型,它的类型是int&吗?
- 答案是NO,引用的类型和被他引用的类型是一致的,int类型变量的引用就是int
- 但是int&和int会构成函数重载,但使用时会出错
- 可以看到swap确实被重载了,但是使用的时候编译器会报错,因为a,b两个变量的类型都是int,而我们知道引用的类型和它引用的变量一致,所以两个swap函数里面的形参类型都是int,那么两个函数的形参都能与a.b匹配上,所以编译器会报错
(3)引用的特性
- 引用在定义时必须初始化
- 我们知道指针定义时可以不给初值,但是引用不行,编译器会报错,由此我们可以知道,有指向空的指针,没有指向空的引用。
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
- b=x,这条语句的意思是将x的值赋给b,而不是让b变成x的引用
(4)常引用
- const对象被引用时,也需要加上const,否则属于权限的放大,编译器会报错
- 非const对象被引用时,引用加上const是可以的,属于权限的缩小
3.引用时的隐式类型转换
- 很多人会觉得编译器报错是因为c的类型double,a的类型是int,不匹配才报错的,其实不是
- 我们在c前面加上const之后就不报错了,原因是a和c的类型不匹配,要发生隐式类型转换,编译器会创建一个double类型的临时变量,数值和a相同,再把这个临时变量赋给c
- 所以c其实是临时变量的引用,而临时变量具有常性,必须要加const
(5)引用的使用场景
- 做参数
void swap(int& r1, int& r2)
{
int tmp = r1;
r1 = r2;
r2 = tmp;
}
- 可以实现和指针类似的效果
- 做返回值
int& Add(int left,int right)
{
static int sum = left + right;
return sum;
}
-
我们传引用返回的目的是为了避免创建临时变量了,提高效率
-
注意:
-
返回的变量不能是临时变量,临时变量出了函数作用域会被销毁,而引用还指向临时变量原来的空间,会很危险
-
可以看到,我们第二次调用Add函数的时候,并没有用ret接收返回值,但是ret的值还是改变了
-
就是因为第一次返回时ret指向了临时变量的空间,两次调用的时候函数指向同一块空间,所以再次调用函数时,临时变量的值被改变了,而ret指向临时变量的空间,所以ret的值也会被改变
-
但如果在第二次调用Add函数之前调用了其它的函数,C所在的空间可能会被调用,ret的值就会变为随机值,在第二次调用Add函数之后调用其它函数也一样
-
所以实际中,如果出了函数作用域,返回的变量就不存在了,就不能传引用返回
(6)引用与指针的关系
- 引用概念上定义一个变量的别名,指针存储一个变量的地址
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体 - 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
6.inline(内联)函数
(1)概念
- 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率
- 这时候你可能会有疑惑,C语言不是有宏函数么,也是一样展开避免函数压栈消耗,怎么还整个inline函数
- 这是因为宏函数有好几个缺点:
- 不支持调试
- 语法复杂,易出错
- 没有类型安全的检查
(2)特性
- inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
7.auto关键字
(1)概念
- auto一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
- 通俗一点,auto能推导出已经初始化的变量的类型
- 如图,auto能自动推导出b和d的类型分别为int,char
- 注意:
- 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。
- 因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
(2)注意事项
- 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
- 在同一行定义多个变量
- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
3.auto不能作为函数的参数
4.auto不能用于声明数组
8.nullprt指针
- 我们知道,C语言中有NULL指针,它其实是一个宏
#define NULL ((void *)0)
或`#define NULL 0- 可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量
- 无论是哪种方式,我们使用的时候可能会遇到一些问题
- 可以看到,我们传的是NULL,但是调用的是f(int)
- 使用nullptr就可以避免这个问题
- 注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。