• C++基础知识


    C++基础


    1.C++ 关键字(98版,63个)

    1.1 C++ 关键字表

    asmdoifreturntrycontinue
    autodoubleinlineshorttypedeffor
    booldynamic_castintsignedtypeidpublic
    breakelselongsizeoftypenamethrow
    caseenummutablestaticunionwchar_t
    catchexplicitnamespacestatic_castunsigneddefault
    charexportnewstructusingfriend
    classexternoperatorswitchvirtualregister
    constfalseprivatetemplatevoidtrue
    const_castfloatprotectedthisvolatilewhile
    deletegotoreinterpret_cast

    C++98有63个关键字,C语言有32个关键字

    C++11有73个关键字(新增alignas、alignof、char16_t、char32_t、constexpr、decltype、noexcept、nullptr、static_assert、thread_local)

    C++兼容C语言头文件,也有自己的用C库的方法:stdio.h(C/C++)、cstdio(C++)


    2.C++ 命名空间

    2.1 命名空间的常见形式

    C++中定义命名空间使用namespace关键字,一般写法为namespace+命名空间名字+{}命名空间解决了命名空间污染问题

    C++命名空间的三种情况:

    1. 单独命名空间
    2. 嵌套命名空间
    3. 同名命名空间会合并
    //C++ 命名空间的三种常见情况
    
    //1.单独命名空间
    namespace A //A为命名空间的名称
    {
        //命名空间里可以定义变量也可以定义函数
        int num;
        int ADD(int num1,int num2)
        {
            return num1+nunm2;
        }
    }
    //2.嵌套命名空间
    namespace B
    {
        int num;
        int ADD(int a,int b)
        {
            return a+b;
        }
        namespace C
        {
            int num;
            int ADD(int aa,int bb)
            {
                return aa+bb;
            }
        }
    }
    //3.一个工程里,多个相同名称的命名空间,最后会合并成同一个命名空间
    namespace A
    {
        int num;
    }
    namespace A
    {
        int ADD(int num1,int num2)
        {
            return num1+num2;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    2.2 命名空间的常见使用方法

    命名空间一般有三种使用方法:

    1. 命名空间名+: :
    2. using+命名空间名;
    3. using namespace+命名空间名;
    //1.命名空间名称+作用域限定符
    namespace A
    {
    	int num=1;
        int num2=2;
        int ADD(int x,int y)
        {
            return x+y;
        }
    }
    int main()
    {
        cin>>A::num>>A::num2;
        return 0;
    }
    
    //2.使用using将命名空间中成员引入释放
    namespace A
    {
    	int num=1;
        int num2=2;
        int ADD(int x,int y)
        {
            return x+y;
        }
    }
    using A::num;//释放A中num
    int main()
    {
        cin>>num;
        cin>>A::num2;//num2没有释放出来不能直接用
        return 0;
    }
    
    //3.使用using namespace命名空间名称引入
    namespace A
    {
    	int num=1;
        int num2=2;
        int ADD(int x,int y)
        {
            return x+y;
        }
    }
    using namespace A;//释放A中所有内容
    int main()
    {
        cin>>num>>num2;
        ADD(3,4);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    using可以理解为释放的意思,比如using A::num;就是释放A中num但是不释放num2和ADD,意思就是num2和ADD必须在前面加A: :才能正常使用,而num可以直接用!


    3.C++ 输入与输出

    3.1 cout与cin标识符

    C++中的输入与输出是使用头文件和std标准命名空间实现的

    我们使用标准化输出依靠cout和cin标识符。cout标准输出(控制台)、cin标准输入(键盘)

    #include
    using namespace std;
    int main()
    {
        int a;
        float b;
        //endl是换行的意思,和'\n'意思相同,可以替换
        cin>>a>>b;
        cout<<a<<b<<endl;
        cout<<a<<b<<'\n';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.C++ 缺省参数/默认参数

    4.1 缺省参数概念

    缺省参数(也称默认参数)是声明或定义函数时,为函数的参数指定一个默认值

    void func(int x=0)
    {
        cout<<x<<endl;
    }
    
    int main()
    {
        func();//没有传入实参,使用默认值0,打印出0
        func(1);//传入实参,使用实参值,打印出1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.2 缺省参数类型

    缺省参数分为:全缺省参数、半缺省参数

    缺省参数特性:

    1.缺省参数不能在声明和定义中同时出现,声明和定义二选一写- - -否则报错:重定义默认参数- - -原因:怕你两个默认参数给的不一样

    2.半缺省参数必须从右往左依次给出,不能间隔给,否则编译器不知道识别谁

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

    4.C语言不支持缺省参数,C++支持缺省参数

    //全缺省参数
    void func(int x=0,int y=1,int z=2)
    {
    	cout<<"x="<<x<<endl;
        cout<<"y="<<y<<endl;
        cout<<"z="<<z<<endl;
    }
    
    //半缺省参数
    void func2(int x,int y=1,int z=2)
    {
        cout<<"x="<<x<<endl;
        cout<<"y="<<y<<endl;
        cout<<"z="<<z<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.C++ 函数重载

    5.1 函数重载的概念

    函数重载:在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表必须不同

    函数重载特性:函数名相同,参数不同(参数的个数类型顺序不同),与返回值类型无关

    int Add(int x,int y)//原函数Add
    {
    	return x+y;
    }
    
    double Add(double x,double y)//Add重载函数
    {
        return x+y;
    }
    
    long Add(long x,long y)//Add重载函数
    {
        return x+y;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.2 函数名字修饰原则

    实际上我们的项目通常是由多个头文件和多个源文件构成的,通过编译链接我们可以知道:

    当两个cpp文件(一个函数在x.cpp中声明,y.cpp中定义)中定义一个Add函数时,我们编译后链接前,x.o的目标文件中没有Add函数的地址,因为Add是在x.cpp中声明,y.cpp中定义的,所以Add的地址在y.o中!==因此链接阶段就是专门处理这个问题的,链接器看到x.o调用了Add,但是没有Add的地址,就会到y.o的符号表中找到Add的地址,然后链接在一起!==因此我们需要为函数名字拟定一个修饰规则

    Linux下gcc、g++的修饰规则: ①gcc函数修饰名字不变 ②g++函数修饰为_Z+函数长度+函数名+类型首字母

    比如gcc我们定义一个int f(int x,int y)—> f

    比如g++我们定义一个int Add(int x,int y)—> _Z3Addii

    比如g++我们定义一个int Add(double x,double y)—> _Z3Adddd

    请添加图片描述

    请添加图片描述

    如图,在Linux下采用gcc编译后,函数名字的修饰没有发生改变,函数名后的数据是函数参数类型的信息

    如图,在LInux下采用g++编译后,函数名字修饰发生改变,函数名后的数据是函数参数类型的信息

    windows下名字修饰规则根据编译器规则

    请添加图片描述

    从以上说明就可以基本理解,经常说的C语言不支持重载,而C++支持重载的原因是:g++编译后函数名字修饰改变了,同时也说明了为什么函数重载要求参数不同,而跟返回值无关的原因

    C++通过函数修饰规则来区分函数重载,只要参数不同,修饰出来的名字不同,就支持了重载!而C语言无法识别出来!


    6.C++ 引用

    6.1 引用的概念

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

    引用类型和引用实体的类型必须相同

    //引用类型定义格式:类型& 引用变量名=引用实体
    void func()
    {
    	int x=0;
        int& tmp=x;//定义引用类型
        printf("%p\n",&x);
        printf("%p\n",&tmp);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.2 引用的特性

    1.引用在定义时必须初始化

    2.一个变量可以有多个引用

    3.一旦引用一个实体,不能再引用其他实体

    4.引用只能缩小权限不能放大权限,及引用的权限范围小于或者等于引用实体

    void func()
    {
    	int x=0;
        //int& x;这条语句编译会错误,引用在定义时必须初始化
        int& num1=x;
        int& num2=x;
        printf("%p %p %p\n",x,num1,num2);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.3 常引用

    常引用涉及const权限的放大与缩小问题

    void func()
    {
    	const int x=0;
        //int& tmp=x;该语句编译错误,因为x为常量  权限放大了
        
        const int& tmp=x;//同一权限级
        
        //int& num=0;该语句编译错误,因为0为常量  权限方大了
        const int& num=0;
        
        double y=1.234;
        //int& num2=y;该语句编译错误,因为类型不同,引用要求类型相同  常值具有常性const
        const int& num2=y;
        //正确,因为底层实现是y先赋值给一个临时变量,再让临时变量赋值给num2,临时变量有常性,所以引用必须加const---临时变量问题也解释了为什么nums2的地址和y的地址不一样,nums2存的应该是临时变量的地址
        
        int f=y;//正确,因为f和y没有使用指针和引用,不会改变原值,所以不加const也可以
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    使用场景:①做参数 ②做返回值

    //1.做参数---代替指针使用
    void Swap(int& x,int& y)
    {
    	int	temp=x;
        x=y;
        y=temp;
    }
    
    //2.做返回值---变量出了函数还在用引用,不在用传值---其实就是&必须有目标变量在才能用,否则目标变量没了,你还用它的地址,不就是野指针了吗
    int& count()
    {
        static int n=0;//补充:static只会初始化一次
        n++;
        //...
        return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,必须使用传值返回(即变量出了函数还在吗,在就用引用,不在就用传值)

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

    #include
    using namespace std;
    #include
    struct A
    {
        int a[10000];
    };
    void func1(A a){}//传值
    void func2(A& a){}//传引用
    
    void func()
    {
        A a;
        //以值作为函数参数
        size_t begin1=clock();
        for(size_t i=0;i<10000;++i)
            func1(a);
       	size_t end1=clock();
    
        //以引用作为函数参数
        size_t begin2=clock();
        for(size_t i=0;i<10000;++i)
            func2(a);
        size_t end2=clock();
        
        //分别计算出两个函数运行结束后的时间
        cout<<"func1(A)-time:"<<end1-begin1<<endl;
        cout<<"func2(A)-time:"<<end2-begin2<<endl;
    }
    int main()
    {
        func();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    请添加图片描述

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

    #include
    #include
    using namespace std;
    struct A
    {
    	int a[10000];
    };
    A a;
    
    A func1()
    {
        return a;
    }
    A& func2()
    {
        return a;
    }
    void func()
    {
        //以值作为函数的返回值类型
        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 time:"<<end1-begin1<<endl;
        cout<<"func2 time:"<<end2-begin2<<endl;
    }
    int main()
    {
        func();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    请添加图片描述

    通过上述代码的比较,发现传值和指针在作为传参以及返回值类型上效率相差很大,传引用远优于传值

    传值与传引用返回总结:

    1. 传值返回:会有临时拷贝
    2. 传引用返回:没有临时拷贝,函数返回的直接就是返回变量的别名
    3. 反正记住返回的变量是static才用传引用返回,否则可能出现越界问题

    6.4 引用与指针的区别

    引用就是一个别名,没有独立空间,和其引用实体共用同一块空间

    在底层实现上实际是有空间的,因为引用是按照指针的方式来实现的
    请添加图片描述

    指针和引用的不同点:

    1.引用在定义时必须初始化,指针无要求

    2.引用在初始化引用一个实体后,无法再引用其他实体,而指针无要求

    3.没有NULL引用,有NULL指针

    4.在sizeof中含义不同:引用结果为引用类型的大小,指针始终是地址空间所占字节个数

    5.引用自加则实体增加1,指针自加则指针向后偏移一个类型大小

    6.有多级指针,但是没有多级引用

    7.访问实体方法不同,指针需要显示解引用,引用编译器自己处理

    8.引用比指针使用更安全


    7.C++ 内联函数

    7.1 内联函数概念

    inline修饰的函数叫做内联函数,编译时C++编译器会把调用内联函数的地方展开,没有函数压栈的开销内联函数提升程序运行的效率

    内联函数不放入符号表

    函数展开也就是说编译器将指定的函数体插入并取代每一处调用该函数的地方,相当于直接把内容写在了调用的地方


    7.2 内联函数的查看方法

    方法一:在release模式下,查看编译器生成的Add函数汇编代码中是否存在call Add

    请添加图片描述

    方法二:在debug模式下,需要对编译器设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化)

    请添加图片描述


    7.3 内联函数的特性

    1.inline是一种以空间换时间的方法,省去函数压栈开销

    2.inline不适合代码很长或者有循环、递归的函数(10行以内适合用)

    3.inline对于编译器而言只是一个建议,编译器会自动优化

    4.inline不建议声明和定义分离,会发生链接错误(inline被展开,它不进入符号表,就没有函数地址了,链接找不到)

    // demo.h
    #include
    using namespace std;
    inline void func(int x);
    
    //demo.cpp
    #include"demo.h"
    void func(int x)
    {
        cout<<x<<endl;
    }
    
    //main.cpp
    #include"demo.h"
    int main()
    {
        func(10);
        return 0;
    }
    
    //这里就会报错!
    /*链接错误:main.obj LNK2019:无法解析的外部符号"void _cdeck func(int)"?
    f@@YAXH@Z),该符号在函数 _main中被引用*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    8.C++ auto关键字(C++11)

    8.1 auto关键字的概念

    1.个人觉得,auto很少有人使用,只有后面用模板的时候才会用到

    2.在早期C/C++中auto时含义:使用auto修饰的变量,是具有自动存储器的局部变量

    3.C++11中,标准委员会重新赋予了auto含义:auto作为新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

    简单来说:auto自动匹配数据类型

    int func()
    {
    	return 0;
    }
    int main()
    {
        int x=1;
        auto xx=x;
        auto xxx='x';
        auto xxxx=func();
        //auto xxxxx;编译错误,使用auto必须初始化
        //typied(x).name  输出x的类型名字
        cout<<typied(xx).name<<endl;
        cout<<typied(xxx).name<<endl;
        cout<<tupied(xxxx).name<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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


    8.2 auto使用规则

    1.auto声明指针类型时,auto与auto*没有区别

    2.auto声明引用类型时,必须加&

    3.auto声明多个变量,类型必须相同—auto识别第一个变量,后面也认为时这个变量

    4.auto不能作为函数参数

    5.auto不能声明数组

    1.auto与指针和引用结合使用

    1.auto声明指针类型时,用autoauto*没有任何区别
    2.auto声明引用类型时必须加&
    
    int main()
    {
    	int x=0;
        auto xx=&x;
        auto* xxx=&x;
        auto& xxxx=x;
        cout<<typeid(xx).name<<endl;
        cout<<typeid(xxx).name<<endl;
        cout<<typeid(xxxx).name<<endl;
        *xx=1;
        *xxx=2;
        xxxx=3;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.在同一行定义多个变量

    1.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错
    2.编译器只对第一个类型进行推导,然后用推导出来的类型定义其他变量
        
    //void func()
    {
        auto a=1,b=2;//正确
        auto c=3,d=4.0;//错误,因为c和d的初始化表达式类型不同
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.auto不能作为函数的参数

    //编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型就行推导
    void func(auto a)
    {
    	return a;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.auto不能直接用来声明数组

    //编译错误,没有这种用法
    void func()
    {
        int a[]={1,2,3,4,5,6};
        auto b[]={1,2,3,4,5};
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    补充:

    ①为了避免和C++98中的auto发生混洗,C++11只保留了auto作为类型指示符的用法

    ②auto在实际中最常见的优势用法就是跟以后会讲到的C++提供的新式for循环,还有lambda表达式等进行配合使用


    9.C++ 基于范围的for循环(C++11)

    9.1 for遍历与范围for遍历

    对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围

    注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

    //for遍历
    void func()
    {
    	int a[]={1,2,3,4,5,6,7};
        for(int i=0;i<sizeof(a)/sizeof(a[0]);++i)
            a[i]+=1;
        for(int* j=a;j<a+sizeof(a)/sizeof(a[0]),++j)
            cout<<*j<<endl;
    }
    
    //范围for遍历
    //自动获取array中的数据,赋值给e,自动判断结束
    //
    void func()
    {
        int a[]={1,2,3,4,5,6,7};
        int aa[]={2,4,6,8,10,12};
        
        //auto&与auto的区别
        
        //auto
        for(auto e:a)
            e/=2;
        for(auto e:a)
            cout<<e<<" ";//还是打印的1 2 3 4 5 6 7---原因:把a的元素赋值给e相当于形参的改变不影响实参
        
        //auto&
        for(auto&ee :aa)
            ee/=2;
        for(auto& ee:aa)
            cout<<ee<<" ";//打印1 2 3 4 5 6
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    9.2 范围for的使用要求

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

    //对于数组而言,就是数组中的第一个元素和最后一个元素的范围
    //对类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围
    
    void func(int a[])
    {
        //编译错误,for范围不确定
        for(auto& e:a)
            cout<<e<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    10.1 指针空值的底层原理

    指针空值的初始化:

    //如果一个指针没有合法的指向,按照一下方法对其初始化
    void Ptr()
    {
    	int* p1=NULL;//NULL就是0
        int* p2=nullptr;
        int* p3=0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    NULL底层就是一个宏,值为0,在传统的C头文件stddef.h中

    #ifndef NULL
    #ifdef __cplusplus
    #define NULL	0
    #else
    #define NULL	((void *)0)
    #endif
    #endif
    
    可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量---NULL0的值一样就会引发问题--所以C++使用了nullptr
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    指针空值的规则:

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

    11.剖析extern “C”

    曾经Google有一个项目叫tcmalloc和tcfree项目,就是用C++写tcmalloc和tcfree两个接口来实现更高的效率,但是写出来的话C语言就没办法使用,于是就诞生了extern "C"这个东西

    extern "C"的主要作用是:为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名

    11.1 解析extern “C”

    #ifndef __INCvxWorksh  /*防止该头文件被重复引用*/
    #define __INCvxWorksh
    
    #ifdef __cplusplus    //__cplusplus是cpp中自定义的一个宏
    extern "C" {          //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
    #endif
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif /* __INCvxWorksh */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    extern “C” 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。被extern "C"限定的函数或变量是extern类型的。


    11.2 extern "C"的应用场景

    1.C++代码调用C语言代码、在C++的头文件中使用

    在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为Example.h)时,需进行下列处理:

    extern "C"
    {
    #include "Example.h"
    }
    
    • 1
    • 2
    • 3
    • 4

    而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误

    //c语言头文件:Example.h 
    #ifndef C_EXAMPLE_H
    #define C_EXAMPLE_H
    extern int add(int x,int y);     //写成extern "C" int add(int,int );也可以
    #endif
    
    //c语言实现文件:Example.c 
    #include "cExample.h"
    int Add( int x, int y )
    {
     return x + y;
    }
    
    // c++实现文件,调用add:cppFile.cpp
    extern "C"
    {
     #include "Example.h"       
     //如果这样编译通不过,换成 extern "C" int  add(int , int ); 可以通过
    }
    
    int main(int argc, char* argv[])
    {
     Add(2,3);
     return 0;
    }
    
    //如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C"{}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在C中引用C++语言中的函数和变量时,C++的头文件需添加extern “C”,但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型

    //C++头文件 cppExample.h
    #ifndef CPP_EXAMPLE_H
    #define CPP_EXAMPLE_H
    extern "C" int Add( int x, int y );
    #endif
    
    //C++实现文件 cppExample.cpp
    #include "cppExample.h"
    int Add( int x, int y )
    {
     return x + y;
    }
    
    // C实现文件 cFile.c
    //这样会编译出错:#include "Example.h" 
    extern int Add( int x, int y );
    int main( int argc, char* argv[] )
    {
     Add( 2, 3 );
     return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    11.3 extern "C"引出的宏问题

    宏的优点:

    ①增强代码的复用性

    ②提高性能

    宏的缺点:

    ①不方便调试宏(因为预编译阶段进行了替换,不好读)

    ②导致代码可读性差,可维护性差,容易误用

    ③没有类型安全的检查

    C++替换宏的技术方法:

    ①C++使用常量定义换用const

    ②C++函数定义换用内联函数

  • 相关阅读:
    推荐一款在线的JDK17中文文档
    第三章 UI开发的点点滴滴
    极具吸引力的小程序 UI 风格
    SpringBoot-Profile功能与外部化配置
    ArcGIS模型构建器实例:一键拓扑(附模型下载)
    熊市来临,加密货币公司将广告支出削减了 90%
    Nginx学习(3)—— Nginx的应用
    C#8.0本质论第五章--方法和参数
    MATLAB--二维图像和三维图像的绘制
    队列的链式存储结构
  • 原文地址:https://blog.csdn.net/qq_29678157/article/details/127361333