2、C与C++的关系

C与C++的关系

1、c++继承了所有的c特性

2、c++以C语言为基础,又增强了面向对象的支持,类型加强,函数加强,异常处理。

3、语言中的变量都必须在作用域开始的位置定义,c++可以在需要使用时再定义。

4、register关键字请求编译器将局部变量存储于寄存器中,但是编译器是可以拒绝的。c++编译器有自己的优化方式,变量也可能存储在寄存器中,所以c++中很少见register,只是为了兼容c语言而已。c语言中无法获取register变量的地址。c++可以取得register变量的地址。

5、c语言中可以定义多个同名的全局变量,C++中不允许定义多个同名的全局变量。

6、struct关键字的加强

    c语言中的struct定义了一组变量的集合,并不是一种新的类型(如果要当成新的类型,需要用typedef关键字重命名一下)。c++在类型的加强后,把struct定义为一种全新的类型。

7、c++中所有的标识符都必须显示声明类型,不像c语言中的默认类型,c++则不支持默认类型,不允许不写函数返回类型。

    一个小问题:  int f()与 int f(void) 有区别吗?

    c++中两个函数具有相同的意义,表示返回值为int的无参函数。c语言中两个函数则不一样,int f()表示返回值为int,接受任意参数的函数,而int f(void)则表示返回值为int的无参函数。

8、const关键字的进化

    C语言中const修饰的变量是只读的,使得变量具有只读属性,本质还是变量,它修饰的局部变量还会在栈上分配空间,只在编译期有效,在运行期无效。特别的,const修饰的全局变量在只读存储区分配空间,因此修改了const修饰的全局变量将出现奔溃。const修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边,所以const不能定义真正意义的常量。C语言中真正意义的常量只有枚举。通过指针可以修改const修改的变量值。

    c++中当碰见const声明时在符号表中放入常量。编译过程中若发现使用常量则直接以符号表中的值替换。符号表是编译器在编译的过程中产生的数据结构。编译过程中若发现下述情况则给对应的常量分配存储空间: 

    (1)对const常量使用了extern,即当const常量为全局并且需要在其它文件中使用。(2)当使用 & 操作符对const常量取地址。 

    注意:c++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。 原因是为了兼容C语言。

与宏的区别:const常量是由编译器处理,编译器对const常量进行类型检查和作用域检查(宏没有)。编译器中没有宏的概念,宏定义由预处理器处理,单纯的文本替换(字面量),没有类型检查和作用域概念。

    const int *p=&x;    底层

    int * const p=&x;   顶层

    const int *const p=&x; 靠右的const是顶层,靠左的是底层const

9、c++中新增加了bool基础类型和引用

c++(类型增强和面向对象特性)在C语言的基本类型系统之上增加了bool类型(布尔类型),可取的值只有true和false,理论只上占用一个字节。true用1表示,false用0来表示。C语言中用int代替bool不严谨。

   c++对三目运算符进行了升级,c语言中三目运算符返回的是变量值,不能当做左值来使用。c++中的三目运算符如果都是变量的话则直接返回变量本身,即可作为右值使用,又可作为左值使用(只有在所有的可能返回值都是变量时才能使用)。

    注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用。

c++中的引用:变量名的本质是存储空间的别名。在c++中新增加了引用的概念,可以看做是一个已定义变量的别名。

Type& name=var;

 问题:c++对三目运算符做了什么?

    当三目运算符的可能返回都是变量时,返回的是变量引用。当三目运算符的可能返回中有常量时,返回的是值。

10、引用的本质

    引用在一些场合可以代替指针,列如swap函数。引用作为函数形参不需要初始化。函数调用时需要初始化。

const引用: const引用让变量拥有只读属性,但是也能通过指针来修改值。

特殊的引用:当使用常量对const引用进行初始化时,c++编译器会为常量值分配空间,并将引用名作为这段空间的别名。使用字面常量对const引用初始化后将生成一个只读变量。

    问题:引用有自己的存储空间吗?

引用在c++中的内部实现是一个指针常量,因此引用所占用的空间大小与指针相同。从使用的角度,引用只是一个别名,c++为了实用性而隐藏了引用的存储空间这一细节。

c++中的引用旨在大多数情况下代替指针,避免指针错误。某些情况下避免不了内存方面的操作错误,比如函数返回局部变量的引用,不要返回局部变量的引用。可以返回静态局部变量的引用。

11、C++中的新概念:内联函数

        c++中的const常量可以替代宏常数定义

如:const int A=3;== #define A 3 (编译之前预处理器将 A文本替换为3,没有语法检查)

c++中是否有解决方案替代宏代码片段呢?

        c++中推荐使用内联函数替代宏代码片段,用 inline声明内联函数

