c++面试

const与static的区别

‌const和‌static的主要区别在于它们修饰的对象的行为和属性。 const用于声明一个值不能被修改的常量,主要用于定义常量、修饰指针、函数的输入参数和返回值,以确保这些值在程序运行期间保持不变。const修饰的变量或对象具有不可变性,这有助于提高程序的安全性和可靠性。‌12

const的具体用途包括:

阻止变量被改变:使用const关键字可以阻止一个变量被改变。在定义const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了。‌
修饰指针:可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const。‌
修饰函数参数和返回值:在函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值。对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量。
static的具体用途包括:

修饰变量:当static修饰变量时,该变量具有静态存储期,即在整个程序运行期间都存在,而不是在函数调用时分配和释放内存。static修饰的变量可以在函数内部使用,但作用域仅限于定义该变量的文件内。‌
修饰函数:static可以限制函数的可见性,使其成为静态函数,只能在定义它的文件中使用。此外,在类中,被static修饰的函数是类的静态成员函数,属于类而不属于某一个特定对象,被所有对象共享。
修饰类的成员变量:在类中,被static修饰的成员变量是类的静态数据成员,属于类而不属于对象。静态数据成员在整个类中只有一份复制,被所有对象共享。
总结来说,const主要用于声明不可变的常量或参数,提高程序的安全性和可靠性;而static主要用于改变变量的存储方式、作用域或生命周期,实现一些特定的功能如单例模式或共享数据。

指针和引用的区别

‌指针和‌引用的区别主要包括以下几个方面:

本质与声明方式:
指针是一个实体,使用来声明,它本身存放的是另一个变量的地址。‌
引用则是一个别名,它并非新定义一个变量,而是给已存在变量取的一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。‌
初始化与赋值:
指针可以不初始化,也可以在任何时候指向一个同类型的实体,且可以被重新赋值。‌
引用则必须在定义时初始化,且一经初始化后就不能再引用其他实体。
空值与判断:
指针可以为空,即可以被初始化为nullptr或被赋值为NULL。
引用则不能为空,且不需要检查其有效性。‌
操作方式:
指针需要使用解引用符
来操作所指向的变量,且可以进行算术运算,如加减、比较等。‌
对引用的操作就是直接修改所引用的变量的值,无需解引用,且没有指针的算术运算。
内存与效率:
sizeof引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针本身的大小。‌
引用作为函数参数时,传递的是实参本身,避免了拷贝,效率更高;而指针作为函数参数时,传递的是指针变量的值,虽然也是地址,但相对于引用多了一层间接性。‌
多级与自增运算:
可以有指向指针的指针,即存在多级指针。‌
但没有指向引用的引用,即不存在多级引用。同时,指针和引用的自增(++)运算意义也不一样。
综上所述,指针和引用虽然都是C++中用于间接访问其他对象的机制,但它们在声明方式、初始化与赋值、空值与判断、操作方式、内存与效率以及多级与自增运算等方面存在显著差异

指针常量与常量指针

指针常量是指针本身的值(即所指向的地址)不能被修改的指针。在‌C/C++中,指针常量通常使用int const p这样的声明方式,其中const关键字位于指针声明操作符的右侧,表示p是一个常量指针,即指针p所指向的地址在初始化后不能被改变。‌

指针常量与常量指针是两个不同的概念:

指针常量:指针本身是常量,即指针所指向的地址不能改变,但指针所指向的内容(即该地址处的值)可以改变。‌

常量指针:指针指向的内容是常量,即指针可以指向不同的地址,但指针所指向的内容(即该地址处的值)不能被改变。常量指针的声明方式通常为const int *p或int const *p。‌

*指针常量不能作为函数重载使用**的原因在于,函数重载是基于函数的参数类型和数量来区分的,而指针常量与指针变量在参数类型上并无区别(都是指针类型),因此编译器无法根据指针是否为常量来区分不同的函数重载。‌

引用与指针常量、常量指针的关系*:引用在C++中是一个已存在变量的别名,它本身不是一个指针,但它在某些方面与指针有相似的行为,如都可以间接访问变量的值。然而,引用在声明时必须初始化,并且一旦初始化后就不能再指向其他变量,这与指针常量类似(指针常量在初始化后也不能再指向其他地址)。但引用本身并不是指针,也不是指针常量或常量指针。

