计算机考研复试C语言常见面试题「建议收藏」

2022-09-27 09:55:44 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

本文是我2021年考研时准备的复试面试题,现在拿出来给大家分享一下

觉得好的点个赞哦,毕竟当初我也是整理了好久,改了好几次版本呢

祝大家都上岸!!!!

P.S. 我当初整理的时候是word,直接复制过来的话代码不会自动变成CSDN的代码块,所以代码我是一段一段重新标记为CSDN代码段的,这样大家看起来舒服点

C语言基础 目录

1、static关键字的作用 2

2、C 和C的区别 2

3、Java的方法重载 2

4、重写和重载 2

5、面向对象编程 3

6、c 可以有多个父类 3

7、指针与引用 4

8、struct和class的区别(C ) 4

9、c 模板 4

10、内存泄漏 5

11、智能指针 6

12、野指针 7

13、new与malloc的区别 7

14、堆栈区 7

15、虚函数与纯虚函数 8

16、为什么析构函数必须是虚函数?为什么C 默认的析构函数不是虚函数 8

17、函数指针 9

18、fork函数 9

19、类构造和析构顺序 9

20、静态函数和虚函数的区别 10

21、静态多态与动态多态 10

22、const修饰普通函数与成员函数的目的 10

23、C语言参数压栈顺序 10

24、STL六大组件 10

25、C 源文件从文本到可执行文件经历的过程 10

1、static关键字的作用

  1. 隐藏

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。如果加了static,就会对其它源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。

  1. 保持变量内容持久

如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域不会发生改变,只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。

  1. 初始化

Static变量默认初始化为0.

对一个类中成员变量和成员函数来说,加了static关键字,则此变量/函数就没有了this指针了,必须通过类名才能访问。此时表示不依赖对象调用,它不与任何的对象相联系,由该类型的所有对象共享访问,故不存在this指针。

2、C 和C的区别

设计思想上:

C 是面向对象的语言,而C是面向过程的结构化编程语言

语法上:

C 具有封装、继承和多态三种特性

C 相比C,增加了许多类型安全的功能,比如强制类型转换、

C 支持范式编程,比如模板类、函数模板等

3、Java的方法重载

就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。

4、重写和重载

5、面向对象编程

(1)封装:将数据或函数集合在一个类中类。

(2)继承:子类可以继承父类的一些数据和函数。

(3)多态:运行时,可以通过指向基类的指针,调用派生类中的方法。

意义:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法

代码语言:javascript复制
class A{

public:

    A(){}

    virtual void foo(){

        cout<<"This is A."<<endl;

    }

};



class B: public A{

public:

    B(){}

    void foo(){

        cout<<"This is B."<<endl;

    }

};



int main(){

    A *a = new B();

    a->foo();

}

输出是B的输出,如果改为B *a = new B();则此时输出一致,但这个时候不叫做多态。

6、c 可以有多个父类

代码语言:javascript复制
class A{

public:

A(){cout << "A Constructor!bai" << endl;}

~A(){cout << "A Destructor!" << endl;}

};

class B{

public:

B(){cout << "B Constructor!" << endl;}

~B(){cout << "B Destructor!" << endl;}

};

//基类的构造函数按照继承时声明的先后顺序从前到后执行,

最后执行自己的构造函数;析构函数则按照相反的顺序执行。

class C:public A, public B {

public:

C(){cout << "C Constructor!" << endl;}

~C(){cout << "C Destructor!" << endl;}

};

int main(){

C c;

}

输出是

A Constructor!

B Constructor!

C Constructor!

C Destructor!

B Destructor!

A Destructor!

如果在每一类中都写deal函数,则最终只能执行c的deal函数。

7、指针与引用

指针有自己的空间

指针初始化可以为NULL

指针可以有多级指针(存在指向指针的指针)

指针初始化后可以再指向其他对象

引用只是一个别名

引用初始化时必须有一个对象

引用只能一级(没有引用引用的引用)

引用初始化后不能再引用其他对象

8、struct和class的区别(C )

共同点:struct和class都可以定义成员和函数,都具有继承、多态。

不同点:

class默认权限是private,struct默认权限是public。

class可以声明类模板,而struct不可以。

9、c 模板

模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。

函数模板:

代码语言:javascript复制
template <class T>

T Max (T a, T b) {

    return a < b ? b:a;

}

这样不用区分a和b是int还是double

类模板:

代码语言:javascript复制
template <class T>

class Stack {

  private:

    vector<T> elems;     // 元素

  public:

    void push(T const&);  // 入栈

    void pop();               // 出栈

    T top() const;            // 返回栈顶元素

    bool empty() const{       // 如果为空则返回真。

        return elems.empty();

    }

};



template <class T>

void Stack<T>::push (T const& elem)

{

    // 追加传入元素的副本

    elems.push_back(elem);    

}



template <class T>

void Stack<T>::pop ()

{

    if (elems.empty()) {

        throw out_of_range("Stack<>::pop(): empty stack");

    }

    // 删除最后一个元素

    elems.pop_back();         

}



template <class T>

T Stack<T>::top () const

{

    if (elems.empty()) {

        throw out_of_range("Stack<>::top(): empty stack");

    }

    // 返回最后一个元素的副本

    return elems.back();      

}

用的时候跟STL一样

Stack<int> intStack

10、内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

11、智能指针

智能指针的作用是管理一个指针,因为普通指针申请的空间在函数结束时常常忘记释放,从而造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。

智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

C 里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C 11支持,并且第一个已经被C 11弃用。

  1. auto_ptr

(C 98的方案,C 11已经抛弃)采用所有权模式。

auto_ptr<string> p1 (new string (“I reigned lonely as a cloud.”));

auto_ptr<string> p2;

p2 = p1;

此时访问p2正常,但是访问p1则无法正常访问,会出错。因为此时p2指向p1的内存地址,而p1则改为指向其他地址(实测指向0地址)

  1. unique_ptr

(替换auto_ptr)unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。此时上述代码会直接报错

  1. shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。它使用计数机制来表明资源被几个指针共享。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

  1. weak_ptr

当两个对象同时使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄露。为了解决循环引用导致的内存泄漏,引入了弱指针weak_ptr,weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:

shared_ptr<B> p = pa->pb_.lock();

p->print();

补充:智能指针泄露问题

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

代码语言:javascript复制
class B;    //声明

class A{

public:

    shared_ptr<B> pb_;

    ~A(){

        cout << "A deleten";

    }

};

class B{

public:

    shared_ptr<A> pa_;

    ~B(){

        cout << "B deleten";

    }

};

void fun(){

    shared_ptr<B> pb(new B());

    shared_ptr<A> pa(new A());

    cout << pb.use_count() << endl; //1

    cout << pa.use_count() << endl; //1

    pb->pa_ = pa;

    pa->pb_ = pb;

    cout << pb.use_count() << endl; //2

    cout << pa.use_count() << endl; //2

}

int main(){

    fun();

    return 0;

}

解决方案:为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。把类A里面的shared_ptr pb_,改为weak_ptr pb_即可

12、野指针

野指针就是指向一个已删除的对象或者所指向的空间是访问受限的空间的指针。

产生原因:

(1)指针变量未初始化

(2)指针释放后之后未置空

(3)指针操作超越变量作用域

13、new与malloc的区别

(1)new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配;

(2)new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。

(3)new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。

(4)new是一个运算符,malloc是一个库函数。

(5)new如果分配失败了会抛出异常,而malloc失败了会返回NULL。

14、堆栈区

stack栈区主要是存储函数的局部变量,然后程序结束后操作系统自行回收但是栈区容量比较小。一级缓存。从高地址向低地址移动。

heap堆区是程序里动态分配的内容,堆区的内存容量大,使用灵活,使用后要自行回收。容易产生内存碎片。二级缓存,速度比一级缓存慢。从低地址向高地址移动。

15、虚函数与纯虚函数

定义一个函数为虚函数,不代表该函数没有被实现,而是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。定义纯虚函数是为了实现一个接口,起到一个规范的作用,继承这个类必须实现这个函数。

代码语言:javascript复制
class A  {  

public:  

    virtual void foo()  {  

        cout<<"A::foo() is called"<<endl;  

    }  

};  

class B:public A  {  

public:  

    void foo()  {  

        cout<<"B::foo() is called"<<endl;  

    }  

};  

int main(void)  {  

    A *a = new B();  

    a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!  

    return 0;  

}

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

如:virtual void funtion1()=0

如果A中的virtual去掉以后,以上的结果将会是A的foo

16、为什么析构函数必须是虚函数?为什么C 默认的析构函数不是虚函数

析构函数设置为虚函数可以保证我们new一个子类时,可以使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

C 默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C 默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

17、函数指针

有了指向函数的指针变量后,可用该指针变量调用函数

代码语言:javascript复制
int Func(int x);   /*声明一个函数*/

int (*p) (int x);  /*定义一个函数指针*/

p = Func;          /*将Func函数的首地址赋给指针变量p*/

调用的时候直接(*p)(a, b)即可

优点:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。

比如提供了一个函数的原型,具体的实现用调用者来设定

代码语言:javascript复制
void test( void (*Fn)()){

    Fn();

}

void print(){

// 用户自己写

}

void QQ(){

// 用户自己写

}

test(print)

test(QQ)

18、fork函数

用途:创建一个和当前进程映像一样的进程

fork可能有三种不同的返回值:

(1)fork向父进程返回最新创建的子进程的进程ID;

(2)fork向新创建的子进程返回0,以告知它已经被成功创建;

(3)如果出现错误,fork返回一个负值;

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

19、类构造和析构顺序

构造:

基类成员对象的构造函数

基类的构造函数

子类成员对象的构造函数

子类的构造函数

析构:

子类的析构函数

子类成员的析构函数

基类的析构函数

基类成员的析构函数

两者正好相反

20、静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。

21、静态多态与动态多态

静态多态有两种实现方式:函数重载与函数模板的使用。

静态多态:也称为编译期间的多态,编译器根据函数实参的类型,可推断出要调用哪个函数,如果没有对应函数则出现编译错误。

动态多态主要是调用虚函数时,根据虚函数表确定具体调用的模块。

动态多态:即运行时的多态,在程序执行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。

22、const修饰普通函数与成员函数的目的

成员函数指的是class中的函数。

const修饰的函数表明函数调用不会对对象做出任何更改,事实上,如果确认不会对对象做更改,就应该为函数加上const限定,这样无论是const对象还是普通对象都可以调用该函数。因为非const对象是可以调用const函数的,而const对象无法调用非const函数。

23、C语言参数压栈顺序

从右到左

24、STL六大组件

(1)容器:各种数据结构,如Vector

(2)算法:各种常用算法如Sort

(3)迭代器

(4)仿函数

(5)配接器(适配器)

(6)分配器

25、C 源文件从文本到可执行文件经历的过程

预处理阶段:对源代码文件中头文件、宏定义进行分析和替换,生成.i文件。

编译阶段:将预编译文件转换成特定汇编代码,生成.s文件。

汇编阶段:将汇编文件转化成机器码,生成.o或者.obj文件。

链接阶段:连接所需要的库,形成最终的可执行目标文件,即.out或者.exe文件。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/174309.html原文链接:https://javaforall.cn

0 人点赞