C++学习笔记05-偏八股向知识(问题-解答自查版)

devtools/2024/9/20 3:52:53/ 标签: 算法, 开发语言, c++

前言

以下问题以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 bPerson 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)语句不起作用。


http://www.ppmy.cn/devtools/85033.html

相关文章

【图像标签转换】XML转为TXT图像数据集标签

引言 该脚本用于将包含对象标注的 XML 文件转换为 YOLO&#xff08;You Only Look Once&#xff09;对象检测格式的 TXT 文件。脚本读取 XML 文件&#xff0c;提取对象信息&#xff0c;规范化边界框坐标&#xff0c;并将数据写入相应的 TXT 文件。此外&#xff0c;它还生成一个…

Linus: vim编辑器的使用,快捷键及配置等周边知识详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 vim的安装创建新用户 adduser 用户名Linus是个多用户的操作系统是否有创建用户的权限查看当前用户身份:whoami** 怎么创建设置密码passwdsudo提权(sudo输入的是用户…

力扣 19删除链表倒数第N个结点

思路 双指针法&#xff0c;快指针用于与慢指针拉开距离&#xff0c;慢指针用于删除元素&#xff0c;越过慢指针后面的元素。 具体删除时&#xff0c;需要添加虚拟头结点&#xff0c;原因是如果倒数第N个结点是头结点的话&#xff0c;slow指向的就是头结点&#xff0c;没有办法…

AWS全服务历史年表:发布日期、GA和服务概述一览(四)

我一直在尝试从各种角度撰写关于Amazon Web Services&#xff08;AWS&#xff09;的信息和魅力。由于我喜欢技术历史&#xff0c;这次我总结了AWS服务发布的历史年表。 虽然AWS官方也通过“Whats New”发布了官方公告&#xff0c;但我一直希望能有一篇文章将公告日期、GA日期&…

每天一个设计模式之职责链模式(第一天)

特别感谢刘伟老师&#xff0c;看他的书我学到了很多东西&#xff0c;从今天开始我要开始更新啦&#xff01; 在csdn个人博客来总结知识&#xff0c;把他们变成自己的能力。 对三&#xff0c;要不起&#xff0c;张三李四王五几个人在玩斗地主&#xff0c;过过过&#xff0c;一…

前端位运算运用场景小知识(权限相关)

文前推荐一下&#x1f449;前端必备工具推荐网站(图床、API和ChatAI、智能AI简历、AI思维导图神器等实用工具): 站点入口&#xff1a;http://luckycola.com.cn/ 图床&#xff1a;https://luckycola.com.cn/public/dist/#/imghub 多种API&#xff1a;https://luckycola.com.cn/p…

Linux 常用命令详解:从基础操作到进阶应用

Linux 常用命令详解&#xff1a;从基础操作到进阶应用 简介 Linux 是一个强大且灵活的操作系统&#xff0c;它在服务器、开发环境和个人计算机中得到了广泛的应用。Linux 的命令行界面提供了丰富的工具和命令&#xff0c;可以帮助用户高效地管理系统、处理文件、监控性能和进…

# OpenCV 图像预处理—形态学:膨胀、腐蚀、开运算、闭运算 原理详解

文章目录 形态学概念膨胀使用膨胀操作来修复裂痕示例代码关键解析&#xff1a; 腐蚀使用腐蚀操作消除噪点示例代码&#xff1a; 开运算—先腐蚀后膨胀闭运算—先膨胀后腐蚀 形态学概念 首先看这两张图片 一张图周围有大大小小的噪音和彩点&#xff0c;另一张图片中字母有间隙&…

Vue自定义指令与Vue插槽学习

文章目录 自定义指令1.指令介绍2.自定义指令3.自定义指令语法4.指令中的配置项 自定义指令-指令的值1.使用效果2.语法 插槽-默认插槽1.作用2.用处4.插槽的基本语法 插槽-具名插槽1.作用2.具名插槽语法3.v-slot的简写 插槽总结1.插槽分类2.作用3.场景4.使用步骤 自定义指令 1.指…

hcip学习 多实例生成树,VRRP工作原理

一、STP 和 RSTP 解决了什么问题 1、STP&#xff1a;解决了在冗余的二层网络中所出现的环路问题 2、RSTP&#xff1a;在 STP 的基础上&#xff0c;解决了 STP 收敛速度慢的问题&#xff0c;引入了一些 STP 保护机制&#xff0c;使其网络更加稳定 二、MSTP 针对 RSTP 的改进 …

进程概念(三)----- fork 初识

目录 前言1. pid && ppid2. forka. 为什么 fork 要给子进程返回 0&#xff0c; 给父进程返回子进程的 pid &#xff1f;b. 一个函数是如何做到两次的&#xff1f;c. fork 函数在干什么&#xff1f;d. 一个变量怎么做到拥有不同的内容的&#xff1f;e. 拓展&#xff1a;…

Python爬虫技术 第15节 CSS选择器基础

在使用Python进行网页爬取时&#xff0c;CSS选择器是提取HTML文档中特定元素的常用方法之一。CSS选择器基于HTML元素的结构和属性来定位和选择页面中的元素。结合Python中的BeautifulSoup库或PyQuery库等&#xff0c;可以非常高效地解析和筛选出你想要的数据。 CSS选择器基础 …

MYSQL 七、mysql 日志与备份

一、其他数据库日志 千万不要小看日志。很多看似奇怪的问题&#xff0c;答案往往就藏在日志里。很多情况下&#xff0c;只有通过查看日志才 能发现问题的原因&#xff0c;真正解决问题。所以&#xff0c;一定要学会查看日志&#xff0c;养成检查日志的习惯&#xff0c;对提升你…

Android笔试面试题AI答之控件Views(6)

答案来着文心一言&#xff0c;仅供参考 目录 1.简述什么是RemoteViews?使用场景有哪些?RemoteViews的特性使用场景总结 2.获取View宽高的几种方法?1. 在onWindowFocusChanged方法中获取2. 使用ViewTreeObserver.OnGlobalLayoutListener3. 使用ViewTreeObserver.OnPreDrawLi…

QT:控件样式设置误区

当我设置不同控件格式样式&#xff0c;原先的代码如下 //设置MainWindow的背景R颜色this->setStyleSheet("QMainWindow{background-color:#F5F8FD;}");//设置菜单栏字体和背景颜色this->setStyleSheet("QMenuBar{color:#FFFFFF;background-color:#2A579A;…

浅聊一下编程!

在数字时代的浪潮中&#xff0c;编程已成为连接现实与虚拟世界的桥梁&#xff0c;它不仅是技术创新的基石&#xff0c;更是推动社会进步的强大动力。编程&#xff0c;这一看似深奥而复杂的技能&#xff0c;实则蕴含着无尽的创造力和可能性。本文将深入探讨编程的魅力所在&#…

C++ —— STL简介

1. 什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的 组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架 2.STL的版本 原始版本 Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本…

Golang | Leetcode Golang题解之第287题寻找重复数

题目&#xff1a; 题解&#xff1a; func findDuplicate(nums []int) int {slow, fast : 0, 0for slow, fast nums[slow], nums[nums[fast]]; slow ! fast; slow, fast nums[slow], nums[nums[fast]] { }slow 0for slow ! fast {slow nums[slow]fast nums[fast]}return s…

概率论三大分布

目录 基本概念 卡方分布&#xff08;χ分布&#xff09;&#xff1a; t分布&#xff1a; F分布&#xff1a; 延伸 卡方分布在哪些具体情况下最适合用于数据分析&#xff1f; t分布在大样本情况下的表现与正态分布相比如何&#xff1f; F分布在进行方差比较时与t分布的区…

springSecurity学习之springSecurity web如何取得用户信息

web如何取得用户信息 之前说过SecurityContextHolder默认使用的是ThreadLocal来进行存储的&#xff0c;而且每次都会清除&#xff0c;但是web每次请求都会验证用户权限&#xff0c;这是如何做到的呢&#xff1f; 这是通过SecurityContextPersistenceFilter来实现的&#xff0…