你知道C++多少——C++入门(祖师爷爆改C语言)

🌈个人主页:小新_-

🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝

🏆所属专栏:话说那些与C++的爱恨情仇    欢迎订阅,持续更新中~~~​​​​​​​

                

                                    ✨让小新带着你快乐的学习吧~✨

目录

一、话说C++的那些事

1、C++发展历史

2、C++的使用占比

二、命名空间(爆改一)

1、命名空间的定义

2、命名空间的使用

3、 展开命名空间查找顺序

三、缺省函数(爆改二)

1、缺省函数概念

2、缺省函数的分类

四、函数重载(爆改三)

1、 函数重载的定义

2、 函数重载的实现原理

五、引用(爆改四)

1、引用的概念

2 、引用特性

3 、常引用

4、 使用场景

5 传值、传引用效率比较

5.1值和引用的作为返回值类型的性能比较

5.2 值和引用的作为返回值类型的性能比较

6.引用和指针的区别

六、C++输⼊&输出(爆改五)

七、 内联函数(爆改六)

1、 概念

2 特性

八、 auto关键字(C++11)(爆改七)

1 类型别名思考

2 auto简介

3 auto的使用细则

九、 基于范围的for循环(C++11)(爆改八)

1、 范围for的语法

2 、范围for的使用条件

十、 指针空值nullptr(C++11)(爆改九)

C++98中的指针空值


一、话说C++的那些事

1、C++发展历史

首先,我们看看祖师爷的风采(记住这张脸,就是这个人发明了C++)

丹麦计算机科学家Bjarne Stroustrup在贝尔实验室创建。最初,这种语言被命名为“C with Classes”,因为它是在C语言的基础上增加了类和其他面向对象编程的特性。后来,这种语言被正式命名为C++,象征着它是C语言的增强版 。

