C++语言基础-(①)基础要点
目录
介绍
本文系统介绍了C++基础语法特性,主要包括: 命名空间(namespace)机制,通过隔离不同代码块的标识符解决命名冲突问题; 缺省参数功能,支持为函数形参设置默认值; 函数重载特性,允许同名函数通过参数类型/数量/顺序不同实现多态; 引用(reference)机制,详细分析了引用与指针的区别、引用作为参数和返回值的应用场景及安全性问题; 内联函数(inline)优化,通过空间换时间提高小函数调用效率; C++11新增特性:auto类型推导和基于范围的for循环,空指针nullptr。 文章通过大量代码示例和内存图示,深入浅出。
1.命名空间
在实际的工程项目中,一个人几乎不能单独的完成一整个项目,在不同人写的代码中变量、函数和后面要学到的类都是大量存在于全局作用域中的,可能会导致许多冲突,一个思路就是将不同人定义的变量、函数和类等隔离起来或者是说对标识符的名称进行本地化,以避免命名冲突或名字污染,于是namespace关键字就用来解决这个问题。
通俗的理解一下C++支持定义命名空间的好处:小王可以定义自己的命名空间,小红也可以定义自己的命名空间,那么小王和小红的命名空间中可以定义同名的变量和函数,这样以来小王和小红写的代码合并之后不会发生冲突。
1.1 命名空间定义
定义命名空间使用namespace关键字,后面跟命名空间的名字,然后跟一对{},{} 中即为命名空间的成员(一般开发中用项目的名字代替命名空间的名字)。一个命名空间就是一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
1.普通的命名空间定义
namespace space_one
{
/*命名空间中可以定义变量,函数,类型*/
int scanf = 10;
int Add(int a, int b) {
return a + b;
}
struct LinkNode {
struct LinkNode* next;
int value;
};
}
2.命名空间可以嵌套(命名空间中定义命名空间)
namespace N1
{
int scanf = 10;
int printf = 20;
int Add(int a, int b) {
return a + b;
}
namespace N2
{
int c = 1;
int d = 2;
int Sub(int a, int b) {
return a - b;
}
}
}
小问题:对于嵌套的命名空间,里层命名空间中定义的变量名是否可以与外层的命名空间中定义的变量名相同?可以。在内层命名空间中直接使用该变量名时,访问的是内层定义的变量。在内层通过完整命名空间路径仍可访问外层变量。(如下代码所示)
namespace Outer {
int x = 10; // 外层变量
namespace Inner {
int x = 20; // 内层同名变量
void print() {
std::cout << x << std::endl; // 输出20(访问内层)
std::cout << Outer::x << std::endl; // 输出10(显式访问外层)
}
}
}
3.同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace N3
{
int a;
int b;
int Add(int L1, int L2) {
return L1 + L2;
}
}
namespace N3
{
int value;
//int b; 注意:相同命名空间中的不能定义两个相同的成员
int Sub(int L1, int L2) {
return L1 - L2;
}
}
1.2使用命名空间
三种使用方式:
1.使用时指定使用的成员属于某一个命名空间。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
namespace N4
{
int a;
int b;
int Add(int L1, int L2) {
return L1 + L2;
}
int Sub(int L1, int L2) {
return L1 - L2;
}
}
int main()
{
N4::a = 10;
N4::b = 5;
printf("%d\n", N4::a);
printf("%d\n", N4::b);
printf("add = %d\n", N4::Add(N4::a, N4::b));
printf("sub = %d\n", N4::Sub(N4::a, N4::b));
return 0;
}
2.提前展开命名空间中的某一个成员(针对经常使用的命名空间中的某个成员)。
using N4::b;
int main()
{
N4::a = 10;
b = 5;
printf("%d\n", N4::a);
printf("%d\n", b);
printf("add = %d\n", N4::Add(N4::a, b));
printf("sub = %d\n", N4::Sub(N4::a, b));
return 0;
}
3.提前将命名空间中的成员全部展开(针对经常使用的命名空间中的所有成员)。
using namespace N4;
int main()
{
a = 10;
b = 5;
printf("%d\n", a);
printf("%d\n", b);
printf("add = %d\n", Add(a, b));
printf("sub = %d\n", Sub(a, b));
return 0;
}
注意:当然展开后,使用时仍然指定使用的成员属于该命名空间也是OK的。
1.3std命名空间
C++库中的所有东西都是放到std命名空间中的(所以下面的代码倍感熟悉)。当然有三种展开方式。全展开方式想使用谁就使用谁,当然也有一个坏处:库里面的东西全部都散出来了,如果定义与库里面同名的东西则会命名冲突(当然也可以自己再定义一个命名空间与std库隔离起来)。
using namespace std;
2.输入输出流
#include<iostream>
int main()
{
std::cout << "hello world";//不会换行
std::cout << "hello world/n";//会换行
std::cout << "hello world"<<std::endl;//会换行
return 0;
}
类似于c语言中的scanf与printf函数,C++中的cin与cout表示标准输入对象-从键盘输入数据和标准输出对象-向控制台输出数据。可以这么理解:cout<<"hello"<<endl;表示字符串“hello”和换行符号顺着箭头<<流向标准输出对象中。
<<是流插入运算符,>>是流提取运算符,endl相当于换行的意思。这三个符号都被包含在输入输出流<iostream>头文件中,使用之前需要声明。同时这三个符号都在std库中实现因此使用前需要从命名空间中展开。库(命名空间)与头文件的区别:库提供实际的功能实现,通过链接器与程序合并。静态库在编译时直接嵌入程序,动态库在运行时加载。头文件仅提供声明(如函数原型、类接口),不包含实现代码,用于编译阶段的语法检查。
scanf,printf和cin,cout的差异:cin,cout可以自动识别类型(通过函数重载实现),字符串,浮点数,整数等等,但是scanf,printf需要特别指定。
#include<iostream>
int main()
{
//cout可以自动识别类型(通过函数重载实现),字符串,浮点数,整数等等
int a = 10;
double b = 10.01;
std::cout << "hello world" << std::endl;
std::cout << a << " " << b << std::endl;
return 0;
}
#include<iostream>
int main()
{
int a;
double b;
char c;
/*cin也可以自动识别变量的类型*/
std::cin >> a;
std::cin >> b >> c;
std::cout << std::endl;
std::cout << a << std::endl;
std::cout << b << " " << c << std::endl;
return 0;
}

