面试八股文

局部变量、全局变量、静态变量、静态函数

普通局部变量:在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化,也就是说它的值在初始时是不确定的,除非对其显式赋值。普通局部变量存储于进程栈空间,使用完毕会立即释放。当定义它的函数结束时,其作用域也随之结束。

静态局部变量:静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变

全局变量:全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。

在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。

静态函数:函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数。其特性如下——

  • 静态函数只能在声明它的文件中可见,其他文件不能引用该函数
  • 不同的文件可以使用相同名字的静态函数,互不影响

非静态函数可以在另一个文件中直接引用,甚至不必使用extern声明

面向对象中的静态数据成员:存储在全局数据域,在定义时即分配了存储空间,是类的成员,不属于任何对象,在没有类的实例时其作用域就可见,在没有任何对象时就可以进行操作,对于这个类之存在一个拷贝——也就是在这个类下任一实例化的对象都可以对静态数据成员进行操作;而相对而言,非静态数据成员,每个对象都有自己的一份拷贝

面向对象中的静态成员函数:属于整个类,不属于某个对象,没有this指针,无法访问属于类对象的非静态数据成员或成员函数,只能调用其余的静态成员函数,但非静态成员函数可以任意访问静态成员函数和静态数据成员

static用法

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用

4)类内的static成员变量属于整个类所拥有,不能在类内进行定义,只能在类的作用域内进行定义

5)类内的static成员函数属于整个类所拥有,不能包含this指针,只能调用static成员函数(“this”指针能找到对象的所有非静态成员变量的地址)

解释const

1)在定义的时候必须进行初始化

2)指针可以是const 指针,也可以是指向const对象的指针

3)定义为const的形参(形参是函数被调用时用于接收实参值的变量),在函数内部是不能被修改的

4)类的成员函数可以被声明为常成员函数,不能修改类的成员变量

5)类的成员函数可以返回的是常对象,即被const声明的对象

6)类的成员变量是常成员变量时,不能在声明时初始化,必须在构造函数的列表里进行初始化

const如何做到只读?

这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定(因为结构体类型不是内置数据类型,编译器不知道如何直接替换,因此必须要访问内存去取数据)。

C++面向对象的特征:封装、继承、多态

  其中,封装可以隐藏实现细节,使得代码模块化;

  继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。

  而多态则是为了实现另一个目的——接口重用!

面向对象的七个原则

 

组合和继承的区别,多用哪个

 

多态和重载(也有说法将重载定义为静态多态)

多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

注意函数重载是C++语言特性,是静态的,和面向对象、多态无关,在编译器间就已经确定,而多态无法确定

多态的原理

通过继承重写基类的虚函数实现的多态,因为在运行时确定,所以称为动态多态。运行时在虚函数表中寻找调用函数的地址。(用指针或者取址实现)

在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是子类,就调用子类的函数;如果对象类型是父类,就调用父类的函数,(即指向父类调父类,指向子类调子类)此为多态的表现。

 

每一个有「虚函数」的类(或有虚函数的类的派生类)都有一个「虚函数表」,该类的任何对象中都放着虚函数表的指针。「虚函数表」中列出了该类的「虚函数」地址。

多出来的 8 个字节就是用来放「虚函数表」的地址。

复制代码
// 基类
class Base 
{
public:
    int i;
    virtual void Print() { } // 虚函数
};

// 派生类
class Derived : public Base
{
public:
    int n;
    virtual void Print() { } // 虚函数
};
复制代码

上面 Derived 类继承了 Base类,两个类都有「虚函数」,那么它「虚函数表」的形式可以理解成下图:

 

 多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令。

关于多态应用的场景例子:C++ 一篇搞懂多态的实现原理 - 知乎 (zhihu.com)

java和C/C++区别

1)java语言给开发人员提供了更为简洁的语法;取消了指针带来更高的代码质量;完全面向对象,独特的运行机制是其具有天然的可移植性;Java语言不需要程序对内存进行分配和回收。Java语言不使用指针,并提供了自动的废料收集,在Java语言中,内存的分配和回收都是自动进行的,程序员无须考虑内存碎片的问题。

