C++学习笔记05-偏八股向知识(问题-解答自查版)
前言
以下问题以Q&A形式记录,基本上都是笔者在初学一轮后,掌握不牢或者频繁忘记的点
Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系,也适合做查漏补缺和复盘。
本文对读者可以用作自查,答案在后面,需要时自行对照。
问题集
Q1:INT_MAX这个宏定义的具体数值是由什么决定的?
Q2:以下写法哪个有问题?
1)const int* a = &temp;
2)int const *a = &temp;
3)int* const p = &temp;
Q3:static关键字的作用
Q3.1:函数内static变量
Q3.2:类内static函数?如何理解:静态函数属于类而不是类的实例?
Q3.3:静态成员变量和静态函数,哪个只能在类外定义?
Q4:
1)new和malloc的区别?(返回&空间&语法元素&用法)
2)什么是自由存储区?
Q5:constexpr?怎么用?
Q6:volatile?
Q7:对于运算符"++"的重载,关于前置++与后置++的问题:
1)为什么后置++的函数需要返回对象,而不是引用?
2)为什么后置前面也要加const?
Q8:a++ 和 int a = b 在C++中是否是线程安全的?如何解决?
Q9:int (*ptr)(char); 和 int *ptr(char); 的区别?
Q10:什么是回调机制?如何实现?
Q11:关于cast强制转换的问题
Q11.1:解释:
double b = static_cast<double>(a);
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
int* b = const_cast<int*>(a);
Q11.2:这段代码中,p的动态转换为什么会抛出异常?
class Base {
public:
int no;
virtual void fun(){}
};
class Person : public Base{
public:
int age;
};
int main(){
Base b;
Person p = dynamic_cast<Person &>(b); // 这里为什么抛出异常?
Q13:动态强制转换 dynamic_cast是如何知道“enemy是一个Enemy对象”?C++的多态和C#的实现有何不同?
Q14:内存四区?
Q15:没有将基类的析构函数定义为虚函数,可能会导致什么问题?
Q16:如何防止内存泄露?
Q17:构造函数设为虚函数是有什么说法吗?
Q18: std::weak_ptr (弱引用智能指针)?
Q19:delete 释放的内存块的指针值会被设置为?
Q20:如何避免野指针
Q21:野指针和悬浮指针的区别
Q22:如何避免悬浮指针
Q23:分析运行下面的Test函数会有什么样的结果。
void GetMemory1(char* p)
{
p = (char*)malloc(100);
}
void Test1(void)
{
char* str = NULL;
GetMemory1(str);
strcpy(str, "hello world");
printf(str);
}
Q24:注意看这个free的位置,这段代码有什么问题?
void Test4(void)
{
char *str = (char*)malloc(100); strcpy(str, "hello");
free(str);
if(str != NULL) {
strcpy(str, "world");
cout << str << endl;
}
}
参考解答
Q1:INT_MAX这个宏定义的具体数值是由什么决定的?
A1:可以。头文件 <climits> 定义了符号常亮:例如:INT_MAX 表示 int 的最大值,INT_MIN 表示 int 的最小值。
INT_MAX 的值是由编译器和平台的整数大小决定的,而不是由 C++ 标准直接规定。
C++ 标准只规定了 int 类型必须至少有 16 位,但是大多数现代编译器都使用 32 位的 int 类型。
如果想知道你的系统上 INT_MAX 的具体值,可以简单地包含头文件并打印出来:
#include <iostream>
#include <climits>
int main() {
std::cout << "INT_MAX is " << INT_MAX << std::endl;
return 0;
}
Q2:以下写法哪个有问题?顶层指针和底层指针?
1)const int* a = &temp;
2)int const *a = &temp;
3)int* const p = &temp;
A2:哪个都没问题。对于1和2的合法性容易有记忆上的漏洞。
1和2都是底层const,指向的值不变。3是顶层指针,本身指向不变。
术语 "顶层" 和 "底层" 来源于const 关键字在声明中的相对位置。"顶层" 指的是指针变量本身的层面,而 "底层" 指的是指针所指向的数据的层面。这种命名方式有助于清晰地区分不同层面上的 const 限定,从而更好地理解指针的行为和限制。
本质上const类似于修饰,看修饰的是 *a 还是 p
Q3:static关键字的作用
Q3.1:函数内static变量
void exampleFunction() {
static int count = 0; //
count++;
cout << "Count: " << count << endl;
}
A3.1:静态变量:在函数内部使用 static 关键字修饰。
在程序的整个生命周期内存在,不会因为离开作用域而被销毁。
Q3.2:类内static函数?如何理解:静态函数属于类而不是类的实例?
class ExampleClass {
public:
static void staticFunction() {
cout << "Static function" << endl;
}
};
A3.2:和C#一样,静态函数属于类而不是类的实例,可以通过类名直接调用,而无需创建对象。
特点:不能调用非静态成员变量或者非静态函数。因为不能让非静态内容在它们生命周期开始前获知。
Q3.3:静态成员变量和静态函数,哪个只能在类外定义?
A:静态成员变量必须在类外部单独定义,以便为其分配存储空间。这是因为静态成员变量不属于类的任何特定实例,而是该类的所有实例共享的单一变量。由于静态成员变量与类的任何特定对象无关,它们在程序的全局命名空间中存在,因此需要在类外进行初始化。
class ExampleClass {
public:
static int staticVar; // 静态成员变量声明,合法
};
// 静态成员变量定义
int ExampleClass::staticVar = 0;
如果想在类内初始化,唯一的办法是加上const:
class ExampleClass {
public:
const static int staticVar; // 静态成员变量声明,合法
};
Q4:
1)new和malloc的区别?(返回&空间&语法元素&用法)
2)什么是自由存储区?
A4:
注意两个点:
1)new封装了malloc,只是添加了更多的内存维护
2)new在自由存储区上,实际上自由存储区也是堆的一部分,专门用来支持构造和析构的自动调用。
Q5:constexpr?怎么用?
A5:
const 表示“只读”的语义,constexpr 表示“常量”的语义
复杂系统中很难分辨一个初始值是不是常量表达式,可以将变量声明为constexpr类型,由编译器来验证变量的值是否是一个常量表达式。
必须使用常量初始化:
Q6:volatile?
A6:与该变量有关的运算,不要进行编译优化; // 不许抄近路
会从内存中重新装载内容,而不是直接从寄存器拷贝内容。 // 运行时变慢
使用场合:
在中断服务程序和CPU相关寄存器的定义
Q7:对于运算符"++"的重载,关于前置++与后置++的问题:
1)为什么后置++的函数需要返回对象,而不是引用?
2)为什么后置前面也要加const?
A7:
Q8:a++ 和 int a = b 在C++中是否是线程安全的?如何解决?
A8:都不是,编译器视角下都不是原子的。
C++11新标准提供了对整形变量原⼦操作的相关库,即std::atomic
std::atomic 是个模板类,具体用法:
std::atomic<int> value;
value = 99;
这里有个坑:
std::atomic<int> value = 99; 这个语法不能在g++上通过,因为禁止拷贝构造自动生成
Q9:int (*ptr)(char); 和 int *ptr(char); 的区别?
A9:
1)int (*ptr)(char); 是在声明一个指向函数的指针。这个指针可以指向一个接受 char 类型参数并返回 int 类型的函数。
2)而 int *ptr(char); 则不是声明一个函数指针,而是声明了一个函数。其修正格式应该是:int* ptr(char)
这个函数接受一个 char 类型的参数,并返回一个指向 int 的指针。
关于函数指针的示例代码:
int main(){
int (*ptr)(char);
ptr = &show;
cout << ptr('y') << (*ptr)('y') << endl;
// ptr('y')
// (*ptr)('y')
// 这两种调用方式在功能上是等价的,都能达到调用函数指针指向的函数的目的。然而,使用
// ptr('y') 的方式更为简洁,通常在C++中更常见。
Q10:什么是回调机制?如何实现?
A10:回调机制是一种编程模式,允许将一个函数作为参数传递给另一个函数,然后在需要的时候从内部灵活调用。
所以用上面讲的函数指针方法就可以实现。我们使用的 sort 算法就属于回调的一种应用。
除此之外,界面的用户操作处理也会用到
Q11.1:解释以下cast强制转换变量的作用:
double b = static_cast<double>(a);
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
int* b = const_cast<int*>(a);
A11:
强制转换
C风格的方法: int a = (int)c
C++风格的方法:
1)静态转换:用于非多态类型的转换,如基本数据类型,编译时而非运行时进行检查
int a = 10;
double b = static_cast<double>(a); // 将int转换为double
2)动态转换:用于处理多态性,只能在含有虚函数的类层次结构中使用
class Base { virtual void dummy() {} };
class Derived : public Base {};
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);// 父转子
if (derivedPtr) {
// 转换成功
}
3)const_cast:用来越界修改const值,emmmm...
使得不能改写的const类型数据变得可以修改:
const int* a = new int(10);
int* b = const_cast<int*>(a); // 移除const限定符
Q11.2:这段代码中,p的动态转换为什么会抛出异常?
class Base {
public:
int no;
virtual void fun(){}
};
class Person : public Base{
public:
int age;
};
int main(){
Base b
Person p = dynamic_cast<Person &>(b); // 这里为什么抛出异常?
A11.2:
类型不匹配:b 是 Base 类型的对象,而 Person 是从 Base 派生的类。dynamic_cast 用于类层次结构中的向下转型,即从基类向派生类转换。然而,这里的 b 实际上并不是 Person 类型的对象,而仅仅是 Base 类型的对象。
dynamic_cast 要求:dynamic_cast 需要对象实际上是目标类型的实例或者派生自目标类型。由于 b 不是 Person 或其派生类的实例,dynamic_cast 无法安全地进行转换。
Q12:对于动态强制转换,如何应用?
A12:安全的向下转型:主要用来“将Entity对象转化为Player或者Enemy对象”
我们假设基类Entity有两个子类:Player和Enemy,
通常情况下,Player或者Enemy转化成Entity比较简单,因为他们本来就是,只需要隐式转换即可、
但是“Entity转化为Player或者Enemy”,就必须要cast方法进行强制转化。
工程中,使用的指针可能是个什么样的对象未必清楚,因此这个功能主要用来做验证,
比如尝试进行转换 player→entity 是合法的,而 player→enemy 就会返回nullptr;
如果不这样做而是用 强制转换的方法(如 :player = (Player *)enemy ),语法上能通过,但是访问虚空成员就容易产生崩溃。
所以,工程上一定是用dynamic_cast的安全方法来进行对象的转换和验证
int main() {
Animal* aa = new Bird; // 注意这里创建的是Bird对象,但类型是Animal*
aa->func(); // 多态性:调用Bird的func
Bird* bb = dynamic_cast<Bird*>(aa); // 动态转换
if (bb) {
bb->func(); // 调用Bird的func
} else {
std::cout << "dynamic_cast failed" << std::endl;
}
delete aa;
return 0;
}
Q13:动态强制转换dynamic_cast是如何知道“enemy是一个Enemy对象”?C++的多态和C#的实现有何不同?
A13:dynamic_cast主要是通过RTTI(运行时类型识别)进行识别的,这种方式会使程序产生一定的开销。
在C++中,多态性主要通过虚函数和基类指针或引用来实现,而装箱和拆箱通常与C#等语言中的值类型和引用类型有关。
C++多态性是通过虚函数和运行时类型识别(RTTI)实现的,而C#装箱和拆箱是通过语言的类型系统和垃圾回收机制实现的。
Q14:内存四区?
A14:堆栈全常,还有个代码区
Q15:没有将基类的析构函数定义为虚函数,可能会导致什么问题?
A15:内存泄漏!
基类指针 p_entity 指向子类对象 player时,如果 Entity类的析构不是virtual的
那么这种情况下释放 p_entity 时不会调用子类析构函数,子类没正常释放资源,就会产生内存泄漏
Q16:如何防止内存泄露?
A16:将内存的分配封装在类中,构造函数分配内存,析构函数释放内存;使用智能指针
Q17:构造函数设为虚函数是有什么说法吗?
A17:没有意义。构造函数不应该被定义为虚函数
Q18: std::weak_ptr (弱引用智能指针)?
A18:std::weak_ptr 可以从 std::shared_ptr 创建,但不会增加引用计数,不会影响资源的释放。
通过 std::weak_ptr::lock() 可以获取⼀个 std::shared_ptr 来访问资源。
#include <memory>
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
Q19:delete 释放的内存块的指针值会被设置为?
A19:delete 释放的内存块的指针值会被设置为 nullptr ,以避免野指针。
free 不会修改指针的值,可能导致野指针问题。
Q20:如何避免野指针
A20:
1)在释放内存后将指针置为 nullptr
int* ptr = new int;
// 使⽤ ptr 操作内存
delete ptr;
ptr = nullptr; // 避免成为野指针
2)使用函数返回指针变量时,避免返回局部变量的指针;
3)使用智能指针:避免显式 delete,指针会在超出作⽤域时⾃动释放
Q21:野指针和悬浮指针的区别
A21:野指针一般涉及指针问题。危害:可能导致访问已释放或无效内存,引发崩溃或数据损坏。
悬浮指针一般是引用问题,即指向已被delete或free销毁对象的引用。危害:可能导致访问销毁对象的未定义行为
Q22:如何避免悬浮指针
A22:悬浮指针指向的内存可能已经被释放或重新分配,导致未定义行为和程序错误。其避免方法有:
使用智能指针:如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr,自动管理内存,减少手动管理内存的需要。
std::unique_ptr<int> ptr(new int); // 使用unique_ptr管理内存
避免跨函数返回局部指针:不要从函数返回局部变量的指针,因为局部变量在函数返回后生命周期结束。
int* getPointer() {
int localValue = 42;
return &localValue; // 错误:返回了局部变量的地址
}
Q23:分析运行下面的Test函数会有什么样的结果。
void GetMemory1(char* p)
{
p = (char*)malloc(100);
}
void Test1(void)
{
char* str = NULL;
GetMemory1(str);
strcpy(str, "hello world");
printf(str);
}
A23:运行 Test1 函数的结果很可能是程序崩溃,因为 strcpy 试图向一个空指针写入数据。
为什么说GetMemory1并不能传递动态内存?
void GetMemory1(char* p)
{
p = (char*)malloc(100);
}
在C语言中,函数参数传递是通过值传递来实现的,也就是说,当一个变量作为参数传递给函数时,实际上是传递了这个变量的一个副本。在你提供的GetMemory1函数中,参数p是一个char*类型的指针,它指向一个字符。
当你在函数内部使用malloc来分配内存并尝试将p指向这块新分配的内存时:
p = (char*)malloc(100);
这里,p指针的副本被修改了,它现在指向了新分配的内存区域。但是,这个修改只发生在函数内部,原始的指针(即传递给函数的那个副本)并没有被改变。因此,当函数执行完毕后,返回到调用函数时,原始的指针仍然指向它原来的位置,而不是新分配的内存区域。
这就是为什么说GetMemory1不能传递动态内存的原因。要正确地传递动态内存,你需要使用指针的指针或者引用的引用,这样修改内部的指针就可以反映到外部的指针上。例如:
void GetMemory2(char** p)
{
*p = (char*)malloc(100);
}
Q24:注意看这个free的位置,这段代码有什么问题?
void Test4(void)
{
char *str = (char*)malloc(100); strcpy(str, "hello");
free(str);
if(str != NULL) {
strcpy(str, "world");
cout << str << endl;
}
}
A24:篡改动态内存区的内容,后果难以预料。非常危险。
因为 free(str);之后,str成为野指针,if(str != NULL)语句不起作用。
原文地址:https://blog.csdn.net/qq_29068607/article/details/140669578
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!