c++入门 之 C++命名空间 、 C++输入&输出 、 缺省参数 、 函数重载 、 引用、内联函数 、 auto关键字(C++11) 、 基于范围的for、指针空值---nullptr
本文针对以下解说:
1… 命名空间
2. C++输入&输出
3. 缺省参数
4. 函数重载
5.引用
6. 内联函数
7. auto关键字(C++11)
8. 基于范围的for循环(C++11)
9. 指针空值—nullptr(C++11 )
1. C++关键字namespace
作用:解决名字冲突
命名空间定义 :
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名 空间的成员。
特性:
1.普通的命名空间
namespace N1 // N1为命名空间的名称 {
// 命名空间中的内容,既可以定义变量,也可以定义函数
int a;
int Add(int left, int right)
{
return left + right;
}
}
2.命名空间可以嵌套
namespace N1 // N1为命名空间的名称 {
// 命名空间中的内容,既可以定义变量,也可以定义函数
int a;
int Add(int left, int right)
{
return left + right;
}
namespace N2 {
int b;
int Add(int left, int right)
{
return left + right;
}
}
- 同一个工程中允许存在多个相同名称的命名空间 ,编译器后会合成同一个命名空间中。
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
命名空间的使用:
有三种方式:
方式1:命名空间名字 + 作用域限定符:: + 成员名字
namespace N1 // N1为命名空间的名称 {
// 命名空间中的内容,既可以定义变量,也可以定义函数
int a;
int Add(int left, int right)
{
return left + right;
}
}
int main(){
printf("%d",N1::a);
}
方式2:using+名字N1+作用域限定符::+成员名字(但要保证全局作用域里没有相同名字)
使用场景:命名空间的某个成员使用这个成员频繁
缺陷:可能会(和全局作用域里面的)冲突
下面是例子:
namespace N1 {
int a;
int Add(int left, int right)
{
return left + right;
}
}
using N1::a;//在这里定义
int main(){
printf("%d",a);//访问的时候直接输入
}
方式三:using+namespace+命名空间名字
使用的场景:当前空间的某些成员在某个文件中使用频繁
缺点:缺陷冲突率高,因为全局作用域可能有相同名字的变量概率高
#include<iostream>
#include<stdlib.h>
using namespace std;
namespace N1 {
int a=11;
int Add(int left, int right)
{
return left + right;
}
}
namespace N4{
int a = 10;
int Add(int &left, int& right){
return left + right;
}
double Add(int a, int b){
return a + b;
}
double Add(char left, char right){
return left + right;
}
static int b = 20;
}
using namespace N4;
int main(){
printf("%d", a);
return 0;
}
直接访问N4 中的a,结果为10;
总结: 其实第二种和第三种的作用就是使命名空间中的成员的作用域变成全局的;
2.输入输出
头文件:iostream
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件 即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文 件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用 +std的方式
使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%c
输入:cin>>a;
输出:cout<<a;
换行:<<endl;
#include<iostream>
using namespace std;
int main (){
int a;
int b;
cin>>a>>b;
cout<<a+b<<endl;
return 0;
}
运行起来输入两个 数 ,返回他们相加的结果;
3. 缺省参数
概念:备胎:
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该 默认值,否则使用指定的实参。
意思就是在调用的时候没有传实参,那么就会用到形参里面的值;
#include<iostream>
#include<stdlib.h>
using namespace std;
void fun(int c = 0){
cout << c << endl;
}
int main(){
fun(10); // 传参时,使用指定的实参
fun(); // 没有传参时,使用参数的默认值
return 0;
}
输出结果 10 0;
缺省参数分类:
两个:半缺省参数; 和 全缺省参数
全缺省参数:
oid fun(inta=1,int b =1,int c = 0){
cout << a<<b<<c << endl;
}
半缺省参数:
oid fun(inta,int b ,int c = 0){
cout << a<<b<<c << endl;
}
区别:
全缺省:缺省值都给全,传实参的时候,可以传的个数 为a (0=< a<=形参个数);
传实参参数,没有给全, 传实参的时候是从左往右依次传参;
半缺省:定义形参的时候从右往左的按顺序赋缺省参数值,不能间隔着给, 传实参的时候是从左往右依次传参;可以传的个数 为b ,假设没有缺省值的形参有n个 ,那么b的范围(n<=b<=形参个数);
意思是:调用的时候:几个参数没有缺省值,必须传几个参数以上;
在声明还是定义的时候用:
- 缺省参数不能在函数声明和定义中同时出现
因为:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个 缺省值。
-
所以尽量给在声明的位置
-
缺省值:常量和全局变量都可以
-
c语言不支持参数缺省
4. 函数重载
概念:函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的 形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
其实就三句话:同一作用域,名字相同,参数不同的函数。
参数不同有三个:类型 、个数、 顺序。
举个例子:
int ADD( int a ,int b){
return a+b;
}
int ADD(double a,double b){
return a+b;
}
上面两个就可以构成函数重载;
但是的注意:有时候可能会搞错 ,函数重载和参数的返回值类型无关;
例如:
下面是函数重载吗?
int ADD( int a ,int b){
return a+b;
}
double ADD( int a ,int b){
return a+b;
}
答案:不是,,函数重载和参数的返回值类型无关
那么编译器是如何区分调用哪个函数的?
答:编译器检测传入的实参类型 ,然后根据函数的参数列表类型推演,根据推演选择合适的函数;
在不同编译器下对函数名字修饰不同;
名字修饰(name Mangling):
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
预处理:头文件的展开,去注释 条件编译 宏替换
编译:分析错误语义语法分析,生成汇编码
汇编:将汇编代码生成机器 指令代码
链接:.o文件(机器指令代码)与库文件合在一起生成可执行程序
Name Mangling是一种在编译过程中,将函数、变量的名称重新改编的机制,简单来说就是编译器为了区分 各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称。
C语言的名字修饰规则非常简单,只是在函数名字前面添加了下划线。
例如:
编写如下代码,就可以看见编译器报错,可以看到函数名只是在函数名字前面添加了下划线。
#include<stdio.h>
int add(int a, int b);
int main(){
add(1, 2);
printf("%d", add);
return 0;
}
上述add函数只给了声明没有给定义,因此在链接时就会报错,提示:在main函数中引用的add函数找不到 函数体。从报错结果中可以看到,C语言只是简单的在函数名前添加下划线。因此当工程中存在相同函数名的 函数时,就会产生冲突。
这也是为什么C语言不支持函数重载:就因为它区分函数就是名字下面加个下化线。
由于C++要支持函数重载,命名空间等,使得其修饰规则比较复杂,不同编译器在底层的实现方式可能都有 差异。
#include<iostream>
#include<stdlib.h>
using namespace std;
void fun(int a);//{
//cout << "123"<< endl;
//}
int main(){
fun(10);
//int a;
//int b;
//cin >> a >> b;
//cout << a + b << endl;
return 0;
}
通过上述错误可以看出,编译器实际在底层使用的不是Add名字,而是被重新修饰过的一个比较复杂的名 字,被重新修饰后的名字中包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求 其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包 含在终的名字中,就可保证名字在底层的全局唯一性。
总结:C++将 参数的类型编译到名字中 ,规则为 :[email protected]@@[email protected]这种样式;
函数名字加类型@@+ya+参数返回值类型参数类型[email protected]结束标志,
c++中函数重载底层是怎么处理的?
C++利用name mangling(倾轧)技术,将函数、变量的名称重新改编的机制。在 C++重载、namespace等操作符下,函数可以有同样的名字,编译器为了区分各个不同地方的函数,将各个函数通过编译器内定的算法,将函数改成唯一的名称。
C++中能否将一个函数按照C的风格来编译?
extern “C” ,给函数前加这个 ,就会按c文件去编译
总结一下 extern :
1.c++中extern “C” ,给函数前加这个 ,就会按c文件去编译
2.c中xtern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,如果extern针对的是一个变量,那么其它文件则可以改变这个值;
5.引用
引用概念 :引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它 引用的变量共用同一块内存空间。
定义:
类型& 引用变量名(对象名) = 引用实体;
int a =10 ;
int & b =a;
printf("%d", &a);
printf ("%d",&b);
打印出来的结果是一样;
注意:引用类型必须和引用实体是同种类型的
引用特性 :
1.引用的时候必须初始化
2.一个实体可以有多个引用
3.一个引用只能对应一个实体
如下图:
这个是验证;一个变量可以有多个引用
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(){
int a = 10;
int &b = a;
int &c = a;
printf("%d\n", &a);
printf("%d\n", &b);
printf("%d\n", &c);
return 0;
}
下面这个是验证,一个引用对应一个实体:
运行除错;
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(){
int a = 10;
int &b = a;
int &c = a;
int x = 20;
int &a = x;
printf("%d\n", &a);//运行的时候出错
printf("%d\n", &b);
printf("%d\n", &c);
return 0;
}
常引用:
const 类型的引用 ,是个万能引用,引用对象可以为常量也可以为变量
#include<iostream>
#include<stdlib.h>
using namespace std;
int main(){
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,10为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
也就是说在引用一个常量的时候 ,引用前要加上 const;
还有一个就是:
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
这句的注意 ,其实是const引用将他的实体转为了常量 ,而且类型不同的也可以
说到这里我说一下 const和static的区别:
const就是只读的意思,只在声明中使用。static一般有两个作用,规定作用域和存储方式,对于局部变量,static规定其为静态存储方式,每次调用的初始值为上一次调用的值,调用结束后存储空间不释放。 对于全局变量,如果以文件划分作用域的话,此变量只在当前文件可见,对于static函数也是在当前模块内函数可见。
static的作用是把一个变量设置为静态。函数内部声明的static变量可作为对象间的一种通信机制。如果一个变量是类的一部分却不是该类的各个对象的一部分,就成为一个static静态成员。同理,一个需要访问类成员,而不需要针对特定对象去调用的函数,也被称为一个static成员函数。类的静态成员函数只能访问类的静态成员。 const是设置常量,也就是只读。
总结:
const是只读操作,不能修改;
static:局部改变存储方式为静态的,改变生命周期,全局变量:使这个变量只能在本件中可见 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
引用的使用场景 :
1.直接给一个变量取别名
例如:
int a =10;
int &b =a;
b就相当于a的别名,指向同一块内存;
2.做函数参数
有两种:
a.需要通过形参改变外部实参 , 传普通类型引用
b.不需要通过形参改变外部实参, 传const 参数
举例:实现两个两个变量交换,传普通类型引用
#include<iostream>
#include<stdlib.h>
using namespace std;
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
int main(){
int x = 10;
int y = 20;
int & c = x;
int &d = y;
swap(c, d);
printf("%d\n", x);
printf("%d", y);
return 0;
}
例2:不想改变实体
#include<iostream>
#include<stdlib.h>
using namespace std;
void swap(const int &a){
a = 10;//这句会报错,把参数设为不可修改的,只能读取
printf("%d", a);
}
int main(){
int x = 10;
int & c = x;
swap(c);
printf("%d\n", x);
return 0;
}
3. 做返回值
下面这个代码有什么问题?
#include<iostream>
#include<stdlib.h>
using namespace std;
int & test( int a,int b){
int f = a + b;
return f;
}
int main(){
int x = 10;
int & c = x;
int y = 10;
int &e = y;
int &d=test( 1, 2);
test(3, 4);
printf("%d\n", d);
return 0;
}
运行结果为7;本来预期结果为3的;那么是什么原因呢
先说一下**—cdecl 约定传参顺序**:传参数从右往左,传参机制:压栈的方式
栈上开辟空间;刚开始MAIN函数执行 调用的是,test(1,2)函数,然后将它的返回值给了另一个别名,但是的注意一句话:别名和实体指向的同一个内存,函数在栈上的运行有个特性,返回的时候就释放内存了,其他函数就可以进来了,但是别名还是指的那块空间,所以值就发生了改变;
释放的意思是是 :别的函数可以访问了,但是内存中的数据还在, 那什么时候清理呢?
没有函数访问那片空间,就不清理, 只有当有函数访问的时候,由访问函数来清理那片内存空间。
栈和栈帧的区别:
栈帧:一个是内存空间 和栈的特性相同 ;一个是特殊的数据结构
注意:如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引 用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。
那么应该返回:
:堆上的
:全局变量
:静态变量
:引用类型的变量
总之在函数返回前提前保存的结果;
传值、传引用效率比较:
通过 做参数 和返回值的效率比较:
多运行几次:
下面这个函数做参数是比较传值 、传地址、传引用的效率比较 运行结果为 169 2 2 ;
#include<iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;
struct A {
int arr[10000];
};
void Test1(A a){
}
void Test2(A &a){
}
void Test3(A *a){
}
int main(){
A a;
size_t begin = clock();
for (int i = 0; i < 100000; i++){
Test1(a);
}
size_t end = clock();
cout << end - begin << endl;
size_t begin2 = clock();
for (int i = 0; i < 100000; i++){
Test2(a);
}
size_t end2 = clock();
cout << end2 - begin2 << endl;
size_t begin3 = clock();
for (int i = 0; i < 100000; i++){
Test3(&a);
}
size_t end3= clock();
cout << end3 - begin3 << endl;
return 0;
}
总结:传地址和传引用在传参效率方面没什么区别
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实 参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返 回值类型非常大时,效率就更低
作为返回值类型:
多运行几次:
下面这个函数做类型是比较传值 、传地址、传引用的效率比较 运行结果为 329 2 2 ;
#include<iostream>
#include<stdlib.h>
#include<time.h>
using namespace std;
struct A {
int arr[10000];
};
A a;
A Test1(){
return a;
}
A* Test2(){
return &a;
}
A & Test3(){
return a;
}
int main(){
size_t begin = clock();
for (int i = 0; i < 100000; i++){
Test1();
}
size_t end = clock();
cout << end - begin << endl;
size_t begin2 = clock();
for (int i = 0; i < 100000; i++){
Test2();
}
size_t end2 = clock();
cout << end2 - begin2 << endl;
size_t begin3 = clock();
for (int i = 0; i < 100000; i++){
Test3();
}
size_t end3= clock();
cout << end3 - begin3 << endl;
return 0;
}
通过上述代码的比较,发现引用和指针在作为传参以及返回值类型上效率几乎相同。
那么就引用和指针没有区别吗?
答案:是有的
区别:
1.在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间
2.在底层实现上实际是有空间的,因为引用是按照指针方式来实现的
3.引用在定义时必须初始化,指针没有要求 int *ptr;可以 int&a;不可以
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4. 没有NULL引用,但有NULL指针
5. sizeof大小不一样,引用的大小为其引用类型的大小,指针的大小是固定的,不同编译器下不同
6. 引用自加,其实体加1,指针自加就是指针向后偏移一个类型的大小
7. 有多级指针,没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
6. 内联函数
概念:
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。
例如下面的:
inline int add(int left,int right){
return left+right;
}
int main (){
int a =add(1,3);
cout << a<<endl;
return 0;
}
特性 :
- inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜 使用作为内联函数。
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。
3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会 找不到。
那么怎么做呢,声明的时候直接定义好inline函数
first.h文件
#pragma once
inline int add(int left, int right){
return left + right;
}
main.cpp 文件
#include<iostream>
#include"first.h"
using namespace std;
int main(){
int a = add(1, 2);
cout << a << endl;
return 0;
}
上面就是正确的做法,或者直接在main文件中定义;、
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能
3和类型无关
缺点:
- 宏不进行参数类型检测,在预处理阶段
- 不能调试 ,带类型的宏函数可能引起副作用 (x++ ),表达式求值出现永久性的问题;
例如 #define MAX (a,b) ((a)>(b))?(a):(b)
x=5;
y=8;
z=MAX(x++,y++);
cout<<x<<y<<z;
结果就是 6 9 10 和 预期的结果不一样 - 宏函数过大,会引起代码膨胀
- 可能会带来运算符优先的问题,导致容易出错
- 导致代码可读性差,可维护性差,容易误用
C++有哪些技术替代宏?
- 常量定义 换用const
- 函数定义 换用内联函数2. 函数定义 换用内联函数
内联函数和宏的比较:
什么是内联函数?**
内联函数是代码被插入到调用者代码处的函数。如同 #define 宏,内联函数通过避免被调用的开销来提高执行效率,尤其是它能够通过调用(“过程化集成”)被编译器优化。 宏定义不检查函数参数,返回值什么的,只是展开,相对来说,内联函数会检查参数类型**,所以更安全。**
内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
总结:
宏函数的优点:代码运行效率高,在预处理阶段,用函数替换调用位置上替换;没有调用开销;没有参数压栈等操作
缺点:不会编译,在预处理阶段,不会进行参数检测,,代码膨胀;
内联函数的优点:继承了宏的优点,而且宏函数的缺点他都改善了;
缺点:
- 在本文中有效
2.类中有些函数默认为内联函数
3.代码量大的话。编译器就会放弃编译
4.如果有递归或循环也不会编译
在本文中有效,而且如果有时候有些函数会被编译器默认为内联函数,即使自己没有定义这个函数为内联函数;而且内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
7. auto关键字(C++11)
auto简介 :
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有 人去使用它;
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得
他就是用来推演类型的
#include <iostream>
using namespace std;
#include<typeinfo.h>
int test(int left ,int right ){
return left + right;
}
int main(){
int a = 10;
auto b = a;
auto c = 'a';
auto d = test(1, 2);
auto e;//出错,没有初始化
cout << typeid(e).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
【注意】
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类 型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为 变量实际的类型。
这些都是由编译器来完成的,推演类型,声明占位符,推演出来就换成实际类型,根据初始化表达式来推演
auto的使用细则
- auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
如下:如果引用类型是不加&,g就无法改变x;
#include <iostream>
using namespace std;
#include<typeinfo.h>
int main(){
int x = 20;
auto * f = &x;
auto t = &x;
auto &g = x;
cout << typeid(f).name() << endl;
cout << typeid(t).name() << endl;
cout << typeid(g).name() << endl;
//*f = 10;
//*t = 10;
g = 10;
cout << x << endl;
return 0;
)
2. 在同一行定义多个变量 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对 (第一个类型)进行推导,然后用推导出来的类型定义其他变量。
编译器只根据第一个类型进行推导,所以变量类型要一样:
auto b = 10, c = 209;
auto x = 10, y = 20.2;//编译器报错
编译器不能推导的auto类型:
1. auto不能作为函数的参数
void test (auto a){
}
编译器报错:
2. auto不能直接用来声明数组
void test (){
int arr[3]={1,2,3};
auto a[]=arr;
}
编译器报错:
3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
8. 基于范围的for循环(C++11)
在C++98中如果要遍历一个数组,可以按照以下方式进行
这是一个普通的循环;
#include <iostream>
using namespace std;
#include<typeinfo.h>
int main(){
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (int i = 0; i < 10; i++){
array[i] *= 2;
}
for (int j = 0; j < 10; j++){
cout << array[j]<<" " ;
}
return 0;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中 引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。
格式:
for(变量 : 范围)
对于上一个数组改进:
#include <iostream>
using namespace std;
#include<typeinfo.h>
int main(){
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for ( auto & i : array){
i*= 2;
}
for each( auto j in array){
cout << " " << j;
}
return 0;
}
两种方式 :看第二种,就知道第一个的意思了; 定义一个变量去接收数组中的每一个数 ,进行操作;至于为什么传的是auto &引用类型 是因为 要对里面的数据进行改动,传auto 类型的只是改变了临时的变量 ,而引用改变的是实体。
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
范围for的使用条件:
- for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和后一个元素的范围;对于类而言,应该提供begin和end的 方法,begin和end就是for循环迭代的范围。
下面这个范围就不明确:
void test(int array[]){
for (auto & i:array){
i*=2;
}
}
- 迭代的对象要实现++和==的操作。
9. 指针空值—nullptr(C++11 )
1. C++98中的指针空值
在良好的C/C++编程习惯中,声明一个变量时好给该变量一个合适的初始值,否则可能会出现不可预料的 错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else #define NULL ((void *)0)
#endif
#endif
NULL可能被定义为字面常量0,或者被定义为无类型指针(void)的常量。*
这会导致什么麻烦呢?
#include <iostream>
using namespace std;
#include<typeinfo.h>
void test(int){
cout << "test(int)" << endl;
}
void test(int*){
cout << "test(int*)" << endl;
}
int main(){
test(0);
test(NULL);
test((int*) NULL);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下 将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
为了解决这个问题:
nullptr 与 nullptr_t
为了考虑兼容性,C++11并没有消除常量0的二义性,C++11给出了全新的nullptr表示空值指针。C++11为什 么不在NULL的基础上进行扩展,这是因为NULL以前就是一个宏,而且不同的编译器厂商对于NULL的实现可 能不太相同,而且直接扩展NULL,可能会影响以前旧的程序。
因此:为了避免混淆,C++11提供了 nullptr,即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转 化为指针类型,nullptr_t被定义在头文件中:
typedef decltype(nullptr) nullptr_t
注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void)0)所占的字节数相同*。
- 为了提高代码的健壮性,在表示指针空值时建议最好使用nullptr。