IE盒子

搜索
查看: 116|回复: 0

C++面试常见题目

[复制链接]

1

主题

10

帖子

15

积分

新手上路

Rank: 1

积分
15
发表于 2023-3-10 19:43:58 | 显示全部楼层 |阅读模式
31.&&和&、||和|有什么区别

& | 与或操作
&& 应该是引用的引用
||
左右值

30.short i = 0; i = i + 1L;这两句有错吗

代码一是错的,代码二是正确的。
说明:在数据安全的情况下大类型的数据向小类型的数据转换一定要显示的强制类型转换。
29.谈谈你对编程规范的理解或认识

可行性,可读性,可移植性,可测试性;
程序可以运行,代码简洁明了,变量,函数的起名能够让读者大概知道这个变量的作用。
23.

22.两个栈实现队列的功能

21.简述队列和栈的异同

队列:先入先出,
栈:先入后出,
20.怎样把一个链表反序?

19.链表和数组有什么区别?

存储:数组是一整块连接的存储区域,链表则可能是多块存储区域;
访问:数组可以通过下标访问,而链表只能通过从头节点逐次移动指针进行访问;
链表便与插入和删除,并且不会出现越界的情况。
类的大小怎么计算?

为什么析构函数建议声明为virtual

18.简述多态

对于一个类,如果存在虚函数,则编译器会为其生成一个虚表(虚函数表),还会插入一根虚指针,指向这个虚表,虚表中每一个包含指向对应虚函数的指针。当调用构造函数时候,编译器会自动将虚表指针与虚表关联,将vptr指向vtable。
主要用法是基类的指针指向子类的对象,在调用父类子类同名虚函数的时候,会调用派生类的虚函数,引用也是同理,调用的函数取决于指针指向对象的类型。
class Base
{
public:
    void fun1()
    {
        fun2();
    }
   
    virtual void fun2()  // 虚函数
    {
        cout << "Base::fun2()" << endl;
    }
};

class Derived : public Base
{
public:
    virtual void fun2()  // 虚函数
    {
        cout << "Derived:fun2()" << endl;
    }
};

int main()
{
    Derived d;
    Base * pBase = & d;
    pBase->fun1();
    return 0;
}上述代码的输出是 "Derived:fun2()",如不解,可以将基类中的func2()替换成this->func2();在Base::fun1()成员函数体里执行this->fun2()时,实际上指向的是派生类对象的fun2成员函数。
在非构造函数,非析构函数的成员函数中调用「虚函数」,是多态!!!
「多态」的关键在于通过基类指针或引用调用一个虚函数时,编译时不能确定到底调用的是基类还是派生类的函数,运行时才能确定。
每一个有「虚函数」的类(或有虚函数的类的派生类)都有一个「虚函数表」,该类的任何对象中都放着虚函数表的指针。「虚函数表」中列出了该类的「虚函数」地址。
17.简述类成员函数的重写、重载和隐藏的区别

重写:显示声明为overrid,用于派生类重写基类的函数,参数列表,函数名一定相同,若有差异,编译会报错;重写后;重写的函数在基类中必须被virtual修饰。
重载:是一个类中重载函数,函数参数列表一定不同;
隐藏:隐藏和被隐藏的函数不在同一个类中,函数名相同;当参数列表不一样时,无论基类的函数是否被virtual修饰,基类的函数都会被隐藏,而不是重写。
重载:静态多态。重写:动态多态。
class Base
{
public:
    virtual void f0() { cout << "Base::f0()...." << endl; }

};

class Derive : public Base{
public:
    void f0(int a) {
        cout << "Derive::f0(int a)..." << endl;
    }
   
    // void f0() override {
    //     cout << "Derive::f0() override..." << endl;   
    // }
    void f0() {
        cout << "Derive::f0()   hide..." << endl;  
    }
};16.访问基类的私有虚函数

15.用C++设计一个不能被继承的类

