一,C到C++的升级
1,C++中更强调语言的“实用性”,所有的变量都可以在需要使用时再定义。
2,register可有可无,C++编译器会自动优化,可有对register变量取地址。
3,在C++中,不允许定义多个同名的全局变量
4,C++中的const修饰的是常量,会进入符号表。但是如果被extern和&修饰,会分配空间。但也是只读变量。C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。除非用volatile修饰
5,C+中的const常量与宏的区别
- const常量是由编译器处理的,提供类型检查和作用域检查
- 宏定义由预处理器处理,单纯的文本替换
6,struct在C和C++的不同
- C语言的struct定义了一组变量的集合,C编译器并不认为这是一种新的类型
- C++中的struct是一个新类型的定义声明
- c:struct student s1;c++:student s1;
7,C++中所有的变量和函数都必须有类型
在C语言中:
- int f();表示返回值为int,接受任意参数的函数
- int f(void);表示返回值为int的无参函数
- 默认函数返回值类型和参数类型都是int型
在C++中:
- int f();和int f(void)具有相同的意义,都表示返回值为int的无参函数
二,C++中的引用
1,C++中的布尔类型
- C++中的bool可取的值只有true和false
- 理论上bool只占用一个字节,如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现
- C++编译器会在赋值时将非0值转换为true,0值转换为false
2,三目运算符的升级
- C语言中的三目运算符返回的是变量值,不能作为左值使用
- C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方
- 三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用
- 当三目运算符的可能返回都是变量时,返回的是变量引用
- 当三目运算符的可能返回中有常量时,返回的是值
3,C++中的引用
- 引用可以看作一个已定义变量的别名,引用的语法:Type& name = var;
- 普通引用在声明时必须用其它的变量进行初始化。
- const引用让变量拥有只读属性
- 引用在C++中的内部实现是一个常指针,Type& name ==Type* const name
- 当函数返回值为引用时
-
- 若返回栈变量
-
-
- 不能成为其它引用的初始值
- 不能作为左值使用
-
-
- 若返回静态变量或全局变量
-
-
- 可以成为其他引用的初始值
- 即可作为右值使用,也可作为左值使用
-
- 引用数组和数组的引用
-
- int &array[] 这是引用数组,数组元素是引用,这是非法的。
- int (&array)[]是数组的引用是合法的
- 为什么数组的元素不能是引用
C++不支持传统意义的复制:传统的复制为:int a = b;这里a和b在内存中分别占用不同的内存空间,但是内容一致。如果int& a = b; 这个时候,内存中a并不被分配内存,所以没有复制可言。所以对于数组元素是引用来说,没法完成元素的复制操作,没有给数组分配内存,所以数组中的元素不能是引用
三,函数的升级
1,内联函数
- C++中推荐使用内联函数替代宏代码片段
- C++中使用inline关键字声明内联函数
- 内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求。如果在类中声明为内联,即使在类外定义时没有加inline,也会强制转换为内联函数
- 内联函数的特点
-
- C++编译器可以将一个函数进行内联编译
- 被C++编译器内联编译的函数叫做内联函数
- 内联函数在最终生成的代码中是没有定义的
- C++编译器直接将函数体插入函数调用的地方
- 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
- C++编译器不一定准许函数的内联请求!
- 现代C++编译器能够进行编译优化,因此一些函数即使没有inline声明,也可能被编译器内联编译,另外,一些现代C++编译器提供了扩展语法,能够对函数进行强制内联,如:g++中的__attribute__((always_inline))属性
- 内联函数与宏的不同
-
- 内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)
- 内联函数由编译器处理,直接将编译后的函数体插入调用的地方
- 宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程
- 内联编译的限制
-
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
- 函数内联声明必须在调用语句之前
- 编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销。因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。
- 内联函数的实现机制
2,函数默认参数
- C++中可以在函数声明时为参数提供一个默认值,当函数调用时没有指定这个参数的值,编译器会自动用默认值代替
- 只有参数列表后面部分的参数才可以提供默认参数值
- 一旦在一个函数调用中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数值
3,函数占位参数
- 占位参数只有参数类型声明,而没有参数名声明
- 一般情况下,在函数体内部无法使用占位参数
- 可以将占位参数与默认参数结合起来使用
- 占位符参数一般用于程序扩展和对C代码的兼容
4,函数重载
- 函数重载至少满足下面的一个条件:
-
- 参数个数不同
- 参数类型不同
- 参数顺序不同
- 编译器调用重载函数的准则
-
- 将所有同名函数作为候选者
- 尝试寻找可行的候选函数
- 精确匹配实参
- 通过默认参数能够匹配实参
- 通过默认类型转换匹配实参
- 匹配失败
-
-
- 最终寻找到的可行候选函数不唯一,则出现二义性,编译失败。
- 无法匹配所有候选者,函数未定义,编译失败。
-
- 函数重载注意事项
-
- 重载函数在本质上是相互独立的不同函数
- 重载函数的函数类型是不同的
- 函数返回值不能作为函数重载的依据
- 函数重载是由函数名和参数列表决定的
- 函数重载与函数指针
-
- 当使用重载函数名对函数指针进行赋值时
-
-
- 根据重载规则挑选与函数指针参数列表一致的候选者
- 严格匹配候选者的函数类型与函数指针的函数类型
-
5,C++和C的相互调用
C++编译器既可以以C++的方式编译,也可以以C的方式编译。但是默认是C++的编译方式。可以利用extern “C”强制使C++编译器对代码进行C方式编译。
假如分为两个文件,一个是C代码,一个是C++代码。
- C++调用C代码:在C++文件中用extern C{}包含C代码的头文件
- C调用C++代码:在C++文件中用extern “C”对代码进行C方式编译。需要用-lstdc++对.o文件进行链接。
同一解决方式:
__cplusplus是一个C++内置宏
__cplusplus的意义:让C代码即可以通过C编译器的编译,也可以在C++编译器中以C方式编译。都是以C方式编译的。
#ifdef _cplusplus
extern "c"{
#endif
#ifdef _cplusplus
}
#endif
C++编译器不能以C的方式编译多个重载函数,为什么?
- extern “C”中的重载函数经过C方式编译后将得到相同的函数名,因此extern “C”中不允许重载函数,但extern “C”中的函数可以与extern “C”之外的函数进行重载。
深入理解extern “C”:
- C方式的编译主要指按照C语言的规则对函数名进行编译
- C++编译器为了支持重载,函数名经过编译后会加上参数信息,因而编译后的函数名与源码中完全不同
四,新的关键字
1,C++中的动态内存分配
- C++中通过new关键字进行动态内存申请
- C++中的动态内存申请是基于类型进行的
- delete关键字用于内存释放
-
new关键字与malloc函数的区别
-
- new关键字是C++的一部分,malloc是由C库提供的函数
- new以具体类型为单位进行内存分配,malloc只能以字节为单位进行内存分配
- new在申请单个类型变量时可进行初始化,malloc不具备内存初始化的特性
- new和delete会调用构造函数和析构函数。
2,C++中的命名空间
- 基本概念
-
- 命名空间将全局作用域分成不同的部分
- 不同命名空间中的标识符可以同名而不会发生冲突
- 命名空间可以相互嵌套
- 全局作用域也叫默认命名空间
- C++命名空间的使用:
-
- 使用整个命名空间:using namespace name;
- 使用命名空间中的变量:using name::variable;
- 使用默认命名空间中的变量:::variable
3,强制类型转换
- C方式的强制类型转换存在的问题
-
- ( Type )( Expression ) or (Type) Expression
- 过于粗暴
- 任意类型之间都可以进行转换,编译器很难判断其正确性
- 难于定位
- 在源码中无法快速定位所有使用强制类型转换的语句
- C++的强制类型转换种类
-
- static_cast,const_cast,dynamic_cast,reinterpret_cast
- 用法:xxx_cast< Type >( Expression )
- static_cast用法
-
- 用于基本类型间的转换,但不能用于基本类型指针间的转换
- 用于有继承关系类对象之间的转换和类指针之间的转换
- static_cast是编译期进行转换的,无法在运行时检测类型,所以类类型之间的转换可能存在风险。
- const_cast用法
-
- 用于去除变量的const属性
- reinterpret_cast用法
-
- 用于指针类型间的强制转换
- 用于整数和指针类型间的强制转换
- reinterpret_cast直接从二进制位进行复制,是一种极其不安全的转换。
- dynamic_cast用法
-
- 主要用于类层次间的转换,还可以用于类之间的交叉转换(对象指针转换)
- dynamic_cast具有类型检查的功能,比static_cast更安全
五,经典问题解析
1,指针与引用的区别
- 指针是一个变量,其值为一个内存地址,通过指针可以访问对应内存地址中的值
- 引用只是一个变量的新名字,所有对引用的操作(赋值,取地址等)都会传递到其引用的变量上
- 指针可以被const修饰成为常量或者只读变量
- const引用使其引用的变量具有只读属性
- 指针就是变量,不需要初始化,也可以指向不同的地址
- 引用天生就必须在定义时初始化,之后无法在引用其它变量
从三个方面记忆:内存方面,const方面,初始化方面
2,什么是符号表
- 符号表是编译器在编译过程中产生的关于源程序中语法符号的数据结构
- 如常量表、变量名表、数组名表、函数名表等等
- 符号表是编译器自用的内部数据结构
- 符号表不会进入最终产生的可执行程序中
3,const和引用的疑惑
- 只有用字面量初始化的const常量才会进入符号表
-
- 对const常量进行引用会导致编译器为其分配空间
- 虽然const常量被分配了空间,但是这个空间中的值不会被使用
- 使用其它变量初始化的const常量仍然是只读变量
- 被volatile修饰的const常量不会进入符号表
-
- 退化为只读变量,每次访问都从内存中取值
- const引用的类型与初始化变量的类型
-
- 相同:使初始化变量成为只读变量
- 不同:生成一个新的只读变量,其初始值与初始化变量相同