数组指针和指针数组区别有:

1、数组指针是一个指针,而存指针数组是一个数组;2、数组指针的声明方式是int *p = arr;,而指针数组的声明方式是int *arr[5];;3、数组指针可以用p[i]的形式访问数组中的元素,而指针数组需要用arr[i]的形式访问数组中的元素。

程序执行的四个阶段

程序的执行过程可以分为四个阶段:编辑、预处理、编译、链接和运行。

编辑阶段:
使用系统提供的编辑程序,创建源程序文件。源程序文件是程序员用编程语言编写的代码。‌1

预处理阶段:
预处理阶段主要是对源程序进行“替代”操作,包括宏替换、条件编译、头文件展开和去除注释等。预处理后,生成一个没有宏定义、条件编译、特殊符号的输出文件。‌

编译阶段:
编译阶段将预处理后的输出文件进行词法分析、语法分析、语义分析以及优化,生成汇编代码文件。‌

链接阶段:
链接阶段将编译后生成的目标程序代码文件与系统提供的函数库等链接起来,生成可执行程序文件。‌

运行阶段:
运行阶段是计算机执行可执行程序文件,产生计算结果。

堆和栈的区别是:

1、栈是一种线性数据结构,而堆则是一种树状的数据结构;2、栈的内存分配方式是自动的,而堆的内存分配与释放需要手动管理;3、栈的内存分配速度相对较快,而堆的内存分配速度较慢;4、栈的大小是固定的,而堆的大小可以根据需要进行动态调整;5、栈适用于管理局部变量、函数调用和递归等,而堆适用于需要长时间存储的数据、动态数据结构和大型数据等。

sizeof和strlen区别

‌strlen用法和结果
strlen是一个函数,用于计算‌字符串的长度。其用法为strlen(char* str),其中str是指向以空字符(‘\0’)结尾的字符串的指针。strlen函数会遍历字符串,直到遇到第一个空字符(‘\0’),并返回从字符串开始到该空字符(不包括空字符)之间的字符数。‌

sizeof与strlen区别
类型与用法:
sizeof:是一个运算符,用于计算变量或数据类型所占用的字节数。其参数可以是数组、指针、类型、对象、函数等。 ‌
strlen:是一个函数,专门用于计算字符串的长度(不包括结尾的空字符’\0’)。‌
计算时机:
sizeof:在编译时计算,因此其结果是一个常量表达式。 ‌
strlen:在运行时计算,因为字符串的长度直到运行时才能确定。
返回值:
sizeof:返回的是无符号整数(unsigned int),表示字节数。‌
strlen:返回的是字符串的字符数(不包括结尾的空字符’\0’),也是无符号整数(size_t)。
对字符串的处理:
当使用sizeof计算字符串数组的大小时,它返回的是整个数组所占用的字节数,包括字符串末尾的空字符’\0’。‌
而strlen仅计算字符串中字符的数量,不包括末尾的空字符’\0’。
参数类型:
sizeof:可以接受数组、指针、类型、对象、函数等多种类型的参数。
strlen:只能接受字符指针(char*)作为参数,且该字符串必须以空字符’\0’结尾。‌

malloc与new的主要区别

‌和‌在‌C和‌C++中主要用于动态内存管理,但它们之间存在显著的区别。

属性和来源: malloc是C语言标准库中的一个函数,属于语言外部的库函数,需要包含相应的头文件(如stdlib.h或malloc.h)。而new是C++的一个操作符,属于语言内建的一部分,不需要额外包含任何头文件。‌

使用方式: malloc需要显式地指定要分配的内存大小(以字节为单位),而new操作符更为简洁和类型安全,它根据所创建对象的类型自动计算所需内存,并且如果对象有构造函数,还会自动调用构造函数。

内存位置与类型安全: malloc分配的内存位于堆空间,返回的是void*指针,需要手动进行类型转换,可能导致类型安全问题。而new操作符分配的内存位于自由存储区,直接返回与所创建对象类型匹配的指针,无需进行类型转换,提高了类型安全性。‌