内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求(只是一种请求,编译器可以拒绝)。c++编译器直接将函数体插入函数调用的地方,没有普通函数调用时的额外开销(压栈,跳转,返回)。

        内联函数具有普通函数的特征(参数检查,返回类型等),被内联编译后,函数体直接扩展到调用的地方。由于宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程,因此可能出现副作用。现代c++编译器能够进行编译优化,一些函数即使没有inline声明,也可能被内联编译。一些现代c++编译器能够对函数进行强制内联。

inline内联编译的限制: 函数体不能过于复杂

        不能存在任何形式的循环语句

        不能存在过多的条件判断语句

        函数体不能过大    

        不能对函数进行取指操作

        函数内联声明必须在调用之前

12、函数参数的扩展

        c++中可以在函数声明时为参数提供一个默认值(C语言中没有这个功能)。当函数调用时没有提供参数的值,则使用默认值。

        参数的默认值必须在函数声明中指定,函数定义时则不可以。函数默认参数的规则:

参数的默认值必须从右向左提供。函数调用时使用了默认值,则后续参数必须使用默认值(否则就得调用参数的顺序)。

        在c++中可以为函数提供占位参数。

占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数。

        函数占位参数的意义:

占位参数与默认参数结合起来使用,兼容C语言程序中可能出现的不规范写法。因为c语言中 void func()与void func(  void )是不一样的,第一个表示接收任意参数的函数,而c++是不一样的,为了避免c中的不规范写法而提出函数占位参数。使得当有参函数调用无参函数时出错时只需在声明中提供占位参数就可以了。而占位参数与默认值的结合使得同时出现 func();与 func(1, 2);时合法。

13、函数重载分析

        c语言到c++的质的飞跃:函数重载

概念:同一个标识符在不同的上下文有不同的意义。用同一个函数名定义不同的函数,当函数名和不同的参数搭配时函数的含义不同。

        函数重载至少满足下面的一个条件:

参数个数不同

参数类型不同

参数顺序不同

        当函数默认参数int func(int a,int b,int c=0)遇上函数重载int func(int a,intb)会发生什么? 

调用: int c=func(1,2);  结果会出错,编译不过。

        编译器调用重载函数的准则:

将所有的同名函数作为候选者

尝试寻找可行的候选函数(精确匹配实参,通过默认参数能够匹配实参,通过默认类型转换匹配实参) 如果寻找道德候选函数不唯一,则出现二异性,编译失败。如果无法匹配所有候选者,函数未定义,编译失败。

        函数重载的注意事项:

重载函数在本质上是相互独立的不同的函数

重载函数的函数类型不同

函数返回值不能作为函数重载的依据

注意:函数重载是由函数名和参数列表决定的,与返回值没有关系。

        函数重载遇上函数指针:

将重载函数名赋值给函数指针时 1、根据重载规则挑选重载函数参数列表与函数指针参数列表一致的候选者 2、严格匹配候选者的函数类型(包括返回值类型)与函数指针的函数类型 (指向一致,与上不同)

typedef int(*PFUNC)(int a); //定义了一个函数指针类型PFUNC

PFUNC p=func;                      //通过函数指针类型PFUNC定义了一个函数指针p  将func标识符赋值给p指针变量

c=p(1);                             //通过指针变量p调用函数 

函数调用的是  int func(int x)       

严格匹配 参数列表和函数类型(包括返回值类型)

        注意:函数重载必然发生在同一个作用域中

                  编译器需要用参数列表进行函数选择(函数指针还需要函数类型相同)

                  无法直接通过函数名得到重载函数的入口地址

print("%p\n",(int(*)(int,int))add);   //将函数地址add强制转换为int(*)(int,int)

        c++和c相互调用:

c++编译器会优先使用c++编译的方式,extern关键字能强制让c++编译器进行c方式的编译 (c++编译器)