C++的发展经历了几个重要阶段:

  • 早期阶段:在1980年代至1995年期间,C++主要是作为一种传统的面向对象语言,它在工业界的开发语言中占据了一席之地。
  • 标准化阶段:从1995年到2000年,C++引入了标准模板库(STL),泛型编程开始在C++中占据越来越重要的地位。
  • 现代阶段:自2000年以来,C++通过引入产生式编程和模板元编程等新技术达到了一个新的发展高峰。这些新技术的出现以及与原有技术的融合,使C++成为当今主流编程语言中最复杂的成员之一 

    2、C++的使用占比

    TIOBE 编程语言社区排行榜是编程语言流行趋势的一个指标,每月更新,这份排行榜排名基于互联网上有经 验的程序员、 课程和第三方厂商的数量。排名使用著名的搜索引擎(诸如 Google 、 MSN 、 Yahoo! 、 Wikipedia、 YouTube 以及 Baidu 等)进行计算。 注意: 排名不能说明那个语言好,那个不好,每门编程语 言都有适应自己的应用场景

    二、命名空间(爆改一)

    命名空间是什么呢?命名空间是一种编程语言中用来组织代码的机制,它允许开发者将一组相关的符号(如变量、函数、类等)分组在一起,使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,从而更好地管理和避免命名冲突。

    #include #include int rand = 10 ; // C 语言没办法解决类似这样的命名冲突问题,所以 C++ 提出了 namespace 来解决 int main () { printf ( "%d\n" , rand ); return 0 ; } // 编译后后报错: error C2365: “rand”: 重定义;以前的定义是 “ 函数 ”

    1、命名空间的定义

    关键字:namespace

    规则:namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}

    中即为命名空间的成员。(风间还不明白,那好吧,给你举个例子,呜~~~)
    // 命名空间的名字,一般开发中是用项目名字做命名空间名。
    // 练习用自己名字缩写即可,如张三:zs
    // 1. 正常的命名空间定义
    namespace zs
    {
     // 命名空间中可以定义变量/函数/类型
     int rand = 10;
     int Add(int left, int right)
     {
     return left + right;
     }
    struct Node
     {
     struct Node* next;
     int val;
     };
    }
    //2. 命名空间可以嵌套
    // test.cpp
    namespace N1
    {
    int a;
    int b;
    int Add(int left, int right)
     {
         return left + right;
     }
    namespace N2
     {
         int c;
         int d;
         int Sub(int left, int right)
         {
             return left - right;
         }
     }
    }
    //3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
    // ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
    // test.h
    namespace N1
    {
    int Mul(int left, int right)
     {
         return left * right;
     }
    }
    注意: 一个命名空间就定义了一个新的作用域 ,命名空间中的所有内容都局限于该命名空间中

    2、命名空间的使用

    我们以上面定义的命名空间来说明,如果我们按照之前C语言的方式会报错,有如下三种使用方式

    • 加命名空间名称及作用域限定符

      int main()

      {

         printf("%d\n", zs::a);

         return 0;    

      }

      • 使用using将命名空间中某个成员引入
        using zs::b ;

        int main()

        {     printf ( "%d\n" , zs ::a );     printf ( "%d\n" , b );     return 0 ;     }
        • 使用using namespace 命名空间名称 引入
          using namespce N int main () {     printf ( "%d\n" , N::a );     printf ( "%d\n" , b );     Add ( 10 , 20 );     return 0 ;     }

          3、 展开命名空间查找顺序

          编译默认查找 a、当前局部域 b、全局域找 b、到展开的命名空间中查找 c、不同域可以定义同名的变量/函数/类型

          三、缺省函数(爆改二)

          1、缺省函数概念

          缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实 参,则采⽤该形参的缺省值,否则使⽤指定的实参。
          void Func ( int a = 0 ) { cout << a << endl ; } int main () { Func ();     // 没有传参时,使用参数的默认值 Func ( 10 );   // 传参时,使用指定的实参 return 0 ; }

          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 ; }

              注意:

              1. 半缺省参数必须从右往左依次来给出,不能间隔着给
              2. . 缺省参数不能在函数声明和定义中同时出现
              //a.h   void Func ( int a = 10 );   // a.cpp   void Func ( int a = 20 ) {}   // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该 用那个缺省值。

                   3. 缺省值必须是常量或者全局变量

                   4. C语言不支持(编译器不支持)

              四、函数重载(爆改三)

              1、 函数重载的定义

              函数重载是指在一个程序中,允许存在多个同名函数,但这些函数的参数列表(包括参数的数量、类型或顺序)必须有所不同。编译器可以根据调用时传递的实际参数来确定应该调用哪个函数。

              假设我们想要定义两个函数,一个用于打印整数,另一个用于打印字符串。如果我们使用函数重载,我们可以这样做:

              #include

              using namespace std;

              void print(int num) {

                  cout << "Printing an integer: " << num << endl;

              }

              void print(string str) {

                  cout << "Printing a string: " << str << endl;

              }

              int main() {

                  print(123); // 输出 "Printing an integer: 123"

                  print("Hello, World!"); // 输出 "Printing a string: Hello, World!"

                  return 0;

              }

              在这个例子中,尽管`print()`函数有多个版本,但由于参数列表不同,编译器能够正确地识别出我们要调用的函数。

              ### 注意事项

              在使用函数重载时,需要注意以下几点:

              1. 函数重载的参数列表必须不同,包括参数数量、类型或顺序。

              2. 函数重载与函数的返回类型无关,因此不能通过返回类型来区分重载函数。

              3. 在使用函数重载时,应避免产生二义性,即不应有两个函数在相同的调用情况下可以被选择

              2、 函数重载的实现原理

              为什么 C++ 支持函数重载,而 C 语言不支持函数重载呢? 在 C/C++ 中,一个程序要运行起来,需要经历以下几个阶段: 预处理、编译、汇编、链接

              五、引用(爆改四)

              1、引用的概念

              引用 不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空 间,它和它引用的变量 共用同一块内存空间。

              就比如蜡笔小新,小新就是新之助,新之助就是小新。C语言中的指针太过繁琐和难于理解,祖师爷就设置了引用这一概念

              类型 & 引用变量名 ( 对象名 ) = 引用实体;
              void TestRef () {     int a = 10 ;     int & ra = a ; //<==== 定义引用类型     printf ( "%p\n" , & a );     printf ( "%p\n" , & ra ); }
              注意: 引用类型 必须和引用 实体 是 同种类型 的

              2 、引用特性

              1. 引用在 定义时必须初始化 2. 一个变量可以有多个引用 3. 引用一旦引用一个实体,再不能引用其他实体
              void TestRef () {   int a = 10 ;   // int& ra;   // 该条语句编译时会出错   int & ra = a ;   int & rra = a ;   printf ( "%p %p %p\n" , & a , & ra , & rra );   }

              3 、常引用

              void TestConstRef () {     const int a = 10 ;     //int& ra = a;   // 该语句编译时会出错, a 为常量     const int & ra = a ;     // int& b = 10; // 该语句编译时会出错, b 为常量     const int & b = 10 ;     double d = 12.34 ;     //int& rd = d; // 该语句编译时会出错,类型不同     const int & rd = d ; }

              4、 使用场景

              1. 做参数
              void Swap ( int & left , int & right ) {   int temp = left ;   left = right ;   right = temp ; }
              2、做返回值
              int & Count () {   static int n = 0 ;   n ++ ;   // ...   return n ; }
              下面代码输出什么结果?为什么?
              int & Add ( int a , int b ) {     int c = a + b ;     return c ; } int main () {     int & ret = Add ( 1 , 2 );     Add ( 3 , 4 );     cout << "Add(1, 2) is :" << ret << endl ;     return 0 ; }
              注意: 如果函数返回时,出了函数作用域,如果返回对象还在 ( 还没还给系统 ) ,则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。

              5 传值、传引用效率比较

              以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效 率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
              void Swap ( int & left , int & right ) {   int temp = left ;   left = right ;   right = temp ; } int & Count () {   static int n = 0 ;   n ++ ;   // ...   return n ; } int & Add ( int a , int b ) {     int c = a + b ;     return c ; } int main () {     int & ret = Add ( 1 , 2 );     Add ( 3 , 4 );     cout << "Add(1, 2) is :" << ret << endl ;     return 0 ; }

              5.1值和引用的作为返回值类型的性能比较

              #include struct A { int a [ 10000 ]; }; void TestFunc1 ( A a ){} void TestFunc2 ( A & a ){} void TestRefAndValue () { A a ; // 以值作为函数参数 size_t begin1 = clock (); for ( size_t i = 0 ; i < 10000 ; ++ i ) TestFunc1 ( a ); size_t end1 = clock (); // 以引用作为函数参数 size_t begin2 = clock (); for ( size_t i = 0 ; i < 10000 ; ++ i ) TestFunc2 ( a ); size_t end2 = clock (); // 分别计算两个函数运行结束后的时间 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl ; cout << "TestFunc2(A&)-time: " << end2 - begin2 << endl ; }

              5.2 值和引用的作为返回值类型的性能比较

              #include struct A { int a [ 10000 ]; }; A a ; // 值返回 A TestFunc1 () { return a ;} // 引用返回 A & TestFunc2 (){ return a ;} void TestReturnByRefOrValue () { // 以值作为函数的返回值类型 size_t begin1 = clock (); for ( size_t i = 0 ; i < 100000 ; ++ i ) TestFunc1 (); size_t end1 = clock (); // 以引用作为函数的返回值类型 size_t begin2 = clock (); for ( size_t i = 0 ; i < 100000 ; ++ i ) TestFunc2 (); size_t end2 = clock (); // 计算两个函数运算完成之后的时间 cout << "TestFunc1 time:" << end1 - begin1 << endl ; cout << "TestFunc2 time:" << end2 - begin2 << endl ; }
              通过上述代码的比较,发现 传值和指针在作为传参以及返回值类型上效率相差很大 。

              6.引用和指针的区别

              在 语法概念上 引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
              int main () { int a = 10 ; int & ra = a ; cout << "&a = " <<& a << endl ; cout << "&ra = " <<& ra << endl ; return 0 ; }
              在 底层实现上 实际是有空间的,因为 引用是按照指针方式来实现 的。
              int main () { int a = 10 ; int & ra = a ; ra = 20 ; int* pa = & a ; * pa = 20 ; return 0 ; }
              我们来看下引用和指针的汇编代码对比 引用和指针的不同点 : 1. 引用概念上定义一个变量的别名,指针存储一个变量地址。 2. 引用 在定义时 必须初始化 ,指针没有要求 3. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何 一个同类型实体 4. 没有 NULL 引用 ,但有 NULL 指针 5. 在 sizeof 中含义不同 : 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占 4 个字节 ) 6. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小 7. 有多级指针,但是没有多级引用 8. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理 9. 引用比指针使用起来相对更安全

              六、C++输⼊&输出(爆改五)

              说明:
              1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。
              2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
              3. <<    是流插入运算符,   >>是流提取运算符。
              4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入输出可以自动识别变量类型。
              5.  实际上cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载等知识, 这些知识我们我们后续才会学习,所以我们这里只是简单学习他们的使用。后面我们还会更深入的学习IO流的用法以及原理。
              注意:早期标准库将所有功能在全局域中实现,声明在.h后级的头⽂件中,使⽤时只需包含对应头⽂ 件即可,后来将其实现在std命名空间下,为了和C头⽂件区分,也为了正确使⽤命名空间,规定C+头⽂件不带.h,旧译器(VC 6.0)中还⽀持格式,后续编译器已不⽀持,因此推荐使⽤ +std的⽅式。 std命名空间的使⽤惯例: std是C++标准库的命名空间,如何展开std使⽤更合理呢? 1. 在⽇常练习中,建议直接using namespace std即可,这样就很⽅便。 2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型对象/函数, 就存在冲突问题。该问题在⽇常练习中很少出现,但是项⽬开发中代码较多、规模⼤,就很容易出 现。所以建议在项⽬开发中使⽤,像std::cout这样使⽤时指定命名空间+using std::cout展开常⽤ 的库对象/类型等⽅式。

              七、 内联函数(爆改六)

              1、 概念

              以 inline 修饰 的函数叫做内联函数, 编译时 C++ 编译器会在 调用内联函数的地方展开 ,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。 如果在上述函数前增加 inline 关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的 调用。

              2 特性

              1. inline 是一种 以空间换时间 的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会 用函数体替换函数调用 ,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。 2. inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同 ,一般建 议:将 函数规模较小 ( 即函数不是很长,具体没有准确的说法,取决于编译器内部实现 ) 、 不 是递归、且频繁调用 的函数采用 inline 修饰,否则编译器会忽略 inline 特性。下图为 《 C++prime 》第五版关于 inline 的建议: 3. inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址 了,链接就会找不到
              // F.h #include using namespace std ; inline void f ( int i ); // F.cpp #include "F.h" void f ( int i ) { cout << i << endl ; } // main.cpp #include "F.h" int main () { f ( 10 ); return 0 ; } // 链接错误: main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z) ,该符号在函数 _main 中被引用
              【面试题】 宏的优缺点? 优点: 1. 增强代码的复用性。 2. 提高性能。 缺点: 1. 不方便调试宏。(因为预编译阶段进行了替换) 2. 导致代码可读性差,可维护性差,容易误用。 3. 没有类型安全的检查 。 C++ 有哪些技术替代宏 ? 1. 常量定义 换用 const enum 2. 短小函数定义 换用内联函数

              八、 auto关键字(C++11)(爆改七)

              1 类型别名思考

              随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在: 1. 类型难于拼写 2. 含义不明确导致容易出错
              #include #include 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::iterator 是一个类型,但是该类型太长了,特别容 易写错。聪明的同学可能已经想到:可以通过 typedef 给类型取别名,比如:
              #include #include typedef std::map < std::string , std::string > Map ; int main () { Map m { { "apple" , " 苹果 " },{ "orange" , " 橙子 " }, { "pear" , " 梨 " } }; Map::iterator it = m . begin (); while ( it != m . end ()) { //.... } return 0 ; }
              使用 typedef 给类型取别名确实可以简化代码,但是 typedef 有会遇到新的难题:
              typedef char* pstring ; int main () { const pstring p1 ;     // 编译成功还是失败? const pstring * p2 ;   // 编译成功还是失败? return 0 ; }
              在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的 类型。然而有时候要做到这点并非那么容易,因此 C++11 给 auto 赋予了新的含义。

              2 auto简介

              在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量 ,但遗憾的 是一直没有人去使用它,大家可思考下为什么? C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一 个新的类型指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得 。
              int TestAuto () { return 10 ; } int main () { int a = 10 ; auto b = a ; auto c = 'a' ; auto d = TestAuto (); cout << typeid ( b ). name () << endl ; cout << typeid ( c ). name () << endl ; cout << typeid ( d ). name () << endl ; //auto e; 无法通过编译,使用 auto 定义变量时必须对其进行初始化 return 0 ; }
              【注意】 使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类型 。因此 auto 并非是一种 “ 类型 ” 的声明,而是一个类型声明时的 “ 占位符 ” ,编译器在编 译期会将 auto 替换为变量实际的类型 。

              3 auto的使用细则

              1. auto 与指针和引用结合起来使用 用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto 声明引用类型时则必须 加 &
              int main () {     int x = 10 ;     auto a = & x ;     auto * b = & x ;     auto & c = x ;     cout << typeid ( a ). name () << endl ;     cout << typeid ( b ). name () << endl ;     cout << typeid ( c ). name () << endl ;     * a = 20 ;     * b = 30 ;     c = 40 ;     return 0 ; }
              2. 在同一行定义多个变量 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量 。 8.3 auto 不能推导的场景 1. auto 不能作为函数的参数
              // 此处代码编译失败, auto 不能作为形参类型,因为编译器无法对 a 的实际类型进行推导 void TestAuto ( auto a ) {}
              2. auto 不能直接用来声明数组
              void TestAuto () {     int a [] = { 1 , 2 , 3 };     auto b [] = { 4 , 5 , 6 }; }
              3. 为了避免与 C++98 中的 auto 发生混淆, C++11 只保留了 auto 作为类型指示符的用法 4. auto 在实际中最常见的优势用法就是跟以后会讲到的 C++11 提供的新式 for 循环,还有 lambda 表达式等进行配合使用。

              九、 基于范围的for循环(C++11)(爆改八)

              1、 范围for的语法

              在 C++98 中如果要遍历一个数组,可以按照以下方式进行:
              void TestFor () { int array [] = { 1 , 2 , 3 , 4 , 5 }; for ( int i = 0 ; i < sizeof ( array ) / sizeof ( array [ 0 ]); ++ i )     array [ i ] *= 2 ; for ( int* p = array ; p < array + sizeof ( array ) / sizeof ( array [ 0 ]); ++ p )     cout << * p << endl ; }
              对于一个 有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因 此 C++11 中引入了基于范围的 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 来跳出整个循环 。

              2 、范围for的使用条件

              1. for 循环迭代的范围必须是确定的 对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供 begin 和 end 的方法, begin 和 end 就是 for 循环迭代的范围。 注意:以下代码就有问题,因为 for 的范围不确定
              void TestFor ( int array []) {     for ( auto & e : array )         cout << e << endl ; }
              2. 迭代的对象要实现 ++ 和 == 的操作 。 ( 关于迭代器这个问题,以后会讲,现在提一下,没办法 讲清楚,现在大家了解一下就可以了

              十、 指针空值nullptr(C++11)(爆改九)

              C++98中的指针空值

              在良好的 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 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 。 最后,感谢大家的观看!