01.C++入门

本篇主要讲解C++弥补C语言的不足,以及C++如何对C语言不合理的地方进行优化


目录

1. 命名冲突问题

1.1 各种域

1.2 命名空间定义

1.3 命名空间的使用

2. C++输入和输出

3. 缺省参数

3.1 缺省参数的概念

3.2 缺省参数的起源

3.3 缺省参数的分类

4. 函数重载

4.1 函数重载的概念

4.2 C++支持函数重载的原因--名字修饰

5. 引用

5.1 引用的概念

5.2 引用特性

5.3 常引用

5.4 使用场景

5.5 传值,传引用效率比较

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

5.6 引用和指针的区别

6. auto关键字(自动推导)

6.1 类型别名思考

6.2 auto简介

6.3 auto使用规则

6.4 auto不能推导的场景

7. 基于范围的for循环

7.1 范围for的语法

7.2 范围for的使用条件

8. 内联函数

8.1 概念

8.2 特性

9. 指针空值nullptr(C++11)

9.1 C++98中的指针空值


1. 命名冲突问题

在C与C++中,变量、函数、类的名称都存在全局作用域中,可能导致很多冲突:比如说

  1. 与库里的命名冲突
  2. 在一个项目中,每个人开发不同部分,最后程序合并时,工作人员互相冲突

        使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或者名字污染,namespace关键字的出现就是针对这种问题。

#include<stdio.h>
#include<stdlib.h>

int rand = 10;
int main()
{
    printf("%d\n",rand);
    return 0;
}

        C语言无法解决类似这样的命名冲突问题,所以C++提出了namespace来解决。

1.1 各种域

        C语言中常说全局变量,局部变量,其实C与C++一样,也有这种变量,全局变量所在为全局域,局部变量所在为局部域。而命名空间也会有一个命名空间域

#include <cstdio>

int a = 0;                //全局域

namespace zzy             //命名空间域
{
    int a = 1;
}

using namespace zzy;      //展开命名空间
    
int main()
{
    int a = 2;            //局部域
    printf("%d\n",a);
    return 0;
}

        其中的展开命名空间,其实就是将其中的内容暴露到全局。

        注意三不同域可以存在同一变量,如果同时存在时,printf首先会考虑局部域中的a,其次是全局域。如果我们将局部域中的a注释掉,而保留展开命名空间和全局域,则会出现报错,因为命名空间域中的内容被暴露到全局,而全局域本来就有变量,此时同一变量会发生冲突。

        如果我们不展开命名空间,那么printf不会主动进入namespace中搜索变量a,相当于一个独立的空间。

        所以自动搜索变量的顺序即为:局部域-->全局域-->展开命名空间域or指定访问命名空间域。下面代码演示如何指定访问命名空间域:

int b = 0;

namespace zzy2
{
    int b = 1;
}

int main()
{
    int b = 2;

    printf("%d\n",b);        2
    printf("%d\n",::b);      0
    printf("%d\n",zzy2::b);  1

    return 0;
}

        ::为域操作符,前面什么都不写则表示全局域,前面写namespace的名字即为命名空间域。这种方法还是比较好的。总之,不要轻易展开命名空间。

1.2 命名空间定义

         定义命名空间,需要使用namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

其一:命名空间中可以定义变量/函数/类型

namespace zzy3
{
    int rand = 20;

    int ADD(int left, int right)
    {
        return left+right;
    }

    struct Node
    {
        struct Node *next;
        int val;
    };
}

其二:命名空间可以嵌套

namespace zzy4
{
    int a;
    int b;
    int ADD(int left, int right)
    {
        return left+right;
    }

    namespace zzy
    {
        int c;
        int d;
        int Sub(int left, int right)
        {
            return left-right;
        }
    }
}

调用方法:

int main()
{
    printf("%d\n",zzy4::a);
    printf("%d\n",zzy4::zzy::c);
    printf("%d\n",zzy4::ADD(1,2));
    printf("%d\n",zzy4::zzy::Sub(2,1));
}

其三:同一个工程中允许有多个相同名称的命名空间,编译器最后会合成同一个命名空间中。同一个工程中的test.h和test.cpp中同一个namespace会合并,都可以调用,但是,相同命名空间中不能定义同一个变量。

注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。

1.3 命名空间的使用