2)C/C++开发语言,C语言更偏向硬件底层开发,C++语言是目前为止我认为语法内容最多的一种语言。c++用析构函数回收垃圾,C/C++在执行速度上要快很多,毕竟其他类型的语言大都是C开发的,更多应用于网络编程和嵌入式编程。

 

指针和引用的区别

1)引用是直接访问,指针是间接访问。

2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间

3)引用绑定内存空间(必须赋初值),一个变量别名不能更改绑定,可以改变对象的值。

总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性

 

头文件中的 ifndef/define/endif 干什么用?

预处理,防止头文件被重复使用

 

关于静态内存分配和动态内存分配的区别及过程

1)静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源;

2)静态内存分配是在栈上分配的,动态内存是堆上分配的;

3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;

4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。

5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员;

6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。

 

string的c++实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class String{
 
public:
    //普通构造函数
    String(const char *str = NULL);
 
    //拷贝构造函数
    String(const String &other);
 
    //赋值函数
    String & operator=(String &other) ;
 
//析构函数
~String(void);
private:
    char* m_str;
};

  

复制代码
//普通构造函数

String::String(const char* str){
    if(str==NULL) //如果str为NULL,存空字符串{
        m_str = new char[1]; //分配一个字节
        *m_str = ‘\0′; //赋一个’\0′
    }
    else{
       str = new char[strlen(str) + 1];//分配空间容纳str内容
        strcpy(m_str, str); //复制str到私有成员m_str中
    }
}

//析构函数
String::~String(){
    if(m_str!=NULL){ //如果m_str不为NULL,释放堆内存
        delete [] m_str;
        m_str = NULL;
    }
}

//拷贝构造函数
String::String(const String &other){
    m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
    strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中 
}

//赋值函数
String & String::operator=(String &other){
    if(this == &other) //若对象与other是同一个对象,直接返回本{
        return *this
}
    delete [] m_str; //否则,先释放当前对象堆内存
    m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
    strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中
    return *this;

}
复制代码

 C++智能指针

  1. C++98 提供了 auto_ptr 模板的解决方案

  2. C++11 增加unique_ptr、shared_ptr 和weak_ptr

(30条消息) C++ 智能指针 - 全部用法详解_cpp 智能指针_cpp_learners的博客-CSDN博客

中断

中断是指系统发生某一事件后,CPU暂停正在执行的程序转去执行处理该事件的程序过程,处理中断事件的程序称为中断处理程序,产生中断信号的那个部件称为中断源。硬件的中断机构与处理这些中断的程序统称为中断系统。

当中断发生时,硬件机构自动地进入响应中断过程,由操作系统的中断处理程序对中断事件进行处理,具体过程如下:

①.保存现场

系统开辟现场区,并将现场区组织成“栈”结构,当中断响应时,(1)硬件结构自动将PS和PC寄存器的内容压入栈中作为现场信息保存起来。(2)根据发生的中断,硬件从指定的中断向量单元中取出PS和PC内容,分别装入PS和PC寄存器,同时正确填入寄存器的“当前状态”和“先前状态”字段。

②.分析原因,转中断处理程序

不同原因产生的中断事件要进行不同的处理,根据中断的路寄存器内容得出发生该种中断的具体原因。转入相对应的中断处理程序运行。

③.恢复现场

在多级中断系统中,考虑退回当前中断时,必须依据原先被中断的程序,完成不同的工作,中断处理结束后,软件必须退出中断。如果此次是高级中断,并且被中断的程序是一个低级中断处理程序,则此次中断应返回到该低级中断处理程序。如果原来被中断的是用户程序,则退出中断前应先考虑进行一次调度选择,以挑选出更适合在当前情况下运行的新程序。

向量、多边形问题

射线法判断在多边形内外

点乘和叉乘判断是否为凹凸多边形

 

数据库

索引的概念

数据量特别大的情况下如何设计数据库表结构

TCP/UDP/Socket/HTTP

网络编程

网络编程的本质是多台计算机之间的数据交换。数据传递本身没有多大的难度,不就是把一个设备中的数据发送给其他设备,然后接受另外一个设备反馈的数据。现在的网络编程基本上都是基于请求/响应方式的,也就是一个设备发送请求数据给另外一个,然后接收另一个设备的反馈。在网络编程中,发起连接程序,也就是发送第一次请求的程序,被称作客户端(Client),等待其他程序连接的程序被称作服务器(Server)。客户端程序可以在需要的时候启动,而服务器为了能够时刻相应连接,则需要一直启动。