注意(一些细节):
1.在日常练习中我们不在乎跟库名冲突,可以直接使用全展开using namespace std;(如果命名与库中的名字冲突那么需要换名)。
/*直接使用全展开using namespace std;(如果命名与库中的名字冲突那么需要换名)*/
#include<iostream>
using namespace std;
int main()
{
int a = 10;
double b = 10.01;
cout << "hello world" << endl;
cout << a << " " << b << endl;
return 0;
}
2.或者使用时指定使用的成员属于某一个命名空间(std::cout)。
/*使用时指定使用的成员属于某一个命名空间(std::cout)*/
#include<iostream>
int main()
{
//cout可以自动识别类型(通过函数重载实现),字符串,浮点数,整数等等
int a = 10;
double b = 10.01;
std::cout << "hello world" << std::endl;
std::cout << a << " " << b << std::endl;
return 0;
}
3.将常用的库里面的一些对象和类型提前展出来,不常用的使用时再指定(工程之中常用方式)。在项目中提前展开命名空间中的某一个成员(using std::cout)但此时不能再定义cout同名变量但可以定义其他与std库中重名的变量。
/*提前展开命名空间中的某一个成员(using std::cout)但此时不能再定义cout同名变量但可以定义其他与std库中重名的变量*/
#include<iostream>
using std::cout;
using std::endl;
int main()
{
int a = 10;
double b = 10.01;
cout << "hello world" << endl;
cout << a << " " << b << endl;
return 0;
}
最后:关于cout和cin还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等等。因为C++兼容C语言的用法,这些又用得不是很多,我们这里就不展开。
3.缺省参数(默认实参)
定义:缺省参数是在定义函数时,给函数的形参设置一个默认实参(缺省值)。
应用:对于一个含有默认实参的函数,在调用该函数时形参没有被传入实参,则采用形参的默认实参(缺省值)传入到函数中。调用含有默认实参的函数时可以包含该实参也可以省略该实参。
使用场景:在函数的很多次调用中,这个形参都被赋予了一个相同的值,可以把这个形参设置为缺省参数。
#include<iostream>
using namespace std;
void test(int i = 0)
{
cout << i << endl;
}
int main()
{
test(10);
test();
return 0;
}

