文章目录 11. C++编译过程 12. const vs #define 13. C++内存分区 14. C++变量作用域 15. C++类型转换 16. 函数指针 17. 悬空指针 vs 野指针 18. 为什么使用空指针,建议使用nullptr而不是NULL?
11. C++编译过程
预处理:处理以#开头的预处理指令,比如#include和#define等。 编译:将预处理后的源文件转汇编代码。 汇编:将汇编代码转机器指令,生成目标文件。 链接:将目标文件与相应的库文件进行链接,生成可执行文件。
12. const vs #define
# define MAX_SIZE 100
const int value = 50 ;
处理阶段: #define在预处理阶段进行替换。 const在编译阶段确定其值。 类型安全: #define只是简单的字符串替换,不进行类型检查,存在隐患。 const有数据类型,在编译时进行类型检查。 存储方式: #define有多少次替换,在内存中有多少个拷贝。 const定义的常量会分配内存空间,且在程序运行过程中内存只有一个拷贝。 调试信息: #define只是替换,不会产生调试信息。 const被视为变量,可以产生调试信息。如下图。 另外,#define可以定义简单的函数,而const不可以定义函数。
12.1. 全局const vs 局部const
全局const存储在常量区,无法通过指针修改。 局部const存储在栈区,是一个“假”常量,始终是一个变量,只是编译时进行语法检查,发现代码有对const修饰的变量修改时则报错。本质上时可以修改的,利用指针获取变量地址,强制将const属性修饰去掉,就可以修改对应内容,【注意】 会造成未定义行为。代码如下。
void print ( const int & a)
{ cout << a << endl;
} int main ( ) { const int num = 50 ; int * p = const_cast < int * > ( & num) ; * p = 20 ; print ( num) ; return 0 ;
}
分析:当print()函数的参数是引用或指针时,会传入修改后的const内容,而不会因为编译器的优化,即常量折叠,在编译时就替换。这是不当的操作。
13. C++内存分区
堆:用于动态分配内存,使用new或malloc手动分配,使用delete或free手动释放,注意内存泄漏的问题。 栈:存储局部变量和函数调用的控制信息,如返回地址、参数、局部变量等,由系统自动分配和释放。 全局区:存放全局变量、静态变量,程序结束后由系统释放。 常量区:存放字符串常量等,程序结束后由系统释放。 代码区:存放程序的二进制代码。
14. C++变量作用域
全局变量属于进程作用域,在整个进程中都可以访问到。 静态变量属于文件作用域,在当前源码文件内可以访问到。 局部变量属于函数作用域,在函数内可以访问到。 在’{ }'语句块内定义的变量属于块作用域,只能在该块内访问。
14.1. 常量 vs 全局变量 vs 静态变量
相同:程序执行前就存在了,即在编译期就已经确定了地址。通过立即数访问。 不同:作用域和内存分配 常量 全局常量,存放在常量区(只读数据段),整个文件内都可以访问到。 局部常量,存放在栈区,在函数内可以访问到。 全局变量,存放在全局区(可读写数据段),整个文件内都可以访问到。 静态变量 全局静态变量,存放在全局区(可读写数据段),当前文件内可以访问到。 局部静态变量,存放在全局区(可读写数据段),在函数内可以访问到。
15. C++类型转换
隐式类型转换由编译器自动完成 char,short——>int——>unsigned——>long——>double float ——>double 显式类型转换手工强制完成 使用() 标准转型操作符,能够避免许多任意转型引起的潜在错误,如下。
const_cast:去除或添加const、volatile属性。
int num = 42 ;
const int * p = const_cast < const int * > ( & num) ;
static_cast:用于常规类型转换,如数值之间的转换、指针或引用之间的转换。
double d = 3.14 ;
int i = static_cast < int > ( d) ;
dynamic_cast:用于多态对象(即存在虚函数的对象)间类型转换,将基类指针或引用转换为子类指针或引用,从而访问子类特有的成员函数。【注意】 引用转型失败会抛异常”bad_cast“;指针转型失败会返回一个空指针,如果漏写检查代码(assert/if语句)会导致安全隐患。
class Draw
{
public : virtual ~ Draw ( ) { } virtual void drawLen ( ) = 0 ;
} ; class Circle : public Draw
{
private : double radius; public : Circle ( double r) : radius ( r) { } ~ Circle ( ) { printf ( "%s%f\n" , "Delete circle with radius " , radius) ; } void getDescription ( ) { printf ( "%s%f\n" , "Circle with radius " , radius) ; } void drawLen ( ) { printf ( "%s%f\n" , "Circle with len " , 2 * 3.14 * radius) ; }
} ; int main ( )
{ Draw* d = new Circle ( 5 ) ; d-> drawLen ( ) ; Circle* c = dynamic_cast < Circle* > ( d) ; if ( c!= nullptr ) c-> getDescription ( ) ; delete d; return 0 ;
}
reinterpret_cast:对目标的内存二进制位进行低层次的重新解释。如将指针转换为整数、不同类型的指针之间的转换。【注意】 它会忽略指针类型和数据之间的任何差异,存在安全隐患,因此需要谨慎使用。
int num = 20 ;
double * d = reinterpret_cast < double * > ( & num) ;
16. 函数指针
函数调用是直接调用的;而函数指针是先取出指针的值(函数地址)再调用,是间接调用的。 应用场景 实现回调函数,比如在图形用户界面中,可以使用函数指针指定按钮点击事件的响应函数。 把函数指针当形参传递给某些具有通用功能的模块,并封装成接口来提高代码的灵活性,方便后期维护。 可以在排序和搜索算法中,使用函数指针提供自定义的比较逻辑,比如升序、降序,如下。
# include <iostream>
# include <vector>
using namespace std;
using CompareFunction = bool ( * ) ( int , int ) ;
void bubbleSort ( vector< int > & arr, CompareFunction compare) { int n = arr. size ( ) ; for ( int i = 0 ; i < n - 1 ; ++ i) for ( int j = 0 ; j < n - i - 1 ; ++ j) if ( compare ( arr[ j] , arr[ j + 1 ] ) ) swap ( arr[ j] , arr[ j + 1 ] ) ;
}
bool ascending ( int a, int b) { return a > b; }
bool descending ( int a, int b) { return a < b; } int main ( )
{ vector< int > numbers = { 5 , 2 , 8 , 1 , 4 } ; cout << "Ascending order:" << std:: endl; bubbleSort ( numbers, ascending) ; for ( auto && u : numbers) { cout << u << " " ; } cout << endl << "Descending order:" << endl; bubbleSort ( numbers, descending) ; for ( auto && u : numbers) { cout << u << " " ; } return 0 ;
}
程序执行结果,如下图。
17. 悬空指针 vs 野指针
悬空指针:当指向的内存被释放后,指针没有被及时置空。【注意】 访问悬空指针会导致未定义行为,如下。
int * ptr = new int ( 42 ) ;
delete ptr; * ptr = 20 ;
野指针:指针没有被初始化。【注意】 野指针的值是不确定的,可能指向任意的内存地址,访问野指针会导致未定义行为,如下。
int * ptr; * ptr = 20 ;
18. 为什么使用空指针,建议使用nullptr而不是NULL?
nullptr是空指针常量,可以隐式转换成任意指针类型,但不会自动转换为整数类型;而NULL是宏,整数类型,【注意】 可能会导致类型安全问题,如下。
void f ( int a) { cout << "parameter int" << endl; } void f ( void * a) { cout << "parameter void*" << endl; } int main ( ) { f ( NULL ) ; f ( nullptr ) ; return 0 ;
}