C++常见知识点
字符串
- C/C++
- 为节省内存,C/C++把常量字符串放在单独一个内存区域
- 当几个指针赋值给相同常量字符串时,实际上会指向相同的内存地址
- 当用常量字符串初始化数组时,会单独为其分配空间并赋值到数组中去
- C#
- C#字符串每次修改都会产生一个临时对象,只能通过返回值获得
- 若连续修改字符串则开销太大,可使用StringBuilder,能容纳修改后的结果
强制类型转换
typeA x = 强制类型转换符 <typeB> (y)
动静结合,底层常量
dynamic_cast
主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换
用法
-
其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
-
不能用于内置的基本数据类型的强制转换。
-
转换结果
- 若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
- 若对引用进行dynamic_cast,失败抛出一个异常bad_cast,成功返回正常cast后的对象引用。
-
使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。
- B中需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。
- 这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表的概念。
- 只有定义了虚函数的类才有虚函数表。
-
安全的向下转型(Safe Downcasting)
- 在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
- 向上转换,即为子类指针指向父类指针(一般不会出问题);向下转换,即将父类指针转化子类指针。
向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。
在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
class base { public: virtual void m() { cout << "m" << endl; } }; class derived : public base { public: void f() { cout << "f" << endl; } }; int main() { derived* p; base* q=new derived; //p = static_cast<derived*>(new base); //运行时错误 //p = dynamic_cast<derived*>(new base); //不会输出,因为向下转换失败 p = dynamic_cast<derived*>(q); //输出m f,因为原来就是指向derived类型 p->m(); p->f(); return 0; }
static_cast
用法
- 用于基本数据类型之间的转换
- 把空指针转换成目标类型的空指针
- 用于类层次结构中基类和派生类之间指针或引用的转换
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
- 进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,是不安全的
- 把任何类型的表达式转换为void类型
const_cast
用来移除变量的const或volatile限定符。
用法
- const_cast强制转换对象必须为指针或引用
- 用来移除变量的const或volatile限定符。
volatile 关键字:
用于修饰变量,表示变量随时可能变化,每次使用它时系统必须重新从它所在的内存读取数据
可以防止编译器访问优化,可以保证对特殊地址的稳定访问,如寄存器变量或多线程下的变量
编译器访问优化:
当编译器短时间内两次访问同一变量时,会把变量从内存装入 CPU 寄存器中,从而直接访问寄存器里的数据加快访问速度
reinterpret_cast
用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。
用法
- 改变指针或引用的类型
- 将指针或引用转换为一个足够长度的整形
- 将整型转换为指针或引用类型
在使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,因此在使用过程中需要特别谨慎!
浅拷贝和深拷贝
C++中类的拷贝有两种:深拷贝,浅拷贝:当出现类的等号赋值时,即会调用拷贝函数
在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。
-
当数据成员中没有指针时,浅拷贝是可行的;
-
当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝
strcpy和memcpy
- 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。企业中使用memcpy很平常,因为需要拷贝大量的结构体参数。memcpy通常与memset函数配合使用。
- 复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。因此strcpy会复制字符串的结束符“\0”,而memcpy则不会复制。
C++课外知识
宏定义与常量折叠
宏定义
又称为宏代换,宏替换
#define 宏名 字符串 //不带参数的宏定义,仅符号常量代换
#define 宏名(参数表)字符串 //带参数的宏定义
//多行宏定义
#define ADD(m,n)\
(m+n)
- 预处理(预编译)工作也叫做宏展开
- 预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
- 实参如果是表达式容易出问题
- 宏替换只作替换,不做计算,不做表达式求解
- 内存分配
- 对于不带参的宏定义
- 变量定义分配内存,宏定义不分配内存
- 对于带参数的宏定义
- 函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存
- 对于不带参的宏定义
- 宏展开不占运行时间,只占编译时间,函数调用占运行时间
- 可以用#undef命令终止宏定义的作用域
常量传播
常量传播是在编译优化时, 能够将计算出结果的变量直接替换为常量。
int a = 1;
printf("%d",a);
//优化后
printf("%d",1);
常量折叠
多个变量进行计算时,而且能够直接计算出结果,那么变量将由常量直接替换。常量折叠表面上的效果和宏替换是一样的
二者区别
- 宏是字符常量,在预编译完宏替换完成后,该宏名字会消失,因为所有该宏处已被替换成对应表达式
补充知识
ref
使用std::ref
可以在模板传参的时候传入引用,否则无法传递
void func(int& num)
{
cout<<num+1;
}
int answer = 1;
thread thd{func, ref(answer)};
链接文件
include<文件名> 和 #include"文件名" 的区别:
- 查找文件的位置:include<文件名> 在标准库头文件所在的目录中查找,如果没有,再到当前源文件所在目录下查找;#include"文件名" 在当前源文件所在目录中进行查找,如果没有;再到系统目录中查找。
- 使用习惯:对于标准库中的头文件常用 include<文件名>,对于自己定义的头文件,常用 #include"文件名"
extern "C" 表示按照C语言方式进行连接。
加上extern "C"之后,C++编译器就会按照C语言的符号修饰规则对函数进行修饰,这样连接器就能够正确解析。
sizeof(1==1)
C语言
sizeof(1 == 1) === sizeof(1)按照整数处理,所以是4字节,这里也有可能是8字节(看操作系统)
C++
因为有bool 类型
sizeof(1 == 1) == sizeof(true) 按照bool类型处理,所以是1个字节