缺省参数有全缺省参数和半缺省参数两类:
1.全缺省参数:我们可以为每一个形参都提供默认实参。调用这个函数它的所有形参参数都可以缺省,也就是说该函数所有形参都可以使用默认实参。
#include<iostream>
using namespace std;
void test(int i = 1,int j = 2, int k = 3)
{
cout << i << " " << j << " " << k << endl;
}
int main()
{
test();
test(10);
test(10, 20);
test(10, 20, 30);
return 0;
}

2.半缺省参数:我们还可以为一个或多个形参提供默认实参,但是此时如果某个形参被提供默认实参,它右边所有的形参都应该被提供默认实参。也就是说对形参提供默认实参时只能从右向左连续给出,中间不能有间隔。(尽量让不适用默认实参的形参出现在函数参数列表的前面,经常使用默认值的形参出现在参数列表的后面)
#include<iostream>
using namespace std;
void test(int i ,int j = 2, int k = 3)
{
cout << i << " " << j << " " << k << endl;
}
int main()
{
//test();//并不是所有参数都有默认实参不能全省略掉
test(10);
test(10, 20);
test(10, 20, 30);
return 0;
}

注意:调用全缺省参数和半缺省参数函数传参时必须从左往右连续传参(test( , , 30);这样是不被允许的哦)。并且对于半缺省参数传参时必须要给所有没有默认实参的参数传入实参。
4.函数重载(一词多义)
C++中一个函数可以有多种意义或者有多种调用方式。如果在C语言中两个函数的名称不能相同。
4.1什么是函数重载
函数重载:函数名相同,参数不同(参数类型不同或者参数个数不同或者不同类型的参数的顺序不同)。对返回值没有要求(如果只是返回值不同不能构成重载)。
调用重名的函数时编译器会根据传入的实参自动识别区分。
注意:main函数不能进行重载。
#include<iostream>
using namespace std;
int Add(int parameter_first, int parameter_second)
{
return parameter_first + parameter_second;
}
/*参数类型不同*/
double Add(double parameter_first, double parameter_second)
{
return parameter_first + parameter_second;
}
long Add(long parameter_first, long parameter_second)
{
return parameter_first + parameter_second;
}
/*参数个数不同*/
int Add(int parameter_first)
{
return parameter_first + 10;
}
/*参数顺序不同*/
void func(int a, char b) {cout << a << " " << b << endl; }
void func(char a, int b) {cout << a << " " << b << endl; }
/*如果只是返回值不同不能构成重载*/
void func1(){}
//int func1(){}//错误!
int main()
{
cout << Add(10, 20) << endl;
cout << Add(10.1, 20.1) << endl;/*参数类型不同*/
cout << Add(10L, 20L) << endl;
/*参数类型不同,加L是为了告诉编译器匹配long类型而不是int,还有UL表示匹配unsinged long类型*/
cout << Add(10) << endl;/*参数个数不同*/
func(10, 'x');/*参数顺序不同*/
func('x', 10);/*参数顺序不同*/
return 0;
}

4.2C++是如何支持函数重载的(重难点)
1、预处理:头文件的展开。宏替换。条件编译。去掉注释。
list.i test.i
2、编译:语法分析,词法分析,语义分析,符号汇总,生成汇编代码。
list.s test.s
3、汇编:汇编指令转成二进制指令。形成符号表。
list.o test.o
4、链接:将两个目标文件连接到一起。符号表的合并和符号表的重定位。
list.exe test.exe
下面的四张图可以帮大家直观的理解(花了2h暴干的骚图! 点个赞赞支持一下吧^=^)



4.3链接指示:extern "C"
当C语言程序和C++语言程序都想要调用C++程序实现的函数编译成的静态库或动态库时,C++可以调用但是C语言调不动。如果想让C语言也可以调用该函数需要在包含该函数的声明文件中在声明该函数的语句前面加上extern "C"。加上之后有两个部分会随之而改变,第一:C++实现该函数的代码文件在编译之后形成的符号表中函数地址直接对应函数的名字而不再对该函数名称在编译时进行修饰(按照C语言编译后的结果);第二:C++程序在调用该函数时直接按照C语言调用函数的方法按照函数的名称查询符号表。这样C语言程和C++程序都可以调用该函数啦,但是声明文件中在声明该函数的语句前面加上extern "C"之后该函数自然就不能进行重载了原因上面已经详细说过。下面的示意图是一个具体的过程。