TCP/IP即传输控制/网络协议,是面向连接的协议,发送数据前要先建立连接(发送方和接收方的成对的两个之间必须建立连接),TCP提供可靠的服务,也就是说,通过TCP连接传输的数据不会丢失,没有重复,并且按顺序到达

UDP它是属于TCP/IP协议族中的一种。是无连接的协议,发送数据前不需要建立连接,是没有可靠性的协议。因为不需要建立连接所以可以在在网络上以任何可能的路径传输,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

 

TCP和UDP区别

1.TCP是面向字节流的,UDP是面向报文的;面向字节流是指发送数据时以字节为单位,一个数据包可以拆分成若干组进行发送,而UDP一个报文只能一次发完。

2.TCP是面向连接的协议,发送数据前要先建立连接,TCP提供可靠的服务,也就是说,通过TCP连接传输的数据不会丢失,没有重复,并且按顺序到达;UDP是无连接的协议,发送数据前不需要建立连接,是没有可靠性;

3.TCP只支持点对点通信,UDP支持一对一、一对多、多对一、多对多;

4.TCP首部开销(20字节)比UDP首部开销(8字节)要大

5.UDP 的主机不需要维持复杂的连接状态表

6.应用场景上:对某些实时性要求比较高的情况使用UDP,比如游戏,媒体通信,实时直播,即使出现传输错误也可以容忍;其它大部分情况下,HTTP都是用TCP,因为要求传输的内容可靠,不出现丢失的情况

socket编程

(30条消息) TCP、UDP、Socket、HTTP面试题(总结最全面的面试题!!!)_小杰要吃蛋的博客-CSDN博客

指针所占的空间

32位的计算机上是4字节,64位是8字节(地址)

内存中的存储空间

1.栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2.堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3.全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量、未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4.文字常量区 —常量字符串就是放在这里的。程序结束后由系统释放
5.程序代码区—存放函数体的二进制代码。

Segment falut错误原因(产生core dump)

内存访问越界
(1)由于使用错误的下标,导致数组访问越界
(2)搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
(3)使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

多线程程序使用了线程不安全的函数。

多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

非法指针
(1)使用空指针
(2)随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.

堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

排序算法和时间复杂度

排序大的分类可以分为两种:内排序和外排序。在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。下面讲的排序都是属于内排序。

内排序有可以分为以下几类:

  (1)、插入排序:直接插入排序、二分法插入排序、希尔排序。

  (2)、选择排序:直接选择排序、堆排序。

  (3)、交换排序:冒泡排序、快速排序。

  (4)、归并排序

  (5)、基数排序

 中断在操作系统中如何进行?

1.关中断:CPU响应后,要保护现场的状态,CPU不应该响应更高级别的中断,目的是将现场保存完整,否则在中断执行结束之后,无法恢复执行当前的程序。

2.保存断点:记录程序的断点,程序是由一些列的指令构成的,存在一个程序计数器,用于记录此时程序下一个要执行但由于中断没有发生的指令,保存下来,便于返回这个程序

3.引出中断服务程序:取出中断服务程序的入口地址,及使用程序计数器记录该程序的指令。

4.保存现场和屏蔽字:在进入中断程序后,要保存现场,指一些寄存器的内容,如程序的状态字寄存器PSWR和某些通用寄存器的内容

5.开中断:在完成以上的一系列的保存动作后可以接受更高级别的中断了

6.执行中断服务程序

7.关中断:执行完成后,要恢复现场,此时不能接收新的中断

8.恢复现场和屏蔽字:将之前保存的寄存器的内容恢复,对应了第四条

9.开中断、中断返回:中断服务程序的而最后一条指令通常是一条中断返回指令,返回原来的断点处,继续执行原程序。

宏定义最小值函数

#define MIN(x,y) ( (x)<(y)?x:y )

x和y外面加括号是考虑到如果x和y是表达式,会存在运算符优先的问题,比如与或运算的优先级低于比较大小符号

posted @   糖心葫芦  阅读(134)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示