错误处理: 当内存分配失败时,malloc返回NULL,需要开发者手动检查。而new操作符在内存分配失败时会抛出bad_alloc异常,这使得new在错误处理方面更为灵活和强大。

malloc可以不free,new必须delete。

什么是内存分区

内存分区是指计算机系统中用来存储程序运行时数据和指令的物理空间的划分。‌这种划分对程序的性能和稳定性有着重要的影响。‌在计算机程序的运行过程中,‌内存分区的概念涉及到多个区域,‌包括堆区、‌栈区、‌全局/静态存储区和代码区。‌

堆区:‌由编程人员手动申请和释放,‌如果编程人员不手动释放,‌程序结束后由系统回收。‌堆的总大小为机器的虚拟内存大小,‌使用malloc或new进行申请。‌在C语言中,‌malloc是一个函数,‌而new是C++中的操作符。‌使用new申请内存时,‌不仅会分配内存,‌还会自动调用类的构造函数,‌而malloc只负责内存的分配,‌不会调用类的构造函数。‌

栈区:‌由系统进行内存管理,‌主要存放函数的参数以及局部变量。‌当函数完成执行后,‌系统自行释放栈区内存,‌不需要用户管理。‌整个程序的栈区大小可以在编译器中由用户自行设定。‌

全局/静态存储区:‌存放全局变量、‌静态变量和常量。‌这些变量在程序编译阶段已经分配好内存空间并初始化。‌这块内存在程序的整个运行期间都存在。‌

代码区:‌存放二进制代码的地方。‌为了防止代码被非法修改,‌通常设置为只读。‌

什么是内存泄露

指程序中已经不再需要使用的内存没有被释放,从而造成内存资源浪费和程序性能下降。其特征是程序使用内存总量持续增加,直到程序崩溃或者系统强制关闭

动态多态与静态多态

静态多态性(Static Polymorphism),也称为编译时多态性,是在编译时决定调用哪个方法。常见的实现方式包括方法重载和运算符重载。
方法重载(Function Overloading)是指在同一个类中,多个函数的名字相同,但参数列表不同(参数的数量或类型不同)。编译器根据函数调用时传递的参数来决定调用哪个具体函数
运算符重载(Operator Overloading)是指允许开发者定义或改变运算符的行为。在C++中,可以重载大多数运算符
动态多态性(Dynamic Polymorphism),也称为运行时多态性,是在运行时决定调用哪个方法。常见的实现方式包括虚函数和纯虚函数。
虚函数(Virtual Functions)允许子类重写父类的方法。在运行时,调用的具体方法取决于对象的实际类型
C++没有直接的接口,但可以通过纯虚函数(pure virtual function)和抽象类来实现接口的效果。

c++各种数据类型所占空间

在C++中,基本数据类型的大小取决于操作系统和编译器,但是有些规则是通用的。以下是一些常见基本数据类型的大小:

char: 通常是1字节。

int: 在32位系统上通常是4字节,在64位系统上通常是8字节。

float: 通常是4字节。

double: 通常是8字节。

long: 在32位系统上通常是4字节,在64位系统上通常是8字节,与int长度可能不同。

long long: 通常是8字节。

pointer: 在32位系统上通常是4字节,在64位系统上通常是8字节。

常见的C++强制类型转换:

static_cast:用于非多态类型的转换。

double d = 3.14;
int i = static_cast<int>(d); // 将double转换为int

dynamic_cast:用于多态类型的转换,主要用于向下类型转换(从基类指向派生类的指针/引用),会检查转换的有效性,如果转换不安全,则无法进行转换。

class Base { virtual void dummy() {} };
class Derived : public Base { /* ... */ };
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b); // 将Base指针转换为Derived指针

const_cast:用于去除 const 属性或者添加 const 属性。

const int ci = 10;
int* modifiable = const_cast<int*>(&ci); // 去除const属性

reinterpret_cast:用于将任何指针类型转换成任何其他的指针类型,可能也包括指针与足够大的整数类型之间的转换。

int* p = new int(65);
char* ch = reinterpret_cast<char*>(p); // 将int指针转换为char指针