5.引用
5.1基本的引用
引用:给变量取新的名字。原始变量和所有引用变量都指向同一片内存区域,因此修改原始变量和所有引用变量中的任意一变量的值内存区域里的值都会发生变化。
语法格式:变量类型& 新的变量名 = 原始变量名;
#include<iostream>
using namespace std;
int main()
{
int val = 10;
int& reval_1 = val;
int& reval_2 = val;
int& reval_3 = reval_2;
cout << val << " " << reval_1 << " " << reval_2 << " " << reval_3 << endl;
cout << &val << " " << &reval_1 << " " << &reval_2 << " " << &reval_3 << endl;
return 0;
}

结果表明:原始变量和所有引用变量都指向同一片内存区域。
1.引用必须在定义时初始化(引用就是取别名如果不初始化则就不知道是哪一个变量的别名)。
2.引用变量一旦初始化完成,引用变量将和它的初始值变量一直绑定在一起,无法将引用变量重新绑定到另一个新的变量。
#include<iostream>
using namespace std;
int main()
{
int val1 = 10;
int& reval_1 = val1;
int val2 = 20;
reval_1 = val2;/*reval_1变为val2的引用? 还是 al2的值被赋值到reval_1?*/
cout << reval_1 << endl;
cout << "变量val2地址:" << &val2 << endl;
cout << "变量val1地址:" << &val1 << endl;
cout << "引用变量reval_1地址:" << &reval_1 << endl;
/*结果说明:val2的值被赋值到reval_1,reval_1还是val1的引用*/
return 0;
}

5.2const的引用(分析下面几段代码得出结论)
代码①
首先我们应该知道:int&并不是一种类型,引用变量的类型是原始变量的数据类型,不涉及原始变量数据类型前面的修饰权限。(下面代码编译不过的原因即是如此)
#include<iostream>
using namespace std;
int main()
{
const int x = 10;
//int& y = x;
const int& y = x;
return 0;
}
对于int& y = x; 其中x被const修饰是只读的,y的类型只与x的类型有关而与x前面的权限修饰无关,x的类型是int所以y的类型也是int即可读又可写。因此x被const修饰只能读不能修改,而作为别名的y既可读又可修改所以编译不通过。只需要将int& y = x;前面加上const修饰即可编译通过。
代码②
#include<iostream>
using namespace std;
int main()
{
int x = 1;
const int& y = x;
return 0;
}
对于这一段代码是可以编译通过的,因为x是可读可写的,引用变量y是只读的,不会产生影响。
代码③
#include<iostream>
using namespace std;
int main()
{
const int a = 11;
int b = 22;
const int* pa1 = &a;
int* pa2 = pa1;/*编译不通过*/
int* pb1 = &b;
const int* pb2 = pb1;
return 0;
}
对于这一段代码中间不通过的原因是:const修饰的是指针所指向的地址上存的内容,pa1和pa2所指向的都是a的地址,a地址中存储的内容被const修饰内容是只读的,但pa2并没有被const修饰对a地址里面的内容可读可写,所以编译不通过。
总结:对于引用取别名或指针指向时,变量访问的权限可以缩小,但是不能放大。
代码④
#include<iostream>
using namespace std;
int main()
{
int x = 0;
/*隐式类型转换*/
double y = x;
/*不能编译过*/
//double& z = x;
//float& z = x;
/*可以编译过*/
const double& z = x;
const float& z = x;
return 0;
}
对于这一段代码中间不能编译过的原因分析:
例如double y = x进行隐式类型转换时编译器会开辟一个double类型的临时变量,这个临时变量具有常性(临时变量前面有const修饰),x的值首先会赋值给这个临时变量,最后由临时变量再赋值给y。所以double& z = x;不能编译过的原因也就是对中间临时变量的权限放大了(由只读变为可读可写),因此需要在double& z = x;前面加上const后才能被引用。结合下图可以更直观的理解。