命名空间的使用有三种方式:

  • 加命名空间名称及域限定符
namespace zzy
{
    int a;
    int b;
    
    int ADD(int left, int right)
    {
        return left+right;
    }
}

int main()
{
    printf("%d\n",zzy::a);
}
  • 使用using将命名空间中的某个成员引入
using zzy::b;

int main()
{
    printf("%d\n",zzy::a);
    printf("%d\n",b);
}
  • 使用using namespace命名空间名称引入
using namespace zzy;

int main()
{
    printf("%d\n",zzy::a);
    printf("%d\n",b);
    ADD(20,30);
    return 0;
}

2. C++输入和输出

C语言中第一个输出的往往是,hello world,那在C++中如何来实现这种输出呢?

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World"<<endl;
    return 0;
}

std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中。

要注意:iostream中是有std的定义,而using namespace是说明是否去搜索,也就是说,只使用using,没有前面的定义是没有用的。

说明:

  1. 使用cout和cin时,必须包含<iostream>头文件以及按照命名空间使用方法使用std。
  2. endl是特殊符号,其实就是endline,表示换行输出,也包含在iostream中。
  3. <<是流插入运算符,>>是流提取运算符。
  4. 使用C++输入输出是更方便的,不需要像printf一样手动控制格式,C++的输入输出可以自动识别变量类型。
#include <iostream>

using namespace std;

int main()
{
    cout << "Hello World"<<endl;

    int a;
    double b;
    char c;

    cin>>a;
    cin>>b>>c;

    cout<<a<<endl;
    cout<<b<<" "<<c<<endl;
    return 0;
}

连续输入b、c时,中间打空格。

std命名空间的使用惯例:

  1. 在日常练习中,建议直接using namespace std即可,这样就很方便。
  2. using namespace std展开,标准库就全部暴露出来了,如果我们不小心定义了与库中重名的变量,就存在冲突问题,在项目比较大,代码比较多时就容易出现。所以建议在项目开发中使用std::cout这样展开常用库对象。
  3. 指定展开,个人认为是最好用的一种,可以省去写std::cout这样麻烦的方式,又不用展开全部的库:
using std::cout;
using std::endl;

3. 缺省参数

3.1 缺省参数的概念

缺省参数是声明或者定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

using namespace std;
void Func(int a = 0)
{
    cout << a << endl;
}

int main()
{
    Func();
    Func(10);

    return 0;
}

没有传参时,使用参数默认值,传参时,使用指定的实参。

3.2 缺省参数的起源

struct Stack
{
    int *a;
    int top;
    int capacity;
};

void StackInit(struct Stack *pst, int defaultcapacity = 4)
{
    pst->a = (int*)malloc(sizeof(int)*defaultcapacity);
    if(pst->a == NULL)
    {
        perror("malloc fail");
        return;
    }
    pst->top = 0;
    pst->capacity = defaultcapacity;
}

int main()
{
    struct Stack st1;
    StackInit(&st1,100);

    struct Stack st2;
    StackInit(&st2);

    return 0;
}

如果不写缺省参数=4,在不知道要插入多少个数据的情况下就没办法开辟合适的空间,要么大了要么小了。如果写了缺省参数,在知道的情况下可以传100,覆盖掉默认的缺省参数,而不传则会按照默认容量进行开辟。

在C语言中实现这种效果是通过#define 默认容量 4,如果以后想要修改的话,还得到定义处修改。远不如C++中的缺省参数灵活。

3.3 缺省参数的分类

  • 全缺省参数
using namespace std;

void Func(int a = 10, int b = 20, int c = 30)
{
    cout << "a= " << a << endl;
    cout << "b= " << b << endl;
    cout << "c= " << c << endl;
}

int main()
{
    Func();
    Func(1);
    Func(1,2);
    Func(1,2,3);
    // Func(1,,3);
}

传参是从左向右传参,中间不能有间隔,会报错。

  • 半缺省参数
void Func(int a, int b = 20, int c = 30)
{
    cout << "a= " << a << endl;
    cout << "b= " << b << endl;
    cout << "c= " << c << endl;
}

注意:

  1. 半缺省参数必须从右向左依次给出,不饿能间隔着给,即有a,a、b,a、b、c三种缺省情况,所以传参是从左向右一一对应。
  2. 缺省参数不能在函数声明和定义中同时出现。