思考:如果直接把构造函数和析构函数设置为私有的话,虽然该类不能被继承,但同样不能实例化;所以使用友元,因为友元的关系是不能通过继承获得的,所以先让A的构造和拷贝成为私有,然后让B成为A的友元,并且让B虚继承A;此时如果C继承B的话,C是没有办法添加B的虚表中,所以无法通过B调用A的构造函数。
template<typename T>
class A {
  public:
    friend T;
  private:
    A() {};
    virtual ~A(){};
};

class B : public virtual A<B> {
  public:
    B(){std::cout << "B\n";}
};
// Class C : public B {};


class TaskManager final {/*..*/} ;
class PrioritizedTaskManager: public TaskManager {
};  //compilation error: base class TaskManager is final这里需要说明的是:我们设计的不能被继承的类B对基类A的继承必须是虚继承,这样一来C类继承B类时会去直接调用A的构造函数,而不是像普通继承那样,先调用B的构造函数再调用A的构造函数;
虚继承时子类的虚函数不再是添加到父类部分的虚表中,而在普通的继承中确实直接添加到父类的虚表中,这就意味着如果虚继承中子类父类都有各自的虚函数,在子类里面就会多出一个虚表指针,而普通的继承却不会这样。
14.谈谈堆拷贝构造函数和赋值运算符的认识

拷贝构造函数:通过一个已经存在的对象来生成一个新的对象实例;是一种构造函数,创建新的实例;拷贝构造函数必须以引用的方式传递参数。
赋值运算符:将一个已经存在的对象的值复制给另一个已经存在的实例;是一种运算;
class Person
{
public:
        Person(){}
        Person(const Person& p)
        {
                cout << "Copy Constructor" << endl;
        }

        Person& operator=(const Person& p)
        {
                cout << "Assign" << endl;
                return *this;
        }

private:
        int age;
        string name;
};

void f(Person p)
{
        return;
}

Person f1()
{
        Person p;
        return p;
}

int main()
{
        Person p;
        Person p1 = p;    // 1
        Person p2;
        p2 = p;           // 2
        f(p2);            // 3

        p2 = f1();        // 4

        Person p3 = f1(); // 5

        getchar();
        return 0;
}
拷贝和复制运算符都涉及到深浅拷贝,主要是当类成员包含指针时,浅拷贝会使得两个指针指向同一块区域(指针只是简单的值拷贝),此时需要自行编写拷贝构造及复制运算符。
// 深拷贝
    Person(const Person& p) {
      this->age = new int;
      std::memcpy(this->age, p.age, sizeof(int));
    }
    int *age;13.C++的空类有哪些成员函数?

默认(缺省)构造函数,默认(缺省)析构函数,默认(缺省)拷贝构造,默认(缺省)赋值运算符,默认(缺省)取址运算符,默认(缺省)取址运算符const
编译器只有在你使用到对应的函数时候,才会去定义。
12.面向对象的三大特整

封装:明确标识出允许外部使用的所有成员函数和数据;将客观事物抽象成类,每个类可以构建自己的数据和方法;通常类的成员是私有的,一部分方法是私有的,一部分方法是共有的;封装能够使得程序更模块化,更易读写,提升了代码的重用性;
继承+多态:
继承:继承基类的方法,并做出自己的改变或扩展(解决了代码重用问题);声明某个子类兼容于基类,使得外部调用这无需关注其差别;
多态:基于对象所属类的不同,外部对同一个方法的调用,实际的执行逻辑不同;多态依附于继承。
11.设置地址为 0x67a9 的整型变量的值为 0xaa66



1.变量的声明和定义有什么区别

定义:为变量分配地址和存储空间;
声明:不分配存储空间;一个变量可以在多个地方声明,但只能在一个地方定义;
extern用于修饰声明;
int value ; //声明 + 定义

struct Node { // 声明 + 定义
    int left;
    int right;
};
extern int var; // 声明

extern int ble =10; // 定义

typedef int INT; // 声明

struct Node; // 声明2.写出 bool 、int、 float、指针变量与“零值”比较的 if 语句

  bool value_b;
  int value_i;
  float value_f;
  int * p;
  if (0 == value_i) ;
  if (value_f <= 1e-8 && value_f >= -1e-8) ;
  if (nullptr == p) ;
3.sizeof和strlen的区别

sizeof 和 strlen 有以下区别:
1 sizeof 是一个操作符,strlen 是库函数。
2 sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。
3 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof 计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
4 数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。
注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就是 sizeof。
4.C 语言的关键字 static 和 C++ 的关键字 static 有什么区别

C中由于没有类,所以C中的staic用来修饰局部静态变量和外部静态变量,C++中还用来定义类的静态变量和静态成员函数。
静态全局变量静态全局变量就是用来解决重名问题的。静态全局变量定义时在定义前加static关键字, 告诉编译器这个变量只在当前本文件内使用,在别的文件中绝对不会使用。这样就不用担心重名问题。所以静态的全局变量就用在我定义这个全局变量并不是为了给别的文件使用,本来就是给我这个文件自己使用的。
5.C中的 malloc 和C++中的 new 有什么区别
new和delete是操作符(也叫运算符)可以重载,只在C++中使用;malloc和free是函数,可以覆盖,C、C++都可用;
{操作符函数处理的阶段不同,操作符会在编译阶段处理(例如sizeof,会在编译阶段计算出值,以立即数的形式存放到代码段中),而函数则会在代码段中以call的形式去调用;操作符可以重载}
new可以调用对象的构造函数,delete调用析构函数
malloc仅分配内存,free仅回收空间,不会涉及到构造析构;
new,delete返回的是某种数据的类型指针,malloc,free返回的是void类型指针
6.写一个标准宏MIN

正确形式
#define min(a,b) ((a)<=(b)?(a):(b))
错误形式
#define min(a,b) (a<=b?a:b)
int a = 2, b = 4;
std::cout << min(++a,b) << "\n";宏只是字符串的替换
7.一个指针可以是volatile吗?什么是volatile?


8.数组名a和&a的区别

int a[5] = {1, 2, 3, 4, 5};
std::cout << a << " " << &a << "\n";
int *p = (int *)(&a + 1);
std::cout << *(a+1) << " " << *(p - 1) << "\n";

0x7fff3542c390 0x7fff3542c390
2 5首先a是一个地址,a真实的面貌应该是a+0,*(a+0)是什么呢?代表第一个元素的内容 ,也就是a[0]。
数组可以理解成一个基本的数据类型变量,类型是int[5],a这个变量代表5个int大小的这么一个区间,并且是不可分割的;和一般的变量的区别就是操作受限制,不能通过赋值运算符一次性将整个区间赋值给另外一个相同的数组变量,只能一个一个赋值。
a是一个地址,我们知道a其实本来的面目应该是:a+0,只不过这个0我们就省了,*(a+0)是什么呢,这个代表的就是取得第一个元素的内容,也就是a[0],所以我们可以知道a它代表的应该是第一个(下标为0)元素的地址。
&a是什么呢?也就是变量a的地址,而这个a是这5个int数据(看作一个整体);将其复制给p,那p的类型就是int(*)[5],也就是指向包含这个5个int数据的数组的指针。
那么&a+1就是数组的下一个地址,a+1则是第一个元素的地址。但是&a和a打印出来的地址是同样的。
9.简述C,C++程序编译的内存分配情况


  • 静态区域内存分配
编译时候就分配好了,这块内存在整个运行区间都存在,速度快,不易出错;全局变量,static变量;
栈上分配
执行函数时,函数内的局部变量的存储单元都在栈上创建,函数结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令集中,效率高,容量有限。
堆上分配
动态内存分配,运行时候malloc,new申请的内存,程序员负责释放。如不回收,会出现内存泄漏,频繁分配释放不同大小的堆空间,会产生堆内碎片。
五个存储区:堆,栈,程序代码区(存放函数体的二进制代码),全局/静态存储区(全局变量,静态变量)、常量存储区(常量不允许修改)。
10.strcpy,sprintf和memcpy的区别

参考

零声Github整理库:C/C++ 常见1000道面试题(1)
https://zhuanlan.zhihu.com/p/420625314
【C++】设计一个不能被继承的类_小魏同学i的博客-CSDN博客_设计一个不能被继承的类 c++
C++ 一篇搞懂多态的实现原理 - 知乎 (zhihu.com)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表