extern "C"    {  #include "add.h" }

    如何保证一段c代码只会以c的方式被编译?

_cplusplus是 c++编译器内置的标准宏定义 , 意义:

确保c代码以统一的c方式被编译成目标文件

#ifdef _cplusplus

extern " C" {

#endif

#include "add.h"

#ifdef _cplusplus

}

#endif

注意事项:

     c++编译器不能以c方式编译重载函数

    编译方式决定函数名被编译后的目标名(c++编译方式将函数名和参数列表编译成目标名。c 编译方式只将函数名作为目标名进行编译)

小结:

        函数重载是c++对c的一个重要升级        

        函数重载通过函数参数列表区分不同的同名函数

        extern关键字能够实现 c 和 c++的相互调用

        编译方式决定符号表中的函数名的最终目标名

14、c++中的新成员

        c++中的动态内存分配:

c++中通过new关键字进行动态内存申请

c++中的动态内存申请是基于类型进行的

delete关键字用于内存释放(释放掉指针所指向的对象)

        c 语言中用库函数 malloc 完成的,c是偏底层的语言,也许在硬件平台上是不支持的。c++中new成为关键字,是c++的一部分。在任何平台上都能动态内存分配。另一个是malloc是基于字节进行动态内存分配的,c++中动态内存分配是基于类型分配的。new在申请单个类型变量时可以初始化,malloc不具备内存初始化的特性。

        c++中的命名空间

在c语言中只有一个全局作用域,c语言中所有的全局标识符共享同一个作用域,标识符之间可能发生冲突。c++中提出命名空间的概念:命名空间将全局作用域分成不同的部分

            不同命名空间中的标识符可以同名而不会发生冲突

            命名空间可以相互嵌套

            全局作用域也叫默认命名空间

15、新型的类型转换

        c语言中的强制类型转换:(Type)(Expression)  ==>过于粗暴,任何类型之间都可以进行转换,编译器很难判断其正确性。 难于定位,在源码中无法快速定位所有使用强制类型转换的语句。

        c++中将强制类型转换分为4种不同的类型:xxx_cast<Type>(Expression) ,编译器能够帮助检查潜在的问题,非常方便的在代码中定位,支持动态类型识别(dynamic_cast)

static_cast: 静态的,用于基本类型间的转换,不能用于基本类型指针间的转换,用于有继承关系类对象之间的转换和类指针之间的转换。

        int i = 0x12345;
        char c = 'c';
        int* pi = &i;
        char* pc = &c;
        c = static_cast<char>(i);        //可以通过  
        pc = static_cast<char*>(pi);  //error    不能用于基本类型指针间的转换

const_cast: 用于去除变量的只读属性,强制转换的目标类型必须是指针或引用。

        const int& j = 1;
        int& k = const_cast<int&>(j);   //  对
        const int x = 2;
        int& y = const_cast<int&>(x);  //  对
        int z = const_cast<int>(x);       // error  强制转换的目标类型必须是指针或引用

dynamic_cast: 动态的,用于有继承关系的类指针间的转换,用于有交叉关系的类指针间的转换,具有类型检查的功能(不成功的话会返回空指针),需要虚函数的支持。

       int i = 0;
       int* pi = &i;
       char* pc = dynamic_cast<char*>(pi);   // error  没有虚函数

reinterpret_cast: 用于指针类型间的强制转换,用于整数和指针类型间的强制转换(整数<=>指针)。

      int i = 0;
      char c = 'c';
      int* pi = &i;
      char* pc = &c;
      pc = reinterpret_cast<char*>(pi);  //  对
      pi = reinterpret_cast<int*>(pc);    //   对
      pi = reinterpret_cast<int*>(i);      //    对

      c = reinterpret_cast<char>(i);     // error  不支持基本类型转换

16、const常量的判别准则

         只有用字面量初始化的const常量才会进入符号表

         使用其他变量初始化的const常量任然是只读变量   

         被  volatile (易变的) 修饰的 const 常量不会进入符号表

  在编译器间不能直接确定初始值的const标识符,都被作为只读变量。

     const引用的类型与初始化变量的类型:相同:初始化变量成为只读变量。 初始类型不同:生成一个新的只读变量

const int x=1;      // 符号表

const int& rx=x; // rx引用到编译器为x分配而没使用的空间,代表只读变量

int& nrx=const_cast<int&>(rx);  //去掉const属性

x,rx,nrx 的地址是一样的。

    判别是否是常量的标准是: 是否能在编译期间确定它的值。

    引用和指针:如何理解“引用的本质就是指针常量?”

指针是一个变量:

        值是一个内存地址,不需要初始化,可以保存不同的地址。

        通过指针可以访问对应内存地址中的值。

        指针可以被const修饰成为常量或者只读变量。

引用只是一个变量的新名字:

        对引用的操作(赋值,取地址等)都会传递到代表的变量上。

        const引用使其代表的变量具有只读属性。

        引用必须在定义时初始化,之后无法代表其它变量。

    从使用c++语言的角度来看,引用于指针没有任何的关系,引用是变量的新名字,操作引用就是操作对应的变量

   从c++编译器的角度来看,为了支持新概念“引用”必须要一个有效的解决方案,在编译器内部,使用指针常量来实现“引用”,因此“引用”在定义时必须初始化、

     在工程项目开发中,当进行c++编程时,直接站在使用的角度看待引用,与指针毫无关系,引用就是变量的别名。当对c++进行调试分析时,遇到一些特殊情况,可以考虑站在c++编译器的角度看待引用。

  注意:c++中不支持引用数组(会使得相邻的数组元素地址之差不是所期望的)

         指针是一个变量

         引用是一个变量的新名字。

         const引用能够生成新的只读变量。

         在编译器内部使用指针常量实现“引用”。

        编译时不能直接确定初始值的const标识符都是只读变量。

             





猜你喜欢

转载自blog.csdn.net/ws857707645/article/details/80146571