一、命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,
以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
1.定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对 { } 即可, { } 中即为命名空间的成员。命名空间本质上定义的是域。命名空间可以嵌套,也可以定义其他的命名空间。在多个文件中,可以同时定义同名的命名空间,编译器会将这些命名空间进行合并。
namespace mynamespace
{
int a;
int b;
}
2.使用
命名空间有三种使用方式:
(1)加作用域限定符
在C/C++中,不同域可以定义同名变量,但是局部优先。‘:’为作用域限定符,假如我们要访问全局变量,只需在变量前加一个‘::’符号即可。
编译器若要查找一个函数,它的默认查找的顺序是:先局部,再全局域。它不会主动到命名空间去找,除非我们加上作用域限定符之后才会进去查找。
上图中,编译器现在在局部查找a变量,没有进入命名空间中。不加作用域限定符,就打印0,当加上作用域限定符时,编译器才会到我们指定的命名空间去找。
(2)直接展开命名空间
当我们需要重复调用一个命名空间里面的函数时,每一次调用我们就需要指定一下命名空间,多少有点麻烦。我们可以使用下面一条语句:
using namespace mynamespace;
这条语句意为展开命名空间,意在指示编译器可以进入到这个命名空间去查找。当使用了这条语句之后,查找的优先级:局部>全局>=命名空间。在全局和命名空间同时存在同名函数或变量时,展开了命名空间之后,相当于把命名空间里面的东西释放到了全局中,如果之前全局域中存在了同名函数,那么就会报错。
当两个命名空间同时展开时,若两个命名空间同时存在同名函数或变量时,编译器就会报错。因此,展开全部的命名空间是有风险的。
(3)展开指定成员
那么我们可以使用第三种方式:指定展开某一个。
using mynamespace::a;
这条语句意为只展开某一个函数或变量,之后调用它时就不需再指定了。
这种方法解决了直接展开命名空间时可能会引起的命名冲突的问题。
二、C++输入和输出
在C语言中,我们使用printf、putchar、scanf等函数在屏幕上输出、输入数据,它需要包含stdio.h这个头文件,但是在C++中,我们使用cin、cout来输入、输出数据。
其中c为console(控制台)的缩写,out为输出的缩写。在C++中,‘<<’这个符号为“流插入操作符”,endl 为 end line 的缩写,意为换行。在C++中,它多了许多智能的特性,编译器会自动识别变量的类型。当我们想要使用 cout 时,直接使用会报错,因为它在 iostream 中被封装到了一个名为 ‘std’ 的命名空间中。我们个人写代码时,可以直接将std展开,因为没人会和我们起冲突。但是在项目中,多个人参与进来时,尽量不要随意全部展开命名空间。
当我们想要输入一些数据时,可以使用cin,意为从控制台中提取数据。‘>>’为流提取操作符。
它的操作顺序是从左到右,输入两个数分别放在对应的变量中。
当然我们也可以使用 printf 等C语言中的函数,iostream中也定义了这些函数。
三、缺省参数
在C++中,对函数传参时也和C有所不同。它引入了一个新特性,我们可以不对它传值,那么编译器就会使用你默认设置的值。
这个默认的值是在设计函数接受参数时设置的。当然也可以缺省多个值
有一点就是不能跳跃着传参,也就是不能传a和c不传b,不能将b空过去,这样编译器会报错。上面是全缺省,也可以半缺省,但是必须是从右往左缺省,否则在调用的时候就会产生歧义,编译器不知道要将值传给谁。
缺省参数在定义和声明不能同时出现。它只在声明处给。
四、函数重载
在C语言中,在同一作用域是不允许同名函数出现的。但是在C++中,它可以在同一作用域中出现同名函数。它的名字可以相同,但是它的参数类型、参数个数、参数顺序需要不同,与参数名无关,当参数相同时,参数名不同,这两个函数不构成重载。当我们输入不同的参数时,编译器会自动去匹配参数类型然后调用相应的函数。若两个同名函数定义在了两个命名空间中,我们把这两个命名空间展开,调用这两个函数时,编译器会报错。因为两个函数不在同一个作用域中,不构成重载关系,编译器不会去匹配他们的类型。
那么为什么C语言不支持重载呢?
本质上来讲,是编译器对函数名的修饰不同。在C语言中,编译器不对函数名进行修饰,直接通过函数名来匹配。而在C++中,编译器会根据参数的不同对函数名进行不同的修饰,它们的名字可以相同,但是对它们进行的修饰不同,也就是说,在编译和连接之后,它们已经变成了不同名的函数。不同的编译器对函数名的修饰规则不同。
五、引用
1.引用的定义及使用
在C++中,引入了引用的概念。引用与指针类似,但又不同于指针。
int main()
{
int a = 0;
int* b = &a;//指针
int& c = a;//引用
return 0;
}
在第二条语句中,‘&’为取地址符,‘&a’表示取出变量a的地址,它是在C语言中使用的,而在C++中,它仍然可以使用。但是在C++中,‘&’还有另一个用法是引用,简单来说就是取别名。我们对c操作,实际上就是对a操作。它与指针的区别是不需要再添加解引用操作符 ‘ * ’ 了。它不仅可以对整形、浮点型变量取别名,也可以对指针变量取别名。
int*& e = b;
对a的任意一个别名取地址,取到的都是a的地址,也就是说a的别名和a的地址都是相同的,a的别名是c,也可以对c取别名,也可以对c的别名取别名,本质还是a。
在链表的学习中,有许多的函数需要传递二级指针,使用起来不太方便,那么我们就可以给指针取别名传给函数,就不需要再解引用二级指针来改变一级指针的值了,直接改变别名的值即可。
引用一旦引用了一个变量,就不能再改变引用的对象。它没有空引用的概念。
2.引用和指针变量间引起的权限问题
要注意,最初创建的变量的权限是最大的,如果最初创建的变量为可读不可写,那么它的引用的权限是不能大于它所引用的变量的,它最多只能可读可写。
如图,我们使用b来引用经过const修饰的变量a,出现了报错,这就是权限的放大,引用的权限超过了它所指向的变量。a所引用的值是不能改变的,而b却可以改变,这就是权限的放大。
当我们给b前面加上const时,此时b与a的权限相等,不会报错。
指针也是如此,当我们写出下面的代码时,也会报错。
int main()
{
int a = 0;
const int* b = &a;
int* c = b;
return 0;
}
这种也是权限的放大,const修饰的是指针指向的值,这个指针指向的值不能改变,而指针变量c也指向了这个值,却可以通过解引用的方式来修改,是权限的放大,因此编译器会报错。
若const修饰的是整型变量,它不会引发权限放大的问题。
int main()
{
const int a = 0;
int b = a;
b++;
return 0;
}
const修饰的是a的值,a的值不能被改变,但是不影响a赋值给b,b的值怎样改变与a无关,它们分别存储不同的空间,它们的改变不会相互影响。
来看下面一段代码
我们又创建了一个变量c来引用a,然后通过c来改变a,这时发现b的值也改变了,并且没有报错,这是因为const限制的只是它修饰的变量,即不能通过b来改变,但是可以改变c的值来间接改变b的值。
来看下一段代码:
int main()
{
double a = 13.14;
//const int b = a;
int& c = a;
}
这段代码也是会报错,属于权限的放大。因为a在赋值给b时,由于类型的不同,复制之前就要先做类型转换,而类型转换具有常性,相当于经过了const修饰,这时候引用经过const修饰的变量,就是权限的放大。
引用与指针的不同点:
1.在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
2.引用在定义时必须初始化,指针没有要求。
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体。
4.没有NULL引用,但有NULL指针。
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节)。
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7. 有多级指针,但是没有多级引用。
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
9. 引用比指针使用起来相对更安全。
六、内联函数
在一些函数中,需要调用许多次其他小函数,比如在排序中,要排几千万个数据,就要调用几千万次到几亿次的交换函数,但是每一次调用都要开辟对应的栈帧,开辟栈帧又是有消耗的,在C++中,有了内联函数的概念,它的意思是就是像交换函数这种小的函数但是需要调用的次数非常多,那么就直接将它展开在调用它的函数中,省去了开辟空间的消耗。它与C语言中的宏类似。但是它是由编译器自动实现的,C语言中还需要我们自己去实现一个宏函数,宏函数的实现很困难并容易出错,内联函数就很好的解决了这一点。
特性:
1.它是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2.对于编译器而言只是一个建议,不同编译器关于内联函数实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用内联函数修饰,否则编译器会忽略内联函数特性。
3.不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
七、auto关键字(C++11)
auto是一种能够自动推到变量类型的关键字,当一个变量的类型难以拼写时,就可以使用auto代替。
int main()
{
std::map<std::string, std::string> m{ { "apple", "苹果" },
{ "orange","橙子" },{"pear","梨"} };
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
{
//....
}
return 0;
}
std::map<std::string, std::string>::iterator 是一个类型,但是该类型太长了,特别容易写错。
直接将它换成auto,非常方便。
也可以使用typedef来给类型取别名, 但是也有缺陷。
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
使用细则
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
auto*指定了它的对象必须是指针。
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto不能推导的情况
1. auto不能作为函数的参数
2. auto不能直接用来声明数组
3.auto在实际中最常见的优势用法就是C++11提供的新式for循环,还有lambda表达式等进行配合使用。
八、基于范围的for循环(C++11)
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范
围内用于迭代的变量,第二部分则表示被迭代的范围。
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
//使用auto、int都可以,auto只是自动推导类型。
for (auto e : array)
cout << e << " ";
return 0;
}
范围for循环不能改变数组中的值,范围for循环是将数组临时拷贝一份然后遍历这个临时拷贝的数组。改变这个临时数组并不能改变数组中的值。
注意:
1.与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
2.for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围,即不支持在函数中对传过来的整形指针使用范围for遍历;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
3.它只支持从头到尾遍历数组。
十、指针空值nullptr(C++11)
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。
注意:
1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
的。
2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。