//a.h
void Func(int a = 10);
//a.cpp
void Func(int a = 20);

如果声明与定义位置同时出现缺省参数,恰巧两个位置提供的值不同,那编译器就无法确定到底该用哪个缺省值。

由于先进行编译阶段,所以我们要在声明中给定缺省参数,在定义中给缺省是没有必要的。


4. 函数重载

        自然语言中,一个词有多种含义,人们可以通过上下文来判断该词真正含义,即该词被重载了。

4.1 函数重载的概念

        函数重载:是函数的一种特殊情况,C++允许同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

C语言不允许同名函数的存在。

  • 参数类型不同
#include <iostream>
using namespace std;

int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left+right;
}

double Add(double left, double right)
{
    cout << "double Add(double left, double right)" << endl;
    return left+right;
}
  • 参数个数不同
void f()
{
    cout << "f()" << endl;
}

void f(int a)
{
    cout << "f(int a)" << endl;
}

需注意:

void f()
{
    cout << "f()" << endl;
}

void f(int a = 0)
{
    cout << "f(int a)" << endl;
}

这样虽然构成重载,但是是有问题的,无参调用时,会出现冲突,上下两个同名函数会出现歧义,f()到底是调用第一个,还是调用第二个但是使用默认值。

  • 参数类型顺序不同
void Func(int a, char b)
{
    cout << "Func(int a, char b)" << endl;
}

void Func(char b, int a)
{
    cout << "Func(char b, int a)" << endl;
}

int main()
{
    Add(10,20);
    Add(10.1,10.2);

    f();
    f(100);

    Func(10,'b');
    Func('b',20);

    return 0;
}

需注意是类型顺序不同,而不是类型名称不同:

void Func(int a, char b);

void Func(int b, char a);

这种是不构成重载的。

4.2 C++支持函数重载的原因--名字修饰

        为什么C++支持函数重载,而C语言不支持函数重载呢?

        下面我们来分析一个程序运行起来的过程,预处理、编译、汇编、链接

1.实际项目通常是由多个头文件和源文件构成,在test.cpp中调用了函数定义.cpp中定义的函数时,只是有.h中的声明,声明只是相当于一个承诺,但是这个承诺还没有兑现。

2.链接阶段就是专门处理这种问题,用来兑现承诺,而链接时就会按某种特殊的名字去寻找函数。

在C语言编译器编译后:

        函数的名字的修饰不变,还是函数本身的名字。

在C++编译器编译后:

        函数的名字的修饰发生改变,变成【Z+函数名称长度+函数名+函数参数类型首字母】

3. 到这里就理解了C语言没法支持重载,因为同名函数没办法区分,而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

4.如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。


5. 引用

5.1 引用的概念

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

void Test()
{
    int a = 10;
    int& ra = a;        定义引用类型

    cout << a << endl;
    cout << ra << endl;
}

int main()
{
    Test();
}

类型& 引用变量名 = 引用实体;

ra是的别名,两者都是同一块空间,ra++,a也++。注意:引用类型和引用实体是同种类型的。

5.2 引用特性

  1. 引用在定义时一定要初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,那么再不能引用其他实体
void Test2()
{
    int a = 10;
    int& ra = a;
    // int& ra;            该条语句编译时会出错
    int& rra = a;
    
    cout << a << endl;
    cout << ra << endl;
    cout << rra << endl;
}

5.3 常引用

void Test3()
{
    const int a = 10;
    // int& ra = a;    出错,因为a是常量
    const int& ra = a;
    
    // int& b =10;     出错,因为b是常量
    const int& b =10;

    double d = 10.2;
    // int& rd = d;    出错,因为类型不同
    const int& rd = d;
}

那为什么会有这几种结果呢?

首先来看常量情况,a是常量,且被const修饰,即为不可修改,此时若是定义int& ra = a;是将ra定义为a的别名,而ra其实就是a(共享同一块空间),关键就在于没有用const修饰,导致定义的ra是可变的,这与a是const修饰过的不可变的情况不相符。所以会出错

其次,我直接定义10的别名是b也是异曲同工,应该用const进行修饰。

最后,double型的变量再引用时,变成int型,是隐式类型转换,类型转换就会创建临时变量,而临时变量具有常性(即不可修改),加上const说明rd是不可修改的,和临时变量的权限就匹配了。