5.3引用做参数
#include<iostream>
using namespace std;
void c_swap(int* val1, int* val2)
{
int temp = *val1;
*val1 = *val2;
*val2 = temp;
}
void cpp_swap(int& val1, int& val2)
{
int temp = val1;
val1 = val2;
val2 = temp;
}
int main()
{
int x = 10;
int y = 20;
c_swap(&x, &y);
cout << x << " " << y << endl;
cpp_swap(x, y);
cout << x << " " << y << endl;
return 0;
}

这段代码中cpp_swap函数中的参数都是引用的,引用必须进行初始化,这里的初始化是在传参的时候进行的初始化,在传参时val1只是变量x的别名,val2只是变量y的别名,在函数cpp_swap的栈帧中并没有为val1和val2开辟新的空间,在传参时val1的地址和x的地址相同,val2的地址和y的地址相同,所以可以交换成功。
总结:传参的三种方式:传值,传参,传引用。(传值达不到这里交换的效果)
引用做参数的优势:①可以做输出型参数(参数输入给函数,函数在内部改变参数,函数执行结束后外面可以得到这种变化)②可以提高传参效率。
5.4引用做返回值
在函数中,普通的值作为返回值类型在返回时,会再开辟一个临时变量,将返回值拷贝到临时变量中,再将临时变量中的值返回。而引用作为返回值时,不会再开辟一个临时变量来存储返回值,直接为返回值取一个别名,再将这个别名返回。具体细节可见下图。

总结引用变量做返回值和普通变量做返回值的根本区别:返回值是否产生临时变量,最根本区别在于临时变量具有常性。
5.5引用做返回值的不安全性分析
思考一下:上面代码如果返回值变量sum前面不加static会有什么样的后果?
①考虑下面代码:
#include<iostream>
using namespace std;
int& sum1(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& val = sum1(1, 2);
cout << "val = " << val << endl;
return 0;
}

为什么会返回这样一个随机值:因为main()函数第一行调用sum1(1,2)时创建一个sum1(1,2)的栈帧,这个函数栈帧中有三个变量a,b,c。此时val是返回值变量c的一个引用,即val是变量c的一个别名。当这个函数return返回之后该函数对应的栈帧空间就被系统回收了(这个栈帧空间不能被使用但是本身是否还存在具体跟编译器有关),但是val此时仍是这个已被回收的栈帧中变量c所在空间的一个别名,所以再打印val的值就会出现一些随机的值或者还是正确的结果3(因为有的编译器函数对应的栈帧空间就被系统回收了之后,系统并没完全销毁这个栈帧)。这个结果说明当返回变量是一个局部变量时引用返回并不安全。
②考虑下面代码
#include<iostream>
using namespace std;
int& sum1(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& val = sum1(1, 2);
sum1(3, 4);
cout << "val = " << val << endl;
return 0;
}
这句代码在另一个不同的编译器上执行的结果是7,原因是这个编译器函数对应的栈帧空间就被系统回收了之后,系统并没完全销毁这个栈帧,但是此时c的结果已经变了。
③下面这个代码解决这个问题
#include<iostream>
using namespace std;
int& sum1(int a, int b)
{
static int c = a + b;
return c;
}
int main()
{
int& val = sum1(1, 2);
cout << "val = " << val << endl;
return 0;
}

加上static就能解决这个问题。原因是static修饰的变量在程序的运行期间只被初始化一次,并且static修饰的变量不在程序进程的这个函数栈帧中而是在这个进程内存空间的另外一片叫做数据段的区域,所以函数的栈帧被销毁后并不影响这个在数据段中的返回值,更不影响val对这个在数据段中的返回值的引用。
代码①和③在内存中的具体表示:

对不安全性的总结:一个函数要想使用引用返回,返回变量在这个函数的调用结束系统删除这个函数的栈帧后(出了函数的作用域)依然还存在,就可以使用引用返回,否则不安全。比如全局变量、静态变量等等。
函数使用引用返回的优点:少创建拷贝一个临时变量,提高效率。
5.6引用与指针
能用引用的地方大多数都能使用指针。
1.在语法概念上,b就是a的别名,b不会开辟空间,但是在底层编译器实现时是用指针实现的。如下面代码与汇编代码:


2.在概念上引用只是一个别名不占内存空间,但是指针存储变量的地址占用内存空间。引用必须进行初始化但是指针变量可以先不用初始化。
3.引用在成为一个变量的别名后就不能再通过赋值修改为其他变量的别名,但是指针可以先指向a变量再通过修改指向b变量。
4.没有多级引用但有多级指针。
5.指针永远是4个字节,但是引用不一定是4个字节具体跟引用的对象的类型有关。
6.内联函数
观察这段代码:
#include<iostream>
using namespace std;
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10, b = 20;
swap(a, b);
return 0;
}
在许多场景中都会频繁调用swap用来交换两个数,比如在排序算法中每次需要交换数值时都调用一次swap,每一次调用时都需要建立栈帧,返回时销毁栈帧,因此频繁调用swap是有消耗的,那么如何解决这个问题呢?
1.C语言中使用宏函数来解决,如果调用函数时不想创建栈帧那么就直接把函数定义成宏函数。宏函数在预处理阶段调用的地方直接就展开了。但是宏有很多缺点比如可读性差、不能调试等。
2.C++使用内联函数来解决。在这个函数的前面加上一个inline之后这个函数在调用它的地方展开。
#include<iostream>
using namespace std;
inline void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10, b = 20;
swap(a, b);
return 0;
}
这段代码的汇编:

这里需要调一下工程的属性:


调完之后再看一下这段代码的汇编:

注意:内联函数是一种空间换时间的优化策略,用了更多的代码空间但没有函数栈帧建立和销毁的开销。因此递归或者代码比较长的函数不适合用内联函数,一般内联适用于小函数。内联对编译器只是一个建议,编译器会自动优化,如果代码较长(大于二三十行左右)或者有递归等编译器会忽略掉内联。此外,内联函数没有地址,因为内联函数再调用的地方直接展开了,所以内联函数的声明和定义不能够分离,链接时会链接不上。
C++中建议宏定义常量用const来替换,宏函数用内联函数替换。
#define N 10 --> const int N = 10;
void swap(int& a,int& b){......} --> inlinevoid swap(int& a,int& b){......}
7.auto关键字
auto关键字是C++11中的。作用是自动推导变量类型。

auto常见的不能使用的场景:
1.auto可以在同一行定义(自动推导)多个类型相同的变量,但是不能在同一行定义含有不同类型的变量。

2.auto不能作为函数的形参,编译器无法对函数的形参类型自动推导。

3.auto不能应用于自动推导数组的类型。

auto常见的应用场景:
以后可能有一个类型很长的变量进行声明这是就可以直接将类型写成auto让编译器自动推导变量类型。

8.基于范围的for循环
#include<iostream>
using namespace std;
int main()
{
//C语言中的for循环
int arr[] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
arr[i]++;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout<<arr[i]<<" ";
}
cout << endl;
//C++11中基于范围的for循环
for (auto i : arr)
{
i++;
}
for (auto i : arr)
{
cout << i << " ";
}
cout << endl;
return 0;
}

这里因为每个auto依次把数组arr中的值取出来赋值给i,i的值的改变并不会影响数组中的值的改变。所以解决的方法是将i变为引用的。
int main()
{
//C语言中的for循环
int arr[] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
arr[i]++;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout<<arr[i]<<" ";
}
cout << endl;
//C++11中基于范围的for循环
for (auto& i : arr)
{
i++;
}
for (auto i : arr)
{
cout << i << " ";
}
cout << endl;
return 0;
}

一个注意点:对于一个函数如果参数是数组,在传参之后数组会退化成指针,因此就不能在函数体中使用范围for循环啦。

9.空指针nullptr
C++11中推荐使用nullptr作为空指针。
/*C语言中的空指针*/
int* p_c1 = NULL;
int* p_c2 = 0;
/*C++11中的空指针*/
int* p_cpp = nullptr;
在C语言的头文件(stddef.h)中,NULL是被定义了的两个宏(如下代码),NULL可能被定义成常量0,也可能被定义成空指针常量void*类型的0。
#define NULL 0
#define NULL ((void *)0)

上面的两个函数参数不同构成重载,这里func(NULL)目的是想要调用func(int*)但是这里被预处理为func(0),所以在C++11中使用func(nullptr)调用预处理后变为func(int*)更好的展示了调用的是一个空指针类型的函数(可以理解为预处理后变为func((void*)0))。
更多推荐



所有评论(0)