目录
引用和指针的区别
- 指针是一个实体,需要分配内存空间;引用只是变量的别名,不需要分配内存空间。
- 引用在定义的时候必须进行初始化,并且不能够改变。指针在定义的时候不一定要初始化,并且指向的空间可变。
- 指针和引用的自增运算结果不一样。(指针是指向下一个空间,引用时引用的变量值加1)
- sizeof 引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。
- 引用访问一个变量是直接访问,而指针访问一个变量是间接访问。
- 有多级指针,但是没有多级引用,只能有一级引用。
new/delete
问题
和malloc/free的区别
- new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
- new会自动调用对象的构造函数,而malloc只是单纯的分配内存。
- 参数:使用new操作符申请内存分配时不是必须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
- 返回类型:new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
- 内存分配失败时:new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
- 重载:new允许重载,malloc不允许
- 内存区域:new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
什么时候需要重载new/delete
- 通过重载new和delete操作符,我们有机会在对象创建和释放的时候做一些内存管理的工作。比如,每次new一个Obj对象,我们递增new被调用的次数。delete的时候再递减。当程序退出时,我们检查该次数是否归0。如果不为0,则表示有Obj对象没有被delete,这很可能就是内存泄露的潜在原因。
- 一般情况下,我们重载new和delete的目的是将内存创建和对象构造分隔开来。这样有什么好处呢?比如我们可以先创建一个大的内存,然后通过重载new函数将对象构造在这块内存中。当程序退出后,我们只要释放这个大内存即可。
const
作用
- 修饰变量,说明该变量不可以被改变;
- 修饰指针,分为指向常量的指针和指针常量;
- 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;
- 修饰成员函数,说明该成员函数内不能修改成员变量。
- 修饰对象,常对象只能调用常成员函数、更新常成员变量
使用
// 类
class A
{
private:
const int a; // 常对象成员,只能在初始化列表赋值
public:
// 构造函数
A() : a(0) {
};
A(int x) : a(x) {
}; // 初始化列表
// const可用于对重载函数的区分
int getValue(); // 普通成员函数
int getValue() const; // 常成员函数,不得修改类中的任何数据成员的值
};
void function()
{
// 对象
A b; // 普通对象,可以调用全部成员函数
const A a; // 常对象,只能调用常成员函数、更新常成员变量
a.getValue(); // 调用的是常成员函数
const A *p = &a; // 常指针
const A &q = a; // 常引用
// 指针
char greeting[] = "Hello";
char* p1 = greeting; // 指针变量,指向字符数组变量
const char* p2 = greeting; // 指针变量,指向字符数组常量(被const修饰的是char,说明char不可修改,是底层const)
char const *p3 = greeting; // 同上
char* const p4 = greeting; // 常指针,指向字符数组变量(被const修饰的是p4指针,说明指针不可修改,是顶层const)
const char* const p5 = greeting; // 常指针,指向字符数组常量(被const修饰的是char和p5指针,指针和指向对象的值都不能修改,第一个是底层const,第二个是顶层const)
}
// 函数
void function1(const int Var); // 传递过来的参数在函数内不可变
void function2(const char* Var); // 参数指针所指内容为常量
void function3(char* const Var); // 参数指针为常指针
void function4(const int& Var); // 引用参数在函数内为常量
// 函数返回值
const int function5(); // 返回一个常数
const int* function6(); // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7(); // 返回一个指向变量的常指针,使用:int* const p = function7();
问题
为什么常成员函数不能修改类中的数据成员?
class A
{
public:
int m_ia;
int setValue() const
{
m_ia=10;
}
// 常成员函数等价于
int setValue(const A *this)
{
this->m_ia=10;// 修改常指针的值是错误的!
}
}
static
作用
- 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
- 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。
- 修饰成员变量,修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
- 修饰成员函数,修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。
使用
- 静态数据成员必须单独初始化
- 静态成员函数不能调用非静态成员函数和非静态数据成员
- 静态数据成员只有一份,且不依赖对象而存在。
class Tank
{
public:
Tank(){
s_iCount++;}
~Tank(){
s_iCount--;}
static int getCount(){
return s_iCount;}
static int s_iCount;
}
int Tank::s_iCount=0;// 单独初始化,不用再加static关键字
int main()
{
cout<<Tank::getCount()<<endl;
cout<<Tank::s_iCount<<endl;
Tangk tank;
cout<<tank.getCount()<<endl;
cout<<tank.s_iCount<<endl;
return 0;
}
问题
为什么不能在类的内部定义以及初始化static成员变量,而必须要放到类的外部定义
- 答:因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。静态的数据成员不依赖于对象的实例化。因此,也不会在类的构造函数中去初始化。
static关键字为什么只能出现在类内部的声明语句中,而不能重复出现在类外的定义中
- 答:如果类外定义函数时在函数名前加了static,因为作用域的限制,就只能在当前cpp里用,类本来就是为了给程序里各种地方用的,其他地方使用类是包含类的头文件,而无法包含类的源文件。
强制类型转换符
const_cast
- const_cast主要是用来移除变量的const限定符。
使用
int num_e = 4;
const int* p_e = &num_e;
int* p_f = const_cast<int*>(p_e); //使用时一定要知道num_e不是const的类型。
*p_f = 5;
cout << "num_e: " << num_e << endl; // 输出5
cout << "p_e: " << *p_e << endl; // 输出5
cout << "p_f: " << *p_f << endl; // 输出5
- 如果所指向的对象是个底层const
const int num_e = 4; // 是个底层const
const int* p_e = &num_e;
int* p_f = const_cast<int*>(p_e);
*p_f = 5;
cout << "num_e: " << num_e << endl; // 输出4
cout << "p_e: " << *p_e << endl; // 输出5
cout << "p_f: " << *p_f << endl; // 输出5
-
这种行为属于未定义行为,且num_e的值并未改变
-
也可以使用const_cast将变量添加const限定符
const string& shorter(const string& s1, const string& s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
string& shorter(string& s1, string& s2) {
auto &r = shorter(const_cast<const string&>(s1), const_cast<const string&>(s2));
//auto等号右边为引用,类型会忽略掉引用
return const_cast<string&>(r);
}
static_cast
- 用于非多态类型的转换
- 不执行运行时类型检查(转换安全性不如 dynamic_cast)
- 通常用于转换数值数据类型(如 float -> int)
- 可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)
dynamic_cast
- 运行时类型识别RTTI
- 用于多态类型的转换
- 执行行运行时类型检查
- 只适用于指针或引用
- 对不明确的指针的转换将失败(返回 nullptr),但不引发异常;引用转换失败后会抛出std::bad_cast异常。
- 可以在整个类层次结构中移动指针,包括向上转换、向下转换