下面我们来深度理解一下权限:

void Test4()
{
    //1.
    const int a = 0;
    // int& b = a;

    //2.
    const int c = 1;
    int d = c;

    //3.
    int x = 0;
    int& y = x;
    const int& z = x;
    x++;
    // z++;
}

第一块代码中,int& b = a是不可以的,a已经被修饰不可改变了,再引用成可以修改的b是将权限进行放大,如果这样做,修改b相应的也会改变a。

第二块代码中,c拷贝给d,没有放大权限,因为修改d并不影响c。

第三块代码中,可以,int& y = x是将权限平移,const int& z是缩小权限,因为x可读可写,而z只可读,不可更改,其实是缩小了z作为别名的权限,z++是不可以的,而x++是可以的。

最后我们来看一下,传值调用的情况:

int func1()
{
    static int x = 0;
    return x;
}

int& func2()
{
    static int x = 0;
    return x;
}

int main()
{
    // int& ret1 = func1();        权限放大
    const int& ret1 = func1();     权限平移
    int ret1 = func1();            拷贝
    
    int& ret2 = func2();           权限平移 别名再取别名
    const int& rret2 = func2();    权限缩小
}

传值调用也会创建临时变量,所以也要用const修饰,而func2的返回类型是int&,我们接下来马上会在使用场景中见到,引用做返回值及其优点。

5.4 使用场景

  • 做参数
void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}

交换函数传值肯定不行,以前参数都是传指针,现在可以引用做参数,引用也是同一块空间。

引用做参数可以提高效率,体现在大对象和深拷贝上,大对象就是比较大的对象,通常是占用比较大的空间,而深拷贝我们目前暂不做解释。

  • 做返回值
int& Count(int x)
{
    // static int n = x;
    int n = x;
    n++;

    return n;
}

int main()
{
    int& ret = Count(10);
    cout << ret << endl;

    Count(20);
    cout << ret << endl;

    return 0;
}

这种情况下,ret是n的别名,而count栈在调用都会销毁,n的空间虽然还在,但已经无法访问到,那为什么还会显示11呢?这个就是老生常谈的问题,ret访问的是销毁空间的变量,不清理空间则是11,是不会变的,但实际上这样调用是非常危险的。

如果加上static:

int& Count(int x)
{
    static int n = x;
    // int n = x;
    n++;

    return n;
}

n在静态区,count的栈帧的销毁不会影响n,若不传引用,不管n在哪个区,还会创建临时变量。

总之,若没有static,不能使用引用返回,此时结果不确定。

下面来看一种特殊情况:

#include <iostream>

using namespace std;

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 : " << endl;
    return 0;
}

Add函数第一次运行后,该函数对应的栈空间就被回收了,即c变量就没有意义了,在main中ret引用Add函数返回值,实际应用的就是一块已经被释放的空间。

Add函数第二次运行后,原本c的空间被修改为7,因此ret的值就改变了。

注意:如果函数返回时,出了函数作用域,如果返回对象还在,则可以使用引用返回;如果已经返还给了系统,则必须使用传值返回。

5.5 传值,传引用效率比较

        以值作为参数或者返回值类型时,在传参和返回期间,函数不会直接传送实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此,传值效率低下,尤其是当参数或者返回值类型非常大时,效率就更低。

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

5.6 引用和指针的区别

        在语法概念上,引用就是一个别名,没有独立空间,和其引用实体公用同一块空间。

int main()
{
    int a = 10;
    int& ra = a;

    cout<<"&a = "<<&a<<endl;
    cout<<"&ra = "<<&ra<<endl;

    return 0;
}

&a = 0xe103fff974
&ra = 0xe103fff974

        在底层实现上,实际是有空间的,引用是按照指针方式来实现的。

引用和指针的不同点:

引用指针
定义一个变量的别名存储一个变量地址
在定义时必须初始化没有要求
引用实体后,不能引用其他实体任意指向同类实体
没有NULL引用

有NULL指针

引用结果为引用类型的大小始终是地址空间所占字节个数
引用自加即引用的实体加1指针向后偏移一个类型的大小
没有多级引用有多级指针
编译器自己处理需要显示解引用
更安全比较危险

6. auto关键字(自动推导)

6.1 类型别名思考

        随着程序越来越复杂,程序中用到的类型也就越来越复杂,目前还没什么感觉,之后经常体现在:

        1.类型难于拼写

        2.含义不明确导致出错

例如:std::string>::iterator 就是一个类型,但是它太长了,容易写错。如果使用typedef确实可以简化代码,可是在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量时清除的知道表达式的类型。

6.2 auto简介

        在C++11中,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;    i
    cout<<typeid(c).name()<<endl;    c
    cout<<typeid(d).name()<<endl;    i

    return 0;
}

 auto会根据右边的表达式自动推导类型,typeid意为打印类型。

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

6.3 auto使用规则

  • 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;
    
    return 0;
}
  • 在同一行定义多个变量

在同一行定义多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

void TestAuto()
{
    auto a = 1, b = 2;
    auto c = 3, d = 4.0;
}

第二行会编译失败,c和d的初始化表达式类型不同

6.4 auto不能推导的场景

  1. auto不能作为函数的参数类型
  2. auto不能直接用来声明数组
  3. 与新式for循环,lambda表达式等配合使用

7. 基于范围的for循环

7.1 范围for的语法

在C++98中要遍历一个数组,可以按照如下的方法:

void TestFor()
{
    int arr[] = {1,2,3,4,5};

    for (int i = 0; i<sizeof(arr)/sizeof(arr[0]); ++i)
    {
        arr[i] *= 2;
    }
    for(int* p = arr; p<arr+sizeof(arr)/sizeof(arr[0]); ++p)
    {
        cout<<*p<<endl;
    }
}

但是对于这种有范围的数组,或者说一个集合,我们来说明循环范围是多余的,因此在C++11中引入了基于范围的for循环:

void TestFor2()
{
    int arr[] = {1,2,3,4,5};
    for (auto& i : arr)
    {
        i *= 2;
    }
    for (auto i : arr)
    {
        cout<< i <<" ";
    }

}

int main()
{
    TestFor2();
}

for循环后的括号由冒号:分为两部分,第一部分为范围内用于迭代的变量,第二部分表示被迭代的范围。

7.2 范围for的使用条件

  • for循环迭代的范围必须是确定的

对于数组而言,就是第一个元素与最后一个元素的范围

  • 迭代的对象要实现++和==的操作。

8. 内联函数

        在C语言中定义一个比较简短的函数可以使用宏函数的方法,宏是替换,不是调用,比如两个数的和

#define Add(x,y) ((x)+(y)) 

宏函数的优势是不需要建立栈帧,可以提高效率,缺点是复杂,易出错,可读性差,不能调试。

那么怎么解决这个问题呢?即有一种函数,有宏函数的优点又没有宏函数的缺点。

内联函数就是这样一种函数,接下来我们来看一下他的概念和特性。

8.1 概念

        以inline修饰的函数称为内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立的栈帧的开销,内联函数提升程序运行时的效率。

查看方式:

        1.在release模式下,不方便查看汇编代码。

        2.在debug模式下,需要对编译器进行设置,否则不会展开(因为默认debug模式下,编译器默认不会对代码进行优化,即inline不会起作用)。

inline int Add(int left, int right)
{
    return left+right;
}

int main()
{
    int ret = 0;
    ret = Add(1,2);
    return 0;
}

8.2 特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对编译器而言只是一个建议,一般建议:函数规模较小(5行以内)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会自动忽略inline特性。

其实一个复杂的函数不大可能在调用处频繁展开,举个例子,一个50行的函数,在程序中有10000个位置调用它,假如他是内联函数,那么就会有10000*50行代码,导致代码膨胀,会使可执行程序变大(安装包)。

     3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到,所以直接将函数写在.h文件中。

9. 指针空值nullptr(C++11)

9.1 C++98中的指针空值

        在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针,如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
{
    int* p1 = NULL;
    int* p2 = 0;
}

NULL其实是一个宏,在传统的C头文件中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL  0
#else
#define NULL  ((void*)0)
#endif
#endif

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都会遇到一些歧义:

#include <iostream>

using namespace std;

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,因此与城西的初衷相悖。

所以我们引入了nullptr来代替int*型指针NULL,避免歧义定义为0。

f(int)里形参接不接受都可以,只匹配类型也可,这里就算接收了也是没什么用的。

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
  2. C++11中,sizeof(nullptr) 与sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

本章完

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值