大名鼎鼎的c++实际上是由c语言扩展而来的,它最初是由本贾尼在20世纪80年代开发。目的是支持面向对象编程,同时保持c语言高效和可移植等优点。c++是c的扩展,在一定程度上解决了c语言在特殊场景下的使用局限。
1、命名空间
在详细说明命名空间之前,很有必要谈谈命名空间的由来。在c语言开发中,由不同程序员协作写的项目文件经常会出现一个问题:定义的全局变量或函数可能会产生命名冲突的问题。这种大型项目的开发可能会涉及上万行甚至更多行的代码量,进行逐一命名修改是行不通的。为了解决这一问题,c++引进了一个新的概念:命名空间。命名空间的引入主要是要解决命名冲突问题。
关键字:namespace ,使用namespace关键字将变量进行隔离可解决命名冲突。
1.1、命名空间定义
定义命名空间需要使用namespace关键字,后面跟命名空间的名字,然后需要接一对大括号{},这里需要与结构体的定义进行区分,命名空间的在定义之后没有分号 。大括号中即为命名空间的成员。
namespace my_space
{int memmove = 2;char define = '0';
}
一般情况下,程序对于变量的访问顺序为:局部优先 其次全局。命名空间就像是一个隔离房,只有将房门打开和指定的方式才能访问到空间内的变量。也就是说,要么直接打开房门,要么指定去这个房间寻找。
1.2、命名空间的使用
先说打开房门进行访问的情况,需要在全局加上下面的代码,这样就可以正常访问空间内的变量。 这种方式就是展开命名空间,空间内的变量就会暴露到全局。
using namespce my_space;
其实就是使用指定的方式进行访问,所谓指定方式就是加命名空间名称以及作用域限定符。(::),使用方法如下,这样就会到指定的命名空间中去寻找变量再进行访问。
int main()
{printf("%d\n", my_space::define);return 0;
}
也还可以使用using+命名空间名称::常用变量名 单独展开某个常用变量。
using my_space::memmove;//单独展开一个常用变量
2、输入和输出
C++将标准库的函数定义和实现都放在std命名空间中,所以再使用之前最简洁的方式就是展开命名空间。例如在C语言中学习到的第一个语句hello world使用c++就会用如下的形式:
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;int main()
{cout<<"Hello world!!!"<<endl;return 0;
}
使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含<iostream>头文件以及按照命名空间的使用方式使用std。cout和cin是全局的流对象,endl是特殊的C++符号,表示换行(和c语言中的'\n'是相同的作用),他们都包含在<iostream>头文件中。
<< 是流插入运算符, >> 是流提取运算符。实际上,cout和cin分别是ostream和istream类型的对象。它们在使用时可以自动识别变量的类型,不需要像c语言中输入输出函数那样指定变量的类型。
std命名空间的使用惯例:
1、在日常练习中,建议直接using namespace std即可,这样就很方便。
2、using namespace std展开,标准库就会全部暴露出来,如果我们定义跟库重命名的对象或者函数时,就会存在冲突问题,该问题在日常练习中很少出现,但是在项目开发中由于代码量大就很容易出现命名冲突的问题。所以在项目开发中,就使用std::cout 或者 using std::cout这样的形式。
3、缺省参数
3.1、缺省参数概念
缺省参数也叫默认参数,缺省参数是在函数声明或者定义时为函数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则就是用指定的形参。用通俗的话来说就是:在函数定义或者声明的时候设定了一个参数具有默认值,在调用时有实参传入就使用实参,没有实参就使用默认值。(缺省值必须是常量或者全局变量)
void Func(int a = 0)
{cout<<a<<endl;
}int main()
{Func(); // 没有传参时,使用参数的默认值Func(10); // 传参时,使用指定的实参return 0;
}
3.2、缺省参数的分类
全缺省参数:所有形参都设置了默认值。
void Func(int a = 10, int b = 20, int c = 30){cout<< "a = " << a << endl;cout<< "b = " << b << endl;cout<< "c = " << c << endl;}
半缺省参数(注意:只能从右往左缺省,不能间隔。缺省参数不能在函数声明和定义中同时出现)原因是因为:如果声明与定义的位置同时出现,恰巧两个位置提供的值不同,编译器就无法确定该使用哪个缺省值。
void Func(int a, int b = 10, int c = 20)
{cout<< "a = " << a << endl;cout<< "b = " << b << endl;cout<< "c = " << c << endl;
}
4、函数重载
函数重载:C++允许同一作用域中声明多个功能类似的同名函数,这些同名函数的形参列表不同(参数个数 或 类型顺序)不同。这里要强调的是:函数重载只与参数有关,与返回值类型无关。
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}
// 2、参数个数不同
void f()
{cout << "f()" << endl;
}void f(int a)
{cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}
那为什么C语言不支持函数重载?C++却可以呢?
第一、C语言为什么不可以?这是因为C语言的编译链接规则,多个源文件在编译成目标文件之后,会对源文件中的全局变量喝函数名生成符号表。这个符号表用于后续的链接过程,在链接过程中假设A文件使用了B文件的函数,那么编译器在生成可执行程序之前的链接部分就会查找符号表(全局变量、函数名)进行对应的链接。而C语言的编译规则中对于函数名生成符号表时只与函数名有关,也就是说C语言在编译过程中的名字修饰规则只与函数名字有关,与参数无关。假设存在两个同名函数,在这一过程中就会产生冲突,导致在连接过程中编译器无法确定链接哪一个函数,从而导致连接错误。
那么C++是怎么做到的呢?C++在出现问题的地方进行了改进。C++在生成目标文件中的符号时,修改了函数修饰规则。将函数后面形参的类型也纳入了最终的符号,也就是说函数名一致,形参类型不一致,形参个数不一致,形参类型顺序不一致时产生的函数标签就会不一致。在链接过程中,编译器仍然可以找到对应的调用函数进行链接。
5、引用
引用就是给变量再起了一个别名,它不会创建新的空间,而是在原有空间上增加一个新的标签。象现实生活中:小猫还可以被叫成咪咪,还可以被叫成富贵,这是一个道理。同样地,C++中一块空间可以有多个引用名称。
引用定义类型:类型& 引用变量名(对象名)= 引用实体
void TestRef()
{int a = 10;int& ra = a;//<====定义引用类型printf("%p\n", &a);printf("%p\n", &ra);
}
要注意的一点是:引用类型必须和引用实体是同种类型的
5.1、引用特性
1.引用在定义时必须进行初始化。
2.一个变量可以有多个引用。
3.引用一旦引用一个实体,再不能引用其他实体。
5.2、使用场景
1、做参数:使用引用做参数时可以起到形参改变实参的作用,达到了指针的效果,但是实现过程要比使用指针简单,且不需要多余开辟空间来存储指针变量。
void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}
2、做返回值
一般情况下,函数在返回值之前会将要返回值的拷贝到一块临时空间中(因为函数在调用结束之后栈帧一旦销毁,栈帧内的数据就无法访问且存在丢失风险),在主调函数调用完之后,临时空间的返回值再次拷贝到主调函数要赋值的目标变量ret。
如果函数返回时,出了函数作用域,如果返回对象还在(没有还给操作系统),则可以使用引用返回,如果已经还给操作系统,则必须使用传值返回。
int& Count()
{static int n = 0;//变量0在静态区中n++;// ...return n;
}
5.3引用和指针的区别
1.引用就是定义一个变量的别名,指针用来存储一个变量的地址。
2.引用在定义时必须要进行初始化,指针没有要求。
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
4.没有NULL引用,但有NULL指针。
5.在sizeof()中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数。(32位系统下是4个字节,64位系统下是8个字节)
6.引用+1可以实现引用的实体+1,而指针加一是指针向后偏移一个指针指向类型的大小。
7.有多级指针,但没有多级引用。
8.访问实体方式不同,指针需要解引用,引用编译器会自己处理。
6、内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方进行展开,没有函数调用建立栈帧的内存消耗,内联函数提升程序的运行效率。如果内联函数的太长,编译器也会不听取内联建议,在执行过程中仍然采用调用的形式进行使用。inline是一种以空间换时间的做法,在编译阶段,编译器会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
注意:inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
我们可以感受到内联函数在实现上与宏的实现相似,宏的优缺点:优点(增加代码的复用性,提高性能),缺点(代码可读性差,不方便调试)。C++中如果有短小的函数且反复调用,就可以使用内联函数来进行实现。
7、auto关键字
auto关键字是在变量的类型冗长或者不是很清楚变量的类型时进行使用的,变量的类型需要由编译器来进行推导得到。
1.用auto声明指针类型时,用auto和auto*没有任何区别,但auto声明引用类型时必须加&.
2.在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器就会报错。
void TestAuto()
{auto a = 1, b = 2; auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
3.auto不能用来声明数组。
8、基于范围的for循环
对于一个有范围的集合来而言,程序员来说明范围是多余的,有时候还会很容易犯错误。C++中引入了基于范围的for循环。for循环后的括号由冒号:分为两部分,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor()
{int array[] = { 1, 2, 3, 4, 5 };for(auto& e : array)e *= 2;for(auto e : array)cout << e << " ";return 0;
}
与普通循环类似,可以使用continue来结束本次循环,也可以使用break来跳出整个循环。这里要注意的是,使用范围for循环时的范围必须是确定。