动态链接与静态链接的区别

动态链接与静态链接的主要区别在于它们在程序运行时的链接方式和资源管理的不同。‌

动态链接是指在程序运行过程中,‌程序需要根据实际情况来决定是否加载某个外部库或模块。‌这种方式的优点包括灵活性高,‌可以根据需要加载不同的库或模块,‌实现模块化编程,‌同时可以实现共享库的更新,‌无需重新编译程序,‌节省了开发时间和成本。‌然而,‌动态链接的主要缺点是性能开销较大,‌因为需要不断地检查外部库或模块是否已经加载,‌这会增加程序的启动时间和运行时开销,‌还可能导致内存泄漏等问题,‌需要开发者更加关注内存管理。‌

静态链接是指在程序编译时,‌将所有外部库或模块的信息嵌入到可执行文件中,‌使得程序在运行时不再需要加载这些外部库或模块。‌静态链接的优点是性能较高,‌因为不需要在运行时检查外部库或模块是否已经加载,‌从而减少了程序的启动时间和运行时开销,‌还可以避免因外部库更新导致的兼容性问题,‌确保程序的稳定性。‌但静态链接的缺点是灵活性较差,‌因为所有外部库或模块的信息都嵌入到可执行文件中,‌无法实现模块化编程,‌还会增加可执行文件的大小,‌可能导致磁盘空间不足的问题。‌

在实际应用中,‌选择动态链接还是静态链接取决于具体的应用场景和需求。‌例如,‌对于需要频繁更新的库或模块,‌如操作系统、‌数据库等,‌建议使用动态链接以便在不修改程序的情况下更新库或模块。‌而对于性能要求较高的程序,‌如游戏、‌图形处理软件等,‌建议使用静态链接以减少程序的启动时间和运行时开销。‌此外,‌对于项目规模较大、‌模块化程度较高的项目,‌建议使用动态链接以实现更好的代码复用和扩展性。‌

深拷贝与浅拷贝

浅拷贝(引用拷贝):复制对象的指针、引用或者其他句柄等非资源属性,所有拷贝对象共享一份实际数据;
深拷贝:复制对象所有的数据,在堆区重新申请空间,进行拷贝操作

Qt中的connect函数有五种连接方式。‌

直接连接(‌Qt::DirectConnection)‌:‌适用于单线程和多线程环境,‌信号发出后,‌槽函数会立即被执行。‌
队列连接(‌Qt::QueuedConnection)‌:‌用于不同线程之间,‌信号发出后会被放入事件队列,‌等待槽函数线程从队列中获取并执行。‌这种方式下,‌信号发出后,‌发送者可以立即返回,‌无需等待槽函数的执行。‌
阻塞队列连接(‌Qt::BlockingQueuedConnection)‌:‌与队列连接类似,‌但信号发出后需要等待槽函数执行完成才能返回。‌
自动连接(‌Qt::AutoConnection)‌:‌根据信号和槽函数是否在同一线程自动选择直接连接或队列连接。‌
唯一连接(‌Qt::UniqueConnection)‌:‌确保相同的信号和槽函数在同一个对象中只能被连接一次,‌多次连接会失败。‌

tcp/ip协议五层协议

TCP/IP模型通常被认为是一个四层模型,包括物理层、链路层、网络层和传输层。然而,有时也将会话层和表示层添加到模型中,这样就形成了五层模型。

五层模型如下:

物理层(Physical Layer)

数据链路层(Data Link Layer)

网络层(Network Layer)

传输层(Transport Layer)

会话层(Session Layer)和表示层(Presentation Layer)

osi七层模型是什么

OSI七层模型是国际标准化组织(‌ISO)制定的一个用于计算机或通信系统间互联的标准体系,它是一个七层的、抽象的模型,不仅包括一系列抽象的术语或概念,也包括具体的协议。‌
OSI七层模型从低到高分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。‌

各层的功能如下:

物理层:负责传输原始比特流,定义物理设备的标准,如电缆类型、信号电压、传输速度等。‌46
数据链路层:负责将物理层传输的比特流封装成帧,添加MAC地址信息,进行错误检测和纠正。
网络层:负责在不同网络之间传输数据,进行路由选择和数据包转发。
传输层:提供端到端的数据传输服务,确保数据的可靠性、流量控制和错误检测。
会话层:管理会话的建立、维护和结束,处理会话层面的错误和同步问题。
表示层:负责数据的格式转换、加密和解密,确保应用程序能够正确地解释数据。
应用层:为用户提供网络应用服务,包括文件传输、电子邮件、网页浏览等。

tcp和udp的区别:

1、连接性,tcp是一种面向连接的协议,udp是一种无连接的协议;2、可靠性,tcp对数据的可靠性要求非常严格,udp对数据的可靠性要求较低;3、速度和效率,tcp要求建立连接和使用确认重传机制,udp不受拥塞控制的限制;4、数据包大小,tcp将数据划分为较小的数据包进行传输,udp允许将多个数据包打包成一个较大的数据报进行传输

TCP的三次握手和四次挥手是计算机网络中用于建立和终止TCP连接的过程。‌

三次握手:‌这是建立TCP连接的过程,‌通过三个步骤确保双方都准备好进行数据传输。‌具体步骤如下:‌

客户端向服务器发送一个SYN报文(‌请求连接)‌,‌报文中包含客户端选择的初始序列号。‌
服务器收到SYN报文后,‌会确认客户端的SYN报文,‌并发送一个SYN-ACK报文,‌表示同意建立连接,‌同时也会包含一个确认序列号,‌这个序列号是对客户端SYN报文的确认。‌
客户端收到服务器的SYN-ACK报文后,‌会发送一个ACK报文给服务器,‌确认收到并同意建立连接。‌

四次挥手:‌这是终止TCP连接的过程,‌通过四个步骤确保数据传输的完整性和可靠性。‌具体步骤如下:‌
客户端向服务器发送一个FIN报文,‌表示客户端不再发送数据,‌请求关闭连接。‌
服务器收到FIN报文后,‌会发送一个ACK报文,‌确认收到客户端的关闭请求。‌
服务器在完成所有数据的发送和确认后,‌也会向客户端发送一个FIN报文,‌表示服务器也不再发送数据,‌请求关闭连接。‌
客户端收到服务器的FIN报文后,‌会发送一个ACK报文,‌确认收到服务器的关闭请求,‌完成连接的关闭。‌

为什么需要三次而不是两次握手

举例:已失效的连接请求报文段。

client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server

本来这已经是一个失效的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。

假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求

但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的很多资源就没白白浪费掉了

采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。

为什么需要四次挥手

为了确保数据能够完成传输。

关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了

所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢.

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。

但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。

思考:客户端突然挂掉了怎么办?

正常连接时,客户端突然挂掉了,如果没有措施处理这种情况,那么就会出现客户端和服务器端出现长时期的空闲。

解决办法是在服务器端设置保活计时器,每当服务器收到客户端的消息,就将计时器复位。超时时间通常设置为2小时。

若服务器超过2小时没收到客户的信息,他就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,还没有响应就认为客户端出了故障,因而终止该连接。

红黑树和b树和b+树的区别

‌红黑树、‌B树和‌B+树是计算机科学中常用的三种数据结构,它们各有特点和适用场景。以下是它们之间的主要区别:

定义和基本性质:

红黑树是一种自平衡的二叉查找树,通过颜色标记和一系列规则来保持平衡,确保在最坏情况下的时间复杂度为O(log n)。红黑树主要用于需要高效查找、插入和删除操作的场景。‌1
B树(B-tree)和B+树是自平衡的多路搜索树,设计用于处理大量数据,特别是当数据需要存储在外部存储设备(如磁盘)上时。B树允许每个节点有多个子节点,这有助于减少磁盘I/O操作次数,提高查询效率。‌2
应用场景:

红黑树适用于需要快速查找、插入和删除操作的内存数据结构,如STL中的map和set等。‌3
B树和B+树则更适合处理大量数据,特别是当数据存储在外部存储设备上时。它们通过减少磁盘I/O操作次数来提高效率,常用于数据库系统。
结构和特点:

红黑树是一种二叉树,通过颜色(红色或黑色)和一系列规则来保持平衡。每个节点最多有两个子节点。
B树和B+树是多路搜索树,每个节点可以有多个子节点,这使得它们能够存储更多的数据在每个节点中,从而减少了磁盘I/O操作的次数。B+树与B树的主要区别在于其叶子节点的组织方式,B+树的叶子节点包含所有数据,而非叶子节点仅保存键值和指向子树的指针。
优缺点:

红黑树的优点包括高效的查找、插入和删除操作,适用于内存数据结构。缺点是当数据量非常大时,可能需要更多的内存空间。
B树和B+树的优点在于能够高效处理大量数据,特别是在外部存储设备上。它们通过减少磁盘I/O操作次数来提高效率。缺点是相对于红黑树来说,实现和维护可能更为复杂。
综上所述,红黑树、B树和B+树各有其独特的优势和应用场景。选择哪种数据结构取决于具体的应用需求,包括数据的规模、存储位置(内存或外部存储)以及操作类型(查找、插入、删除等)。

快速排序的实现步骤

从待排序的数组中选择一个元素,称之为枢纽元(pivot)。
将数组中小于枢纽元的元素移到枢纽元的左边,将大于枢纽元的元素移到枢纽元的右边,这个过程称为分区(partition)。
递归地对枢纽元左边的子数组和右边的子数组进行排序。
当所有子数组都有序时,整个数组就自然有序了。

STL中sort的底层是采用哪种或者哪几种排序?

采用三种排序
如果元素的个数小于等于32,那么就直接使用插入排序

如果元素的个数大于32,但是已经达到最大的递归深度,则采用堆排序
这里为什么采用堆排序,而不采用归并排序呢?因为堆排序不需要消耗额外的空间

如果元素的个数大于32,但还没达到最大的递归深度,则采用快速排序

GDB调试

GDB调试
‌GDB(GNU Debugger)是一个功能强大的命令行调试工具,主要用于‌C和‌C++程序的调试,但也支持其他语言如‌Go等。 GDB允许开发者设置断点、单步执行程序、查看变量值、内存和寄存器状态等,从而帮助定位和解决程序中的错误和异常。

基本使用:在使用GDB之前,需要确保程序在编译时添加了-g参数以包含调试信息。例如,使用gcc -g test.c -o test编译程序。然后,通过输入gdb test命令来启动GDB并加载程序。
设置断点:在GDB中,可以使用break或b命令在特定行或函数处设置断点。例如,b main会在main函数处设置断点,而b35会在第35行设置断点。
运行和调试:使用run或r命令启动程序。在断点处,程序会暂停,允许检查变量值和执行其他调试命令。可以使用‌或‌命令执行下一条指令,或使用‌或‌命令进入函数内部。
查看变量和信息:在GDB中,可以使用print或p命令查看变量的当前值。此外,还可以使用info breakpoints命令查看所有设置的断点,使用‌或‌命令查看源代码等。
高级功能:GDB还支持多线程调试、查看CPU寄存器和内存等高级功能。例如,使用thread apply all bt命令可以查看所有线程的堆栈跟踪。
通过这些功能,GDB成为软件开发中不可或缺的工具,特别是在需要深入理解程序运行时行为的情况下。‌

const与宏定义的区别

(1) 编译器处理方式不同

define宏是在预处理阶段展开。

const常量是编译运行阶段使用。

(2) 类型和安全检查不同

define宏没有类型,不做任何类型检查,仅仅是展开。

const常量有具体的类型,在编译阶段会执行类型检查。

(3) 存储方式不同

define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。

const常量会在内存中分配(可以是堆中也可以是栈中)。

(4)const 可以节省空间,避免不必要的内存分配。 例如:

#define PI 3.14159 //常量宏  
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......  
double i=Pi; //此时为Pi分配内存,以后不再分配!  
double I=PI; //编译期间进行宏替换,分配内存  
double j=Pi; //没有内存分配  
double J=PI; //再进行宏替换,又一次分配内存!  
    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。 

(5) 提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

const 与 #define的比较

C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:

(1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。

(2) 有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。

  • 18
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值