赞
踩
C语言是结构化和模块化的语言,适合处理较小规模的程序,对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,在20世纪80年代,计算机界提出OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言就此诞生。
1982年,Bjarne Stroustrup 博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达这个语言与C语言的渊源关系,命名为C++(C plus plus).因此C++ 是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
1979年,贝尔实验室的本贾尼等人试图分析 unix内核 的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为 C with classes。
先看看C++的历史版本。
阶段 | 内容 |
---|---|
C with classes | 类及派生类、公有和私有成员、类的构造和析构、友元、内联函数、赋值运算符重载等 |
C++1.0 | 添加虚函数概念,函数和运算符重载,引用、常量等 |
C++2.0 | 更加完善支持面向对象,新增保护成员、多重继承、对象的初始化、抽象类、静态成员以及const成员函数 |
C++3.0 | 进一步完善,引入模板,解决多重继承产生的二义性问题和相应构造和析构的处理 |
C++98 | C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会的认可,以模板方式重写C++标准库,引入了STL(标准模板库) |
C++03 | C++标准第二个版本,语言特性无大改变,主要是修订错误,减少多异性 |
C++05 | C++标准委员会发布了一份计数报告(Technical Report , TR1),正式更名C++0x,即:计划在本世纪第一个10年的某个时间发布 |
C++11 | 增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等 |
C++14 | 对C++11的扩展,主要是修复C++11中的漏洞以及改进,比如:泛型的lambda表达式,auto的返回值类型推导,二进制字面常量等 |
C++17 | 在C++11上做了一些小幅改进,增加了19个新特性,比如:static_assert () 的文本信息可选,Fold 表达式用于可变的模板,if 和 switch 语言中的初始化器等 |
C++20 | 自 C++11 以来最大的发行版,引入了许多新的特性,比如:**模板(Modules)、协程(Coroutines)、范围(Ranges)、概念(Constraints)**等重大特性,还有对已有特性的更新:比如:Lambda支持模板、范围for支持初始化等 |
C++23 | 制定中 |
C++总计63个关键字,C语言有32个关键字。
接下里让我们看看C++的关键字:
当然这里包括32个C语言的关键字
在C/C++中,变量、函数及后面要学习到的类都是大量存在的,这些变量、函数和类的名称都将存在于全局作用域中,可能会导致很多冲突。 这些命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
比如:
#include <stdio.h>
#include <stdlib.h>
int rand = 100;
int main()
{
printf("%d\n", rand);
return 0;
}
编译后报错:error C2365 :“rand” :重定义;以前的定义是“函数”
C语言没办法解决类型这样的命名冲突问题,所以C++提出了 namespace 来解决。
定义命名空间,需要使用到namespace 关键字,后面跟命名空间的名字,然**后加上一对 { }**即可,{ } 中为命名空间的成员。
1.一般的命名空间定义
//一般的命名空间定义 namespace N { //命名空间中可以定义变量、函数、类型 int i = 1; int Add(int x, int y) { return x + y; } struct Node { int data; struct Node* next; }; }
2.命名空间的嵌套
namespace H1 { int i; int Div(int x, int y) { return x / y; } namespace H2 { int j; int Mul(int x, int y) { return x * y; } } }
3.同一个项目中允许存在多个相同名称的命名空间,编译器最后会合并成一个命名空间中。
注意:一个命名空间就表示定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
我们前面已经学习了命名空间的定义,现在来学习一下命名空间的使用。命名空间中成员该如何使用呢?
命名空间的使用有三种方式:
//命名空间名称 H #include <stdio.h> namespace H { int i; double d; } int main() { //命名空间名称 :: 命名空间中的成员名 H::i = 6; //给命名空间中成员赋值为6 H::d = 3.14; //给命名空间中成员赋值为3.14 printf("%d\n", H::i); printf("%lf\n", H::d); return 0; }
//using 将命名空间中的某个成员引入 #include <stdio.h> namespace H { int i; double d; } //将命名空间中的成员 i 引入 using H::i; int main() { i = 12;//为命名空间成员a赋值12 printf("%d\n", i);//打印命名空间成员 i return 0; }
#include <stdio.h> namespace H { int a; double d; } //using namespace 命名空间名称,将命名空间H中的全部成员引入 using namespace H; int main() { a = 12; d = 3.14; printf("%d\n", a); printf("%lf\n", d); return 0; }
计算机语言与新生儿一样,在刚出来的时候都会向这个世界进行问候。C++也是如此。那么C++是如何实现问候这个形式呢?
#include <iostream>
using namespace std;
//std 是C++标准库的命名空间名称,C++将标准库的定义实现都放到这个命名空间中
int main()
{
cout << "hello world" << endl;
return 0;
}
说明:
1.使用cout标准输出对象(控制台) 和 cin 标准输入对象(键盘)时,必须包含 "iostream " 头文件以及按命名空间使用方法使用std。
2.cout 和 cin 是全局的流对象,endl 是特殊的C++符号, 表示换行输出,它们都包含在包含iostream头文件中。
3.符号“ << “ 是流插入运算符 , 而 ” >> “ 是流提取运算符。
4.C++中的输入输出相对于C语言的输入输出更加方便,C语言中的printf与scanf输入输出时需手动控制格式,如:%d 表示整型,%f表示float类型;而C++的输入输出可以自动识别变量类型。
5.实际上cout 和 cin 分别是ostream 和 istream 类型的对象, >> 和 << 也涉及运算符重载等知识。
注意: 早期标准库将所有的功能在全局域中实现,声明放在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std 命名空间下,为了和C的头文件区分,也为了正确使用命名空间,规定C++ 头文件不带 .h ;旧编译器(VC
6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用 + std 的方式。
#include <iostream> using namespace std; int main() { int a; char c; double d; //自动识别变量的类型 cin >> a;//整型 cin >> c >> d;//字符型c 浮点型d cout << a << " " << c << " " << d << endl;//endl表示输出一个换行符和‘\n’功能一样 return 0; }
std 命名空间的使用:
std 是C++标准库的命名空间,如何展开std 进行使用更加合理?
using namespace std 展开,标准库会全部暴露出来,如果我们定义跟库重名的类型/对象/函数,就存在冲突。所以在项目开发中使用,可以像std::cout 这样,使用时指定 命名空间+ using std::cout 展开常用的库对象/类型等方式。
缺省参数是声明 或 定义函数时为函数的参数指定一个缺省值(默认值)。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
#include <iostream> using std::cout; using std::endl; void Func(int x = 20) { cout << "x = " << x << endl; } int main() { Func();//这里没有传参过去,所以使用的是参数的缺省值(默认值) Func(100);//这里传参过去,使用指定的实参 return 0; }
函数的全部参数都使用缺省值。
#include <iostream>
using namespace std;
void Func(int x = 10, int y = 20, int z = 30)
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
}
函数的部分形参使用了缺省值。
#include <iostream>
using namespace std;
void Func(int x , int y = 20, int z = 30)
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
}
注意:
1.半缺省参数必须从右往左依次给出,不能间隔着给。
//错误写法
void Func(int x ,int y = 10 , int z)
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
}
2.缺省参数不能在函数声明和定义中同时出现。
#include <iostream>
using namespace std;
//错误写法
//test.h
void Func(int x ,int y ,int z = 10);
//test.cpp
void Func(int x ,int y , int z = 10)
{
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "z = " << z << endl;
}
特殊情况:
//test.h
void Func(int x = 10);
//test.cpp
void Func(int x = 1)
{
// ....
}
注意:如果声明和定义位置同时出现,恰巧两个位置提供的值不同,那么编译器就无法确定到底该使用哪一个缺省值。所以避免声明和定义同时给缺省值。
3.缺省值必须是常量或者全局变量
4.C语言不支持(编译器不支持)
自然语言中,一个词可以有多重含义人们可以通过上下文来判断该词真实的含义,即该词被重载了。
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些函数的形参列表(参数个数 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
比如:
#include <iostream> using namespace std; int Add(int x, int y) { cout << "int Add(int x, int y)" << endl; return x + y; } double Add(double x, double y ) { cout << "double Add(int x, int y)" << endl; return x + y; } int main() { cout << Add(1, 2) << endl; cout << Add(3.1, 4.5) << endl; return 0; }
1.参数个数不同
#include <iostream>
using namespace std;
int Add(int x, int y)
{
cout << "int Add(int x, int y)" << endl;
return x + y;
}
int Add(int x, int y ,int z)
{
cout << "int Add(int x, int y ,int z)" << endl;
return x + y + z;
}
2.参数类型不同
#include <iostream>
using namespace std;
void Func(int x, double y)
{
cout << "void Func(int x, double y)" << endl;
}
void Func(char c , int y)
{
cout << "void Func(char c, double y)" << endl;
}
3.参数顺序类型不同
#include <iostream>
using namespace std;
void Func(int x, double y)
{
cout << "void Func(int x, double y)" << endl;
}
void Func(double x, int y)
{
cout << "void Func(char c, double y)" << endl;
}
同名函数的参数个数、参数类型、参数类型顺序中只要有一个不同,就可构成重载
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历这些阶段:预处理、编译、汇编、链接。
在编译阶段会把程序中的每个源文件的全局范围的变量分别进行汇总,在汇编阶段会把每个源文件汇总出来的符号分配一个地址,如果该符号只是一个声明,则给它分配一个无意义的地址,然后分别生成一个符号表,最后在链接时会将每个源文件的符号表进行合并,若不同源文件的符号表中出现相同的符号,则取合法的地址为合并后的地址(重定位)。
C语言中,汇编阶段进行符号表汇总时,一个函数汇总后的符号就是它的函数名,所以当汇总时发现多个相同的函数符号时,编译器就会报错。C++在进行符号汇总时,对函数的名字进行修饰,函数汇总的符号不再只是函数名,而是通过其参数的个数、类型与顺序等信息汇总出一个符号,如此一来,就算是函数名相同的函数,只要它的参数类型或参数个数或参数顺序不同,那么汇总出的符号也就不同。
结论:
C语言不支持函数重载,是因为没法区分同名函数。
C++是通过函数修饰规则进行区分的,只要函数的形参列表不同,生成的符号不同,也就可以支持重载。
还有就是如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的,因为编译器没办法区分
引用不是新定义一个变量,而是给已存在变量去一个别名,编译器不会给引用变量开辟内存空间,它与它引用的变量共用同一块内存空间。
使用形式 : 类型 & 引用变量名(对象名) = 引用实体;
#include <iostream>
using namespace std;
int main()
{
int x = 10;
//& 引用操作符
int& y = x;//y 是 x 的别名
cout << x << endl;
cout << y << endl;
printf("%p\n", &x);//打印x的地址
printf("%p\n", &y);//打印y的地址
return 0;
}
注意:引用类型必须和引用实体是同一个类型。
1.引用在定义时必须初始化
int a = 10;
int& ra= a;
2.一个变量可以有多个引用
int a = 10;
int& b = a;
int& c = a;
int &d = b;//为别名取别名也是可以的
3.引用一旦引用一个实体,就不能引用其他实体
#include <iostream> using namespace std; void Ref() { int a = 0; //int& b;//err ,没有初始化 int& b = a;//b是a的别名 int c = 20; b = c;//这里不涉及引用,这里只是赋值,把c的值赋值给b,也就是给a printf("%p %p %p\n", &a, &b, &c);//a b c共用同一块内存空间 } int main() { Ref(); return 0; }
#include <iostream> using namespace std; int main() { const int a = 10; //int& ra = a;//err ,a为常量 const int& ra = a;// ok //int& b = 10; const int& b = 10; double d = 3.12; //int& rd = d;//err,类型不同 const double& rd = d; return 0; }
1.做参数
C语言中,交换两个数据是使用指针来实现的,现在我们学习了引用,我们交换数据的时候可以不用指针也可以实现。
//x 和 y是 a 和 b的别名 #include <iostream> using namespace std; void Swap(int& x, int& y) { int tmp = x; x = y; y = tmp; } int main() { int a = 10; int b = 20; Swap(a, b); cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
2.做返回值
#include <iostream> using namespace std; int& Count() { static int n = 0; n++; //... return n; } int main() { int ret = Count(); cout << ret << endl; return 0; }
接下来看一段代码:
int& Add(int a, int b)
{
int sum = a + b;
return sum;
}
int main()
{
int& ret = Add(3, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;//7
return 0;
分析:
Add函数第一次运行:Add函数运行结束后,该函数对应的栈空间就被回收了,即C变量就没有意义了,在main中ret引入Add函数返回值,实际应用的就是一块已经被释放的空间,。
Add函数第二次运行:Add函数运行结束后,该函数对应的栈空间就被回收了,即sum变量就没有意义了,注意空间被回收指的是空间不能使用了,但是空间本身还在,而ret引用的ret位置被修改成了7了,因此ret的值就改变了。
注意:
1.函数运行时,系统需要给该函数开辟独立的栈空间,用来保存函数的形参、局部变量及一些寄存器信息等。
2.函数运行结束后,该函数对应的栈空间就被系统回收了
3.空间被回收指该块栈空间暂时不饿能使用,但是内存还在。
结论:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回;如果已经还给系统了,则必须使用传值返回。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或返回值类型非常大时,效率就更低。
1.以值和以引用作为函数参数进行比较,可以得到传引用的效率更高些。
#include <iostream> using namespace std; #include <time.h> struct N { int arr[10000]; }; void Func1(N n) { //... } void Func2(N& n) { //... } void RefAndValue() { N n; //以值作为函数参数 size_t begin1 = clock(); for (size_t i = 0; i < 10000; i++) Func1(n); size_t end1 = clock(); //以引用作为函数参数 size_t begin2 = clock(); for (size_t i = 0; i < 10000; i++) Func2(n); size_t end2 = clock(); //计算两个函数运行结束的时间 cout << "Func1(N) time:" << end1 - begin1 << endl; cout << "Func2(N&) time:" << end2 - begin2 << endl; } int main() { RefAndValue(); return 0; }
2.值和引用作为返回值类型的性能比较
#include <iostream> using namespace std; #include <time.h> struct N { int arr[10000]; }; N n; //值返回 N Func1() { return n; } //引用返回 N& Func2() { return n; } void ReturnByRefOrValue() { //以值作为函数的返回值类型 size_t begin1 = clock(); for (size_t i = 0; i < 10000; i++) Func1(); size_t end1 = clock(); //以引用作为函数作为返回值类型 size_t begin2 = clock(); for (size_t i = 0; i < 10000; i++) Func2(); size_t end2 = clock(); //计算两个函数运行结束的时间 cout << "Func1(N) time:" << end1 - begin1 << endl; cout << "Func2(N&) time:" << end2 - begin2 << endl; } int main() { ReturnByRefOrValue(); return 0; }
结论:通过上述代码的比较,传值和引用在作为参数以及返回值类型上效率相差很大。
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用一块空间。
int main()
{
int i = 10;
int& j = i;
cout << "&i = " << &i << endl;
cout << "&j = " << &j << endl;
return 0;
}
在底层实现中实际是有空间的,因为引用是按照指针方式来实现的。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& ra = a;
int* pa = &a;
*pa = 10;
cout << ra << endl;
cout << *pa << endl;
return 0;
}
引用和指针的区别:
1.引用概念上是定义一个变量的别名,指针式存储一个变量的地址。
2.引用在定义时必须初始化,指针则没有要求
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4.没有NULL引用,但是有NULL指针
5.在sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(在32位平台下占用4个字节,64位平台下占用8个字节)
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7.有多级指针,但是没有多级引用
8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
9.引用比指针使用起来相对更安全。
以inline 修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int ret = 0;
ret = Add(2, 4);
return 0;
}
从上图运行的反汇编代码中看出,内联函数调用时没有调用函数这个过程的汇编指令。
内联函数,在编译期间编译器会用函数体替换函数的调用。
1.inline 是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。
缺陷:可能会使目标文件变大;
优势:少了调用开销,提高程序运行效率。
2.inline 对于编译器而言只是一个建议,不同编译器关于inline 实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline 修饰,否则编译器会忽略inline特性。
3.inline 不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
#include <iostream> using namespace std; //Fun.h inline void func(int i); //Fun.cpp #include "Fun.h" void func(int i) { cout << i << endl; } //main.cpp #include "Fun.h" int main() { func(20); return 0; }
编译会出现链接错误。
随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:
1.类型难于拼写;
2.含义不明确导致容易出错。
在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的类型,但是有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义。
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部边, 但是遗憾的是一直没有人去使用它。
C++11中,标准委员赋予了auto全新的含义即:auto 不是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
#include <iostream> using namespace std; int TestAuto() { return 8; } int main() { int a = 10; auto b = a; //自动推导类型为int型 auto c = 'e'; //自动推导类型为char型 auto d = "3.13"; //自动推导类型为double型 auto e = TestAuto();//自动推导类型为int型 cout << typeid(b).name() << endl;//打印变量b的类型 cout << typeid(c).name() << endl;//打印变量c的类型 cout << typeid(d).name() << endl;//打印变量d的类型 cout << typeid(e).name() << endl;//打印变量e的类型 return 0; }
“auto h; ” 这种写法是无法通过编译,使用auto 定义变量时必须对它进行初始化。
【注意】使用auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种”类型“的声明,而是一个类型声明时的“占位符”,编译器在编译期间会将auto替换为变量实际的类型。
1.auto 与 指针 和 引用结合起来使用
用auto声明指针类型时,用 auto 和 auto * 没有任何区别,但是auto声明引用类型时必须加&。
#include <iostream> using namespace std; int main() { int x = 10; auto a = &x; auto* b = &x; auto& c = x; cout << typeid(a).name() << endl;//int* cout << typeid(b).name() << endl;//int* cout << typeid(c).name() << endl;//int *a = 20; *b = 30; c = 40; cout << *a << endl;//40 cout << *b << endl;//40 cout << c << endl;//40 return 0; }
2.在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto()
{
auto a = 3, b = 4;
auto c = 5, d = 3.13;// err ,这行代码会编译失败,因为c和d的初始化表达式类型不一致
}
1.auto不能作为函数的参数
因为编译器无法对使用auto作为形参类型的变量推导出它的实际类型。比如:下面这段代码:
//这段代码会编译失败,auto不能作为形参类型,因为编译器无法对n的实际类型进行推导
void TestAuto(auto n){}
2.auto不能直接用来声明数组
void Test()
{
int a[] = { 2,4,6,8,10 };
auto b[] = { 1,2,3,4 };//编译失败,提示:auto”类型不能出现在顶级数组类型中
}
3.为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法。
4.auto在实际中最常见的优势用法就是跟以后学到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
在C++98中如果要遍历一个数组,可以按照以下方式进行:
void TestFor()
{
int arr[] = { 1,2,3,4,5,6 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
arr[i] += i;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
cout << arr[i] << " ";
}
cout << endl;
}
对于一个有范围的集合而言,循环的范围是多余的,有时候还会容易犯错。因此C++11中引入了基于范围的for循环,for循环后的括号由冒号 ” : “ 分为两部分:第一部分是范围内用于迭代的变量,第二部分则是表示被迭代的范围。
void TestFor() { int arr[] = { 1,2,3,4,5,6 }; //将数组中所有元素+1 for (auto& a : arr) { a += 1; } //打印数组中的全部元素 for (auto a: arr) { cout << a << " "; } cout << endl; } int main() { TestFor(); return 0; }
注意:
与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围。对于类而言,应该提供 begin 和 end 的方法,begin 和 end 就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定。
//这里arr只是数组首元素的地址,并不是一个范围
void TestFor(int arr[])
{
for (auto& a : arr)
cout << a << endl;
}
2.迭代的对象要实现++ 和 ==的操作。
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,哦我们基本都是按照如下方式对其进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
//......
}
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面量0,或者被定义为无类型指针(void)的常量*,无论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦。比如:
void func(int) { cout << "func(int)" << endl; } void func(int*) { cout << "func(int*)" << endl; } int main() { func(0);//打印结果;func(int) func(NULL);//打印结果:func(int) func((int*)NULL);//打印结果:func(int*) return 0; }
程序本意是想通过func(NULL)调用指针版本的func(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强制类型转换成 (void*)0。
注意:
1.在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的。
2.在C++11中,sizeof(nullptr) 与 sizeof( (void) 0) 所占用的字节数相同。
3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
=============================================
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。