C++一些常见问题
创始人
2025-06-01 01:11:55
0

宏是什么

参考博客地址
宏是C/C++所支持的一种语言特性,我对它最初的印象就是它可以替换代码中的符号,最常见的例子便是定义一个圆周率PI,之后在代码中使用PI来代替具体圆周率的值。
确实如此,宏提供了一种机制,能够使你在编译期替换代码中的符号或者语句。当你的代码中存在大量相似的、重复的代码时,使用宏可以极大的减少代码量,便于书写。
在很多书上以及网文上,宏都是不被推荐使用的,因为它会带来一些隐晦的坑,让你不经意间便受其所困。但是,它出现总有它的道理。
C语言中的NULL就是一个语言已经预定义的宏。预定义指的是你不必亲自定义,编译器在编译时,已经提前定义好了。

// 定义圆周率
#define PI 3.14159265
// 定义一个空指针
#define NULL ((void*)0)
// 定义一个宏的名字为 SYSTEM_API,但是没有值
#define SYSTEM_APIdouble perimeter = diameter * 3.14159265; // 等价于
double perimeter = diameter * PI;

继承和派生

http://c.biancheng.net/view/250.html

在 C++ 中,当定义一个新的类 B 时,如果发现类 B 拥有某个已写好的类 A 的全部特点,
此外还有类 A 没有的特点,那么就不必从头重写类 B,而是可以把类 A 作为一个“基类”(也称“父类”),
把类 B 写为基类 A 的一个“派生类”(也称“子类”)。这样,就可以说从类 A “派生”出了类 B,也可以说类 B “继承”了类 A。

虚函数

虚函数的作用是允许在派生类(子类)中重新定义与基类同名的函数,
并且可以通过基类指针或引用来访问基类和派生类中的同名函数。在基类中函数需要被virtual关键字说明,在子类中定义该虚函数时,virtual关键字可写可不写
但是为了清晰一般都写。在派生类中重新定义时,其函数类型、函数名、参数个数、参数类型的顺序,都必须与基类中的原型完全相同。
虚函数的作用是允许在派生类中重新定义与基类同名的函数,
并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

8.sizeof与strlen的区别?

(1)sizeof的返回值类型为size_t(unsigned int);

(2)sizeof是运算符,而strlen是函数;

(3)sizeof可以用类型做参数,其参数可以是任意类型的或者是变量、函数,
而strlen只能用char*做参数,且必须是以’\0’结尾;

(4)数组作sizeof的参数时不会退化为指针,而传递给strlen是就退化为指针;

(5)sizeo是编译时的常量,而strlen要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小;


数组和指针的区别?

可以看看 https://www.ccppcoding.com/archives/234654,写的不错

1. 数组要么在全局数据区被创建,要么在栈上被创建;指针可以随时指向任意类型的内存块;2. 修改内容上的差别:char a[] = “hello”;a[0] = ‘X’;char *p = “world”; // 注意p 指向常量字符串p[0] = ‘X’; // 编译器不能发现该错误,运行时错误3. 用运算符sizeof 可以计算出数组的容量(字节数)。
sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p所指的内存容量。
C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

cpp的类型转换

静态转换、动态转换、常量转换和重新解释转换。

1 静态转换 Static Cast
int i = 10;
float f = static_cast(i); // 静态将int类型转换为float类型
静态转换是将一种数据类型的值强制转换为另一种数据类型的值。
静态转换通常用于比较类型相似的对象之间的转换,例如将 int 类型转换为 float 类型。
静态转换不进行任何运行时类型检查,因此可能会导致运行时错误2动态转换 Dynamic Cast
动态转换通常用于将一个基类指针或引用转换为派生类指针或引用。
动态转换在运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。
class Base {};
class Derived : public Base {};
Base* ptr_base = new Derived;
Derived* ptr_derived = dynamic_cast(ptr_base); // 将基类指针转换为派生类指针3 常量转换 Const Cast
常量转换用于将 const 类型的对象转换为非 const 类型的对象。
常量转换只能用于转换掉 const 属性,不能改变对象的类型。
const int i = 10;
int& r = const_cast(i); // 常量转换,将const int转换为int4 重新解释转换(Reinterpret Cast)
重新解释转换将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。
重新解释转换不进行任何类型检查,因此可能会导致未定义的行为。
int i = 10;
float f = reinterpret_cast(i); // 重新解释将int类型转换为float类型

sizeof 介绍


const 有什么用途?

const是一个限定符,被const限定的变量其值不会被改变。
所以,const变量必须在定义时就被初始化。1 可以定义const常量 
const int bufSize = 512;
2修饰函数的返回值和形参;在C++中,还可以修饰函数的定义体,定义类的const成员函数。
被const修饰的东西受到强制保护,可以预防意外的变动,提高了程序的健壮性。

const和#define有什么区别?

const和#define都可以定义常量,但是const用途更广。
const 常量有数据类型,而宏常量没有数据类型,编译器可以对前者进行类型安全检查。
而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

const引用和非const引用区别

const引用是指向const对象的引用。const引用必须被定义为const类型。
const int a = 100;
const int &refa = a;  // correct:引用和被引用都是const类型
int &refa = a;  // error:引用和被引用const类型const引用可以被读取但是不可以被修改引用对象,任何对const引用进行赋值都是不合法的,
它适用指向const对象的引用,而非const的引用,不适用于指向const对象的引用。

指针和引用的区别?

指针和引用都提供了间接操作对象的功能。引用必须在创建时被初始化。指针可以在任何时间被初始化。
一旦引用被初始化为一个对象,就不能被指向到另一个对象。
指针可以在任何时候指向到另一个对象。
int a = 100;
int &refa = a;  // 正确:&refa引用a
int &refa = b;  // 错误:引用对象必须初始化
int &refa = 10;  //错误:右值必须是对象
int& b; //错误:不存在空引用,引用必须连接到一块合法内存试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此可以通过原始变量名称或引用来访问变量的内容。例如:
例如
int main() {int a = 5;int& b = a;//在这里 & 不是取地址操作符,而是类型标识符的一部分。正如 char* s 也是类型标识符的一部分,表示      	//一个指向 char 类型的指针变量,而我们的 int& 表示一个指向 int 类型的引用变量。cout << &a << endl; // 取a的地址cout << &b << endl; // 取b的地址int c = 20;b = c;cout << a << endl; // 输出a修改后的值cout << b << endl; // 输出b修改后的值return 0;
}
输出
0x7fff5fbff80c
0x7fff5fbff80c
20
20
这里a和b指向相同的值和内存单元。
而由于b是a的引用,一直关联a,不会成为c的引用,而是直接对b赋值,而对b赋值就相当于对a赋值,这是改变a值的另一种方法。
所以指针和引用有赋值行为的差异:指针赋值是将指针重新指向另外一个对象,而引用赋值则是修改对象本身;后续部分可以参考 可以参考primer书,也可以参考博客 https://blog.csdn.net/qq_40873884/article/details/79632314
指针之间存在类型转换,而引用分const引用和非const应用,非const引用只能和同类型的对象绑定,const引用可以绑定到不同但相关类型的对象或者右值,

什么是析构函数

创建对象时系统会自动调用构造函数进行初始化工作,
同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,
这个函数就是析构函数。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,
它不会返回任何值,也不能带有任何参数。
析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
下面的实例有助于更好地理解析构函数的概念:
#include 
using namespace std;class VLA{
public:VLA(int len);  //构造函数~VLA();  //析构函数
public:void input();  //从控制台输入数组元素void show();  //显示数组元素
private:int *at(int i);  //获取第i个元素的指针
private:const int m_len;  //数组长度int *m_arr; //数组指针int *m_p;  //指向数组第i个元素的指针
};VLA::VLA(int len): m_len(len){  //使用初始化列表来给 m_len 赋值if(len > 0){ m_arr = new int[len];  /*分配内存*/ }else{ m_arr = NULL; }
}
VLA::~VLA(){delete[] m_arr;  //释放内存
}
void VLA::input(){for(int i=0; m_p=at(i); i++){ cin>>*at(i); }
}
void VLA::show(){for(int i=0; m_p=at(i); i++){if(i == m_len - 1){ cout<<*at(i)<=m_len){ return NULL; }else{ return m_arr + i; }
}int main(){//创建一个有n个元素的数组(对象)int n;cout<<"Input array length: ";cin>>n;VLA *parr = new VLA(n);//输入数组元素cout<<"Input "< input();//输出数组元素cout<<"Elements: ";parr -> show();//删除数组(对象)delete parr;return 0;
}运行结果:
Input array length: 5
Input 5 numbers: 99 23 45 10 100
Elements: 99, 23, 45, 10, 100~VLA()就是 VLA 类的析构函数,它的唯一作用就是在删除对象(第 53 行代码)后释放已经分配的内存。

什么是智能指针?

当类中有指针成员时,一般有两种方式来管理指针成员:
一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;
另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。智能指针的一种通用实现技术是使用引用计数。
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;
当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

C++中有malloc/free,为什么还有new/delete?

用malloc和free
int *p = (int*) malloc( sizeof(int) * 10 );  //分配10个int型的内存空间
free(p);  //释放内存用new和delete
int *p = new int; 
//分配1个int型的内存空间,new 操作符会根据后面的数据类型来推断所需空间的大小。
delete p;  //释放内存如果希望分配一组连续的数据,可以使用 new[]:
int *p = new int[10];  //分配10个int型的内存空间
delete[] p; // 用 new[] 分配的内存需要用 delete[] 释放,它们是一一对应的。和 malloc() 一样,new 也是在堆区分配内存,必须手动释放,否则只能等到程序运行结束由操作系统回收。
为了避免内存泄露,通常 new 和 delete、new[] 和 delete[] 操作符应该成对出现,
并且不要和C语言中 malloc()、free() 一起混用。在C++中,建议使用 new 和 delete 来管理内存,它们可以使用C++的一些新特性,
最明显的是可以自动调用构造函数和析构函数,后续我们将会讲解。

malloc/free是C/C++标准库函数,new/delete是C++运算符。他们都可以用于动态申请和释放内存。

对于内置类型数据而言,二者没有多大区别。
malloc申请内存的时候要制定分配内存的字节数,而且不会做初始化;
new申请的时候有默认的初始化,同时可以指定初始化;

对于类类型的对象而言,用malloc/free无法满足要求的。
对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。
由于malloc/free是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要new/delete。


CPP源码到文件的过程

每个 .cpp 都是独立的编译单元。
.cpp 源文件经过预处理,将 #include, #ifndef, #define 之类的预处理指令替换成具体的源码,
得到独立的,没有文件依赖的 C++ 源码。
之后才进入编译过程,生成一个的目标文件。
目标文件在 Unix 平台上后缀为 .o,在 Windows 平台上后缀为 .obj。
编译之后进入链接过程,多个 .o 文件可以链接成库,包括静态库(.a)或者动态库(.so)。
多个库和 .o 文件,经过链接过程,也可以链接成可执行文件。这整个生成过程,可以粗略分解成,预处理(preprocess),编译(complie),链接(link) 三个过程。
上面从我将这个过程描述成线性的,实际上的编译系统也可以设计成一边预处理一边编译。
但从概念上理解成上一过程的输出是下一过程的输入,这样会容易些。C++ 标准库的函数很多都是预先编译好的,生成 C++ 程序的时候,只需要链接就行了。
也会有编译选项,选择采用动态链接还是静态链接。
而 STL 之类的模板库通常不能预先编译,采用 #include 源码方式来使用,
经过预处理过程之后,模板库的源码也会被重新编译,链接的时候,模板生成的重复代码剔除。
STL 也会分拆成多个头文件,用到的才会编译。

静态连接和动态链接

什么是链接库
计算机中,有些文件专门用于存储可以重复使用的代码块,例如功能实用的函数或者类,
我们通常将它们称为库文件,简称“库”(Library)。
所谓链接库,其实就是将开源的库文件进行编译、打包操作后得到的二进制文件。
虽然链接库是二进制文件,但无法独立运行,必须等待其它程序调用,才会被载入内存
用c语言为例,为大家展示的就是一个函数库://myMath.c
int add(int a, int b) {return  a + b;
}int sub(int a, int b) {return  a - b;
}int mul(int a, int b) {return  a * b;
}int div(int a, int b) {if (b != 0) {return a / b;}return -1;
}
大部分人不会直接分享源代码。而是分享文件的二进制版本,----链接库
一个完整的 C 语言项目可能包含多个 .c 源文件,项目的运行需要经过“编译”和“链接”两个过程:编译:由编译器逐个对源文件做词法分析、语法分析、语义分析等操作,最终生成多个目标文件。
每个目标文件都是二进制文件,但由于它们会相互调用对方的函数或变量,还可能会调用某些链接库文件中的函数或变量,编译器无法跨文件找到它们确切的存储地址,所以这些目标文件无法单独执行。链接:对于各个目标文件中缺失的函数和变量的存储地址(后续简称“缺失的地址”),
由链接器负责修复,并最终将所有的目标文件和链接库组织成一个可执行文件。注意,一个目标文件中使用的函数或变量,可能定义在其他的目标文件中,也可能定义在某个链接库文件中。
链接器完成完成链接工作的方式有两种,分别是:
无论缺失的地址位于其它目标文件还是链接库,链接库都会逐个找到各目标文件中缺失的地址。采用此链接方式生成的可执行文件,可以独立载入内存运行;
链接器先从所有目标文件中找到部分缺失的地址,然后将所有目标文件组织成一个可执行文件。如此生成的可执行文件,仍缺失部分函数和变量的地址,待文件执行时,需连同所有的链接库文件一起载入内存,再由链接器完成剩余的地址修复工作,才能正常执行。

请讲述堆和栈的区别。

1 申请方式不同。
栈上由系统自动分配和释放;
堆上有程序员自己申请并指明大小;

2
栈是向低地址扩展的数据结构,大小很有限;
堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活;

3
栈由系统分配和释放速度快;
堆由程序员控制,一般较慢,且容易产生碎片;

字符数组与字符串的比较:

https://www.runoob.com/cplusplus/cpp-strings.html
最明显的区别是字符串会在末尾自动添加空字符。
字符串实际上是使用 null 字符 \0 终止的一维字符数组

请描述进程和线程的区别?

1进程是程序的一次执行,线程是进程中的执行单元;2进程间是独立的,这表现在内存空间、上下文环境上,线程运行在进程中;3一般来讲,进程无法突破进程边界存取其他进程内的存储空间;而同一进程所产生的线程共享内存空间;4同一进程中的两段代码不能同时执行,除非引入多线程。

在网络编程中涉及并发服务器,使用多进程与多线程的区别?

1 线程执行开销小,但不利于资源管理和保护;进程则相反,进程可跨越机器迁移。

2 多进程时每个进程都有自己的内存空间,而多线程间共享内存空间;

3 线程产生的速度快,线程间通信快、切换快;

4 线程的资源利用率比较好;

5 线程使用公共变量或者资源时需要同步机制。

相关内容

热门资讯

安卓系统苹果手机识别,跨界融合... 你知道吗?在科技飞速发展的今天,手机已经成为了我们生活中不可或缺的一部分。而说到手机,安卓系统和苹果...
harmonyos系统是不是安... 亲爱的读者,你是否曾好奇过HarmonyOS系统与安卓系统之间的关系?是不是安卓的“亲戚”?今天,就...
手机怎么装系统安卓,安卓系统安... 手机卡顿了?想给安卓系统来个大变身?别急,跟着我一步步来,保证让你的手机焕然一新!一、准备工作在开始...
安卓Linux系统内网穿透,A... 你有没有想过,你的安卓手机里那些看似普通的APP,其实可能正在悄悄地帮你打通网络世界的任督二脉呢?没...
win怎么安装安卓系统,Win... 亲爱的读者,你是不是对Win系统上的安卓应用垂涎已久,但又苦于不知道如何安装安卓系统呢?别急,今天我...
升级小米平板安卓系统,畅享全新... 你有没有发现,你的小米平板用久了,是不是感觉有点卡呢?别急,今天就来教你怎么给它来个系统升级,让它焕...
捷豹安卓系统车载,捷豹安卓系统... 哇,你有没有想过,当你的手机和汽车融为一体,会是怎样的体验呢?想象你正驾驶着你的捷豹,车窗外的风景如...
安卓1到10系统,安卓1.0至... 你有没有想过,手机里的安卓系统就像是我们生活中的好朋友,从青涩的少年成长为稳重的青年呢?从安卓1.0...
安卓8.0停用系统应用,提升使... 你知道吗?最近安卓系统又来了一次大动作,那就是安卓8.0系统开始停用一些系统应用了。这可真是让人有点...
安卓系统修改mtu值,轻松提升... 你有没有想过,你的安卓手机其实是个小小的电脑呢?它里面藏着许多可以自定义的秘密功能,就像修改MTU值...
安卓平板改window系统,探... 你有没有想过,你的安卓平板其实可以摇身一变,变成一个Windows系统的电脑呢?没错,就是那种可以运...
时空猎人安卓苹果系统,探索无尽... 你知道吗?最近在手机游戏圈里,有一款叫做《时空猎人》的游戏可是火得一塌糊涂呢!不管是安卓用户还是苹果...
安卓9.0系统的电视,新一代电... 亲爱的读者们,你是否也像我一样,对科技新玩意儿充满好奇?今天,我要和你聊聊一个让人眼前一亮的话题——...
小pc安装安卓系统,轻松安装安... 你有没有想过,你的小PC也能变身成为安卓系统的超级玩家呢?没错,就是那个平时默默无闻的小家伙,现在也...
高通备份安卓系统,全方位数据安... 你知道吗?在这个科技飞速发展的时代,手机备份可是个不得不提的话题。尤其是对于安卓用户来说,选择一个靠...
谷歌安卓系统有多少,从诞生到全... 你有没有想过,那个无处不在的谷歌安卓系统,究竟在全球有多少用户呢?它就像一个神秘的数字,每天都在悄悄...
fc黄金传说安卓系统,畅享复古... 你有没有听说最近安卓系统上的一款超酷的游戏——《FC黄金传说》?这款游戏可是让不少玩家都沉迷其中,今...
变小的我安卓系统,安卓系统演变... 你有没有发现,最近你的手机好像变轻了?没错,说的就是你,那个陪伴你多年的安卓系统。它悄无声息地进行了...
vivo安卓系统小彩蛋,体验科... 你知道吗?在vivo的安卓系统中,竟然隐藏着一些超有趣的小彩蛋!这些小彩蛋就像是在手机里埋下的宝藏,...
安卓系统如何强制重启,安卓系统... 手机突然卡壳了,是不是又该给它来个“大保健”了?没错,今天就来聊聊安卓系统如何强制重启。别小看这个看...