c++面试宝典

2023-05-16

目录

一 多线程

二 指针

三  字符串

四  面向对象

五 基本用法

六 c++11

七  算法


c++面试必考多线程,内存(智能指针),常见算法,设计模式。

一 多线程

c++11提供了mutex和condition_variable,并没有提供临界区,信号量。(线程同步)

Mutex互斥量,C++ 11中使用 std::mutex类,必须包含 <mutex> 头文件。

(1)Mutex系列类(四种)
std::mutex,最基本的 Mutex 类。构造函数,std::mutex不允许拷贝构造,也不允许 move 拷贝,最初产生的 mutex 对象是处于 unlocked 状态的。lock()还是try_lock()都需要和unlock()配套使用。但性能损耗,推荐使用atomic操作数据类型
std::recursive_mutex,递归 Mutex 类。
std::time_mutex,定时 Mutex 类。

std::recursive_timed_mutex,定时递归 Mutex 类。

(2)Lock系列类(两种)----------这两个类相比使用std::mutex的优势在于不用配对使用,无需担心忘记调用unlock而导致的程序死锁
std::lock_guard,方便线程对互斥量上锁。除了构造函数外没有其他成员函数
std::unique_lock,除了lock_guard的功能外,提供了更多的成员函数,相对来说更灵活一些。这些成员函数包括lock,try_lock,try_lock_for,try_lock_until、unlock等

(3) 条件变量   

std::condition_variable 提供了两种 wait() 函数

无条件等待:

void wait (unique_lock<mutex>& lck);

有条件等待

template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);

std::condition_variable::notify_one()   唤醒其中一个等待线程(不确定)

std::condition_variable::notify_all()   唤醒所有的等待(wait)线程。如果当前没有等待线程,则该函数什么也不做

(4)线程池实现原理

//ThreadPool.h

class ThreadPool{
public:
    //自定义void()的函数类型
    typedef function<void()>Task;
    ThreadPool();
    ~ThreadPool();
public:
    size_t initnum;
    //线程数组
    vector<thread>threads ;
    //任务队列
    queue<Task>task ;
    
    //互斥锁条件变量
    mutex _mutex ;
    condition_variable cond ;
    
    //线程池工作结束时为真
    bool done ;
    
    //队列是否为空
    bool isEmpty ;
    //队列是否为满
    bool isFull;

public:
    void addTask(const Task&f);
    void start(int num);
    void setSize(int num);
    void runTask();
    void finish();
};

//ThreadPool.cpp
#include"ThreadPool.h"
ThreadPool ::ThreadPool():done(false),isEmpty(true),isFull(false){
}

//设置池中初始线程数
void ThreadPool::setSize(int num){
        (*this).initnum = num ;
}

//添加任务
void ThreadPool::addTask(const Task&f)
{
    if(!done){
        //保护共享资源    
        unique_lock<mutex>lk(_mutex);
        
        //要是任务数量到了最大,就等待处理完再添加
        while(isFull){
            cond.wait(lk);
        }

        //给队列中添加任务
        task.push(f);
        
        if(task.size()==initnum)
            isFull = true;
        
        cout<<"Add a task"<<endl;
        isEmpty = false ;
        cond.notify_one();
    }
}

void ThreadPool::finish()
{
        //线程池结束工作
        for(size_t i =0 ;i<threads.size();i++){
               threads[i].join() ;
        }
}

void ThreadPool::runTask(){
    
    //不断遍历队列,判断要是有任务的话,就执行
    while(!done){

        unique_lock<mutex>lk(_mutex);
        
        //队列为空的话,就等待任务
        while(isEmpty){
            cond.wait(lk);
        }
        
        Task ta ;
        //转移控制快,将左值引用转换为右值引用
        ta = move(task.front());  
        task.pop();
        
        if(task.empty()){
            isEmpty = true ;    
        }    
        
        isFull =false ;
        ta();
        cond.notify_one();
    }
}

void ThreadPool::start(int num){
    
    setSize(num);
    
    for(int i=0;i<num;i++){        
        threads.push_back(thread(&ThreadPool::runTask,this));
    }
}
ThreadPool::~ThreadPool(){   
}

//Test.cpp

#include <iostream>
#include"ThreadPool.h"
void func(int i){
    cout<<"task finish"<<"------>"<<i<<endl;
}
int main()
{

    ThreadPool p ;
    p.start(N);
    int i=0;

    while(1){
        i++;
        //调整线程之间cpu调度,可以去掉
       this_thread::sleep_for(chrono::seconds(1));
        auto task = bind(func,i);
        p.addTask(task);
    }

    p.finish();
    return 0;
}
 

二 指针

0 智能指针

shared_ptr,每次创建类的新对象时,初始化指针并将引用计数count=1,拷贝构造函数count++,赋值运算符重载左操作数所指对象的引用计数count--,右操作数所指对象的引用计算count++

实现原理:引入2个类,一个引用计数类Counter,一个指针类SmartPtr

注意事项:1 不能将原始指针直接赋值给shared_ptr,不要将同一个原始指针初始化不同的shared_ptr,不要循环引用,不要delete  get函数获取的原始指针,删除器的使用

weak_ptr,配合shared_ptr使用,观测权,无引用计数

与shared_ptr不同,unique_ptr没有定义类似make_shared的操作,因此只可以使用new来分配内存,并且由于unique_ptr不可拷贝和赋值,初始化unique_ptr必须使用直接初始化的方式。

  1. unique_ptr<int> up1(new int()); //okay,直接初始化

  2. unique_ptr<int> up2 = new int(); //error! 构造函数是explicit

  3. unique_ptr<int> up3(up1); //error! 不允许拷贝

  4. int *p = new int;
    unique_ptr<int> p1(p);//原始指针拷贝,编译不报错,不推荐使用
    unique_ptr<int> p2(p);

  5. 警惕智能指针作为参数。  const  reference是智能指针做参数的底线。

unique_ptr   同一时刻只能有一个unique_ptr指向给定对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。因此不允许多个unique_ptr指向同一个对象,所以不允许拷贝与赋值

unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权

1 new int[12]生成一个大小为12 的int数组,而new int(12)是生成一个初始值为12的int变量。

对于基本类型数组来说,delete a和delete [ ] a效果是一样的,如果a是一个用户自己定义的结构类型数组,只能使用delete [ ] a。

2下面的代码会输出:

int main()
{    
    int a[4]={1,2,3,4};
    int *ptr=(int *)(&a+1);
    printf("%d",*(ptr-1));
}
解析:最终结果会输出4。考察数组和指针, 指针加一的能力由指针指向的类型决定。&a和a都指的是数组首元素的地址,不同的是,a就是a+0,*(a+0)也就是a[0],而&a+1相当于对a[]类型的数组类型的指针加一,此时指针加到数组的末尾。ptr接受之后,ptr指向最后一个元素后面的那个位置,而ptr的类型是int*,因此,ptr-1之后指向数组最后一个元素4。

3 有下面一段程序,则下列不合法的是

int f1(float);
int f2(char);
int f3(float);
int f4(float);
int (*pf)(float)
A:int (*p)(float)=&f1             B:pf=&f4             C:pf=&f2                  D:pf=f3
解析:C错误,原因是函数参数类型不匹配。函数指针声明的方法:  返回值类型   (*指针变量名)([参数列表])
根据定义,则int (*pf)(float)                           int (*p)(float)=&f1     ,pf和p都是函数指针。对于函数地址的获取,可以是函数名,也可以是函数名前加取地址符&。

4 以下程序段执行后结果是:

#include<stdio.h>
void main()
{
    short *p,*q;
    short arr[15]={0};
    p=q=arr;
    p++;
    printf("%d",p-q);
    printf("%d",(char*)p-(char*)q);
    printf("%d",sizeof(arr)/sizeof(*arr));
}
解析:short占两个字节,指针加一的能力由指针指向的类型决定,所以p++与q的偏移量就是2个字节。short arr[15]={0}是初始化所有数组元素为0, 指针相减的结果是指针地址的偏移量除以指针每次移位的大小。
    p-q:偏移量为2个字节,每次移动2个字节,所以p-q=1

            (char*)p-(char*)q:偏移量还是2个字节,但是每次指针移位按照char*移位,也就是每次移动1个字节,所以(char*)p-(char*)q=2;

    对数组名取sizeof()得到的是整个数组的大小,所以sizeof(arr)=15*2=30,;*arr指向数组的第一个元素,所以sizeof(*arr)=2,sizeof(arr)/sizeof(*arr)=15

5 下列程序执行结果是:

#include<iostream>
using namespace std;
class TestClass
{
    char x;
public:
    TestClass()
    {
        cout<<'A';
    }
    TestClass(char a)
    {
        cout<<a;
    }
    ~TestClass()
    {
        cout<<'B';
    }
};
int  main()
{
    TestClass p1,*p2;
    p2=new TestClass ('X');
    delete p2;
    return 0;
}

解析: 类指针的声明不会调用构造函数,但是指向一个类的实例(new)的时候会调用构造函数。类的声明会调用类的构造函数。
    TestClass p1,*p2;     //只会为p1调用默认构造函数

            p2=new TestClass ('X');       //为p2调用构造函数

    delete p2;             //释放p2指向的内存空间,为p2指向的实例调用析构函数

    return 0;              //程序结束,调用析构函数释放p1指向的内存空间

           最后输出AXBB。

6 下面到底哪个是数组指针,哪个是指针数组呢:
A)
int *p1[10];
B)
int (*p2)[10];

“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针

7  struct Test
{
   int Num;
   char *pcName;
   short sDate;
   char cha[2];
   short sBa[4];
}*p;

假设p 的值为0x100000。如下表表达式的值分别为多少?
   p + 0x1 = 0x___ ?
   (unsigned long)p + 0x1 = 0x___?
   (unsigned int*)p + 0x1 = 0x___?

指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数。这个整数的单位不是byte 而是元素的个数。所以:p + 0x1 的值为0x100000+sizof(Test)*0x1。至于此结构体的大小为20byte,前面的章节已经详细讲解过。所以p +0x1 的值为:0x100014。

(unsigned long)p + 0x1 的值呢?这里涉及到强制转换,将指针变量p 保存的值强制转换成无符号的长整型数。任何数值一旦被强制转换,其类型就改变了。所以这个表达式其实就是一个无符号的长整型数加上另一个整数。所以其值为:0x100001。

(unsigned int*)p + 0x1 的值呢?这里的p 被强制转换成一个指向无符号整型的指针。所以其值为:0x100000+sizof(unsigned int)*0x1,等于0x100004。

8  开发c代码时,经常见到如下类型的结构体定义:

typedef struct list_t
{
    struct list_t *next;
    struct list_t *prev;
    char data[0];
}list_t;
则,最后一行char data [0]的作用是:
 A :方便管理内存缓冲区     B:减少内存碎片化    C:标示结构体结束     D:没有作用

解析:最后的char data [ 0 ]是一个柔性数组,只能放在结构体的末尾,是声明一个长度为0的数组,就可以使得这个结构体是变长的。对于编译器来说,此时长度为0并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量,是紧跟在结构体后面的地址,对于这个数组的大小,可以进行动态分配内存,以使得整个结构体的大小是可变的,而且编译器会支持对于数组data的越界访问,这种声明方法可以很巧妙地实现数组扩展。它的作用其实就是方便管理内存缓冲区,减少内存碎片化,而不是标示结构体的结束。

9  关于以下代码中的变量在内存中的存储位置描述不正确的是()
int a=0;
class someClass
{
    int b;
    static int c;
};
 
int main()
{
    int d=0;
    someClass *p=new someClass();
    return 0;
}
A:b存在堆区      B:c存在堆区          C:d存在栈区          D:a存在全局变量区
解析:栈区:由编译器自动分配释放,存储为函数运行而分配的局部变量、函数参数、返回数据、返回地址等

            堆区:有程序员分配释放,new malloc之类的,如果程序员不释放,程序运行结束时由操作系统回收

            全局区:存放全局变量、静态数据、常量,程序结束后由系统释放

           文字常量区:存放常量字符串,程序结束后由系统释放

   程序代码区:存放函数体的二进制代码
           a:全局变量,存在全局变量区      b:成员变量,存放在堆区      c:静态成员变量,存放在全局区     d:局部变量,存放在栈区    p:方法变量,p本身存在栈区,但是指               向堆区内存空间

10 new/delete 和malloc/free区别

1.malloc分配的空间在堆上而new在自由存储区,自由存储区是专门为new操作定义的一个区。//堆是用来动态分配空间的。栈用来存储函数内部声明的变量。

2.malloc是c库里面的一个函数,分配成功的时候返回void指针,需要我们强制转换,而new是关键字,可以为任意数据类型分配内存空间,返回对象类型的指针

3.new请求失败抛出异常,而malloc请求失败返回null

4.new时不需要指定内存大小,但是malloc需要指定类型和大小(int*)malloc(15)这样

5.new/delete需要调用构造和析构函数,但是malloc/free不用调用,实际上new的底层是用malloc写的

6.new和malloc可以被重载但是malloc不行

7.new是关键字,语言编译器支持,而malloc是库函数,需要头文件支持

11 堆和自由存储区的区别

在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区

从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。但程序员也可以通过重载操作符,改用其他内存来实现自由存储,例如全局变量做的对象池,这时自由存储区就区别于堆了。

堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。

12 指针和引用的区别

1.指针可以为空但是引用不行。

2.引用必须要一开始的时候进行初始化,但是指针不用。

3.引用是实际上是一个常量的指针。一旦初始化了一个对象,就不能随意更改。但是指针可以。

4.指针的sizeof由编译器决定,引用的sizeof得不到引用的大小空间,而是引用类型所占的空间。

13 父类指针指向子类数组

class B{

virtual ~B(){}

};

B* pb = new D[3];        //B为父类,D为子类
delete []pb;

解析:delete就是释放堆上对象,它删除一个对象的时候,从指针pb开始,到sizeof(B)结束,记住,这时候是sizeof(B),调用B的析构函数,B的析构函数是虚函数,根据多态性,就先调用D的析构函数,再调用B的析构函数。

删除1个对象或者数组的第1个对象都没有问题,但删除第2个对象就麻烦了,delete第2个对象,就是从2*sizeof(B)开始删除,但是sizeof(B) != sizeof(D),于是截断D对象,结果不能正确调用析构函数,发生错误。
 

三  字符串

1 char a[] ={"abc"} ;  或  char[] = "abc";   //\0由计算机自动加上

2从键盘读入一个字符串
   cin >> string1 ;
   当从输入流中读入一个字符串时, 将读入以空白字符分隔的字符串。
   如当用户键入 Harry Borter时, 只有Harry读入到name。
   为了完整输入用户信息, 可以使用getline函数。
   getline(cin,name);    //   读入回车之前的所有字符,并将所读入的字符串存储到变量name中。
 

3 strlen求字符串长度是不包括\0的,sizeof包括\0

4 除了char型的数组cout<<数组名 输出内容
其他的类型数组cout<<数组名  输出地址

char chartest[256] = "asdhfjka";
int inttest[256] = { 1, 2, 3, 4, 5, 6 };
cout << chartest << endl;        //字符串:asdhfjka
cout << inttest;                       //地址:00B3F1EC

5 char *Example[] = { "tgqaertg", "sadgf", "fg","ggg" };
这里,Example是指针数组。

6 int main()
{
   char a[5]={'A','B','C','D'};
   char (*p3)[5] = &a;
   char (*p4)[5] = a;
   return 0;
}
p3 和p4 都是数组指针,指向的是整个数组。&a 是整个数组的首地址,a是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。p3 这个定义的“=”号两边的数据类型完全一致,而p4 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。在Visual C++6.0 上给出如下警告:
   warning C4047: 'initializing' : 'char (*)[5]' differs in levels of indirection from 'char *'。
还好,这里虽然给出了警告,但由于&a 和a 的值一样,而变量作为右值时编译器只是取变量的值,所以运行并没有什么问题。不过我仍然警告你别这么用。

7 关于下列程序,说法正确的是

void   fun()
{
    char b[2]={0};
    strcpy(b,"aaaa");
}
A:Debug版本崩溃,Release版本正常                              B:Debug版本正常,Release版本崩溃
C:  Debug版本崩溃,Release版本崩溃                              D:  Debug版本正常,Release版本正常 解析:在Debug模式中,会有assert断言保护,所以会崩溃。而在Release模式中,会删掉assert断言保护,所以会正常运行。但是我在VS2010下运行两个版本都正常运行了......这就尴尬了....但是这两种模式的区别还是要知道的。
 

四  面向对象

1 如下代码,result变量的输出结果是多少?

#include<iostream>
using namespace std;
int i=1;
class MyCls
{
    public:
    MyCls():m_nFor(m_nThd),m_nSec(i++),m_nFir(i++),m_nThd(i++)
    {
        m_nThd=i;
    }
    void echo()
    {
        cout<<"result"<<m_nFir+m_nSec+m_nThd+m_nFor<<endl;
    }
    private:
    int m_nFir;
    int m_nSec;
    int m_nThd;
    int &m_nFor;
};
 
int main()
{
    MyCls oCls;
    oCls.echo();
    return 0;
}
 解析:首先要明白, 变量初始化的顺序是其声明的顺序,跟初始化列表中的顺序无关,所以变量的初始化顺序m_nFir(i++),m_nSec(i++),m_nThd(i++),m_nFor(m_nThd),i初始化为1,所以经过初始化列表后的m_nFir=1,m_nSec=2,m_nThd=3,m_nFor是m_nThd的一个引用,并且此时i的值为4,执行构造函数中的赋值语句后,m_nThd=4,此时m_nFor是m_nThd的一个引用,也是4。result=1+2+4+4=11。

2  已知下列的class层次,其中的每一个类都定义有一个default constructor和一个virtual destructor,则下面执行dynamic_cast会失败的是()

class X{...};
    class A{...};
    class B:public A{...};
    class C:public B{...};
    class D:public X,public C{...};
A:  A *pa=new D;  X *px=dynamic_cast<X*>(pa)                        B:  D *pd=new D;  A *pa=dynamic_cast<A*>(pd)
C:  B *pb=new B;  D *pd=dynamic_cast<D*>(pb)                     D:  A *pa=new C;  C *pc=dynamic_cast<C*>(pa)
解析:dynamic_cast<>用于C++类继承多态间的转换,分为子类向基类的向上转换和基类向子类的向下转换。其中,子类向基类的向上转换不需要借助任何其他特殊的方法,只需要将子类的指针或者引用赋给基类的指针或者引用即可。而向下转换时要特别注意,dynamic_cast<>操作符安全的将基类类型的引用或者指针转换为派生类的引用或者指针,dynamic_cast再将基类cast到子类的时候,基类必须有虚函数,因为dynamic_cast运行时需要检查RTTI信息,只有带虚函数的类运行时才会检查RTTI。

           例:假如继承关系如下:C继承B,B继承A

  则:A *a=new B;   //new了一个B,向上转换为A,但是其本质还是B

  B *b=dynamic_cast<B *> a;     //把基类A向下转换为子类B,因为a的本质还是B,所以把B转换为B正确

  C *c=dynamic_cast<C*>a;     //把基类A向下转换为子类C,因为a的本质是B,而C是B的子类,把本质是B的东西转换为子类C,所以是错误的。  由上可知,本题答案为C。

3  下面程序段包含了四个函数,其中具有隐含this指针的是

int f1();
class T
{
public :
    static int f2();
private:
    friend int f3();
protected:
    int f4();
};
解析: 只有非静态类成员才有this指针。友元函数不是类成员函数,所以没有this指针。

4  类A是类B的友元,类C是类A的公有派生类,忽略特殊情况,下列说法正确的是()

A: 类B是类A的友元         B:类C不是类B的友元            C:类C是类B的友元           D:类B不是类A的友元

解析:友元关系是单向的,不对称,不可继承。所以BD正确
 

5  在32位编译器下sizeof(p)为()

class P
{
private:
    int ival;
public:
    P();
    ~P();
    int GetVal()
    {
        return ival;
    }
    virtual int SetVal(int val)
    {
        ival=val;
    }
};
解析: 1、类的大小为类的非静态成员数据的类型大小之和,也就是说静态类成员数据不做考虑
            2、普通成员函数与类的sizeof()无关

            3、虚函数由于要维护虚函数表所以要占据一个指针的大小,也就是4字节

            4、类的总大小也要遵守字节对齐原则

    本题,4+4=8字节

6  头文件已经正常包含,一下代码在vs上编译和运行的结果是:

class A 
{
    public:
    void test()
    {
        printf("test A");
    }
};
int main()
{
    A *pA =NULL;
    pA->test();
}
解析: 对于非虚成员函数,C++是静态绑定的,在编译时就已经确定了,即使pA为NULL,但是已经声明了类型就知道pA有个test函数,且test函数里没有用到成员变量,单单有个打印语句是可以运行成功的。

7 三种继承方式:
            使用private继承,父类的protected和public属性在子类中变为private;
           使用protected继承,父类的protected和public属性在子类中变为protected;
           使用public继承,父类中的protected和public属性不发生改变; 

8 当一个类A 中没有声命任何成员变量与成员函数,这时sizeof(A)=1

9 空对象是否报错

class A

{

public:

int m_iA1;

void print()

{

cout << "A" << endl;

}

};

int main()

{

A *pObjectA=NULL;

pObjectA->print();

return 0; 

}

*a没有初始化,结果成功输出“A”。

分析:成员函数在代码段,成员变量在数据段,地址为在类对象地址基础上累加。

此处的print成员函数没有用到成员变量,与类外独立函数无异。调用的时候不涉及数据段,不会崩溃。

如果成员函数print中使用到了成员变量,情况会是怎样呢?

class A

{

public:

int m_iA1;

char m_chA2;

void print()

{

m_iA1 = 1;

cout << "A" << endl;

}

};

int main()

{

A *pObjectA=NULL;

pObjectA->print();

return 0; 

}

编译执行后,崩在了print函数中的红色标记语句。

分析:此处的print成员函数用到成员变量m_iA1,我们可以看到,&pObjectA=0x00000000,&m_iA1=0x00000000,&m_iA2=0x00000004

​访问了非法地址,崩溃了

尝试给pObjectA分配空间后,&pObjectA=0x003b6060,&m_iA1=0x003b6060,&m_iA2=0x003b6064,

​这是合法的数据段地址,访问成功。

试试static静态变量

class A

{

public:

static int m_iSA4;

static void print()

{

m_iSA4 = 4;

cout << "A" << endl;

}

};

int A::m_iSA4 = 0;

int main()

{

A *pObjectA=NULL;

pObjectA->print();

return 0; 

}

运行成功!静态变量独立于类外部分配好了合法地址,访问成功。

试试继承

class B

{

int m_iB1;

public:

virtual void print()

{

cout << "B" << endl;

}

};

class A:public B

{

    int m_iA1;

    char m_chA2;

public:

    void print()

    {

        cout << "A" << endl;

    }

};

int main()

{

    A *pObjectA=NULL;

    pObjectA->print();

    return 0; 

}

运行之后崩在在main函数的红色标记语句。

分析:虽然A类的print函数没有用到显式的成员变量,但是类A继承于类B,print()是继承于B的虚函数,调用A的print函数时,用到了虚表,虚表地址在类对象数据段最开头,此时访问了非法地址。

可以看到成员变量地址:

​给pObjectA分配空间之后再看

​可以看到,在继承类对象的地址空间中的顺序为:虚表指针、基类成员变量、继承类成员变量
 

10 C和C++区别

a   C++是面向对象的语言,而C是面向过程的。面向过程就是分析解决问题的步骤,然后把这些步骤一步一步进行实现,注重对过程的分析。面向对象就是把数据和方法进行封装起来,把构成问题的事分为各个对象,建立对象的目的不仅是为了解决问题还为了描述对象发生的行为。

b   面向过程的方法用函数进行数据操作,把函数和数据分开。面向对象将函数和数据封装成一个整体,一个类。更加容易维护和实现代码复用。封装,继承,多态

11 多态

多态就是一个接口,多种形态。

编译时的多态可以通过函数重载实现,是静态多态。而运行时候的多态通过虚函数(函数覆盖)实现,是动态多态,具体使用引用对象类型调用函数,在运行时时实现动态绑定。底层实现通过虚函数机制。

多态实现的3个条件:

  • 有继承关系
  • 有虚函数覆写(override)
  • 基类的指针或引用指向继承类

重载,覆盖和隐藏的区别

a  重载在同一个类里面,函数名字相同,但是传递的参数不同。

b  子类复写父类里面的函数,如果父类里面是虚函数,子类默认是虚函数。而且要求函数名,传递的参数都相同,返回的方法也一致。如果不是虚函数,那就是隐藏。

12 虚函数机制

同一个类多个对象,具有相同的虚函数表。(同一类对象的虚函数都是一样的,存放在代码区,不必每个对象都重复存放一份)

在这里插入图片描述

 一般多继承中,继承类的类对象模型中会在对象地址起始位置连续放置与基类个数想当的虚表指针,分别指向不同基类的虚函数表,对于继承类中的虚函数放置在第一个基类虚函数表里,继承类中有覆写基类虚函数的在虚函数表中替换基类虚函数。

父类和子类(有virtual)分别有自己的虚函数表,在编译时就会创建虚函数表。同一个类的不同对象公共一个虚函数表,因为函数存放在代码区。

13 拷贝构造函数使用条件:

1)建立新对象,并用同类对象初始化时;

2)函数的参数为类的对象时;

3)函数的返回值为类的参数时。

14 深拷贝和浅拷贝

浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。

若是类中有指针成员,拷贝构造函数必须另开辟空间,防止多个对象共用同一份存储空间导致二次释放而崩溃

14 构造函数中调用虚函数

c++ primer 第四版中497页15.4.5构造函数和析构中的虚函数讲到,如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。

#include <tchar.h>

#include <iostream>

using namespace std;

class A

{

public:

    A(){

        cout << "A构造函数";

        Test();

    }

    ~A(){

        cout << "A析构函数";

        cout << "A::Test()" << endl;

    }

    virtual void Test(){

        cout << "A::Test()" << endl;

    }

};

class B :public A

{

public:

    B(){

          cout << "B构造函数";

        Test();

    }

    ~B(){

        cout << "B析构函数";

        Test();

    }

    virtual void Test(){

        cout << "B::Test()" << endl;

    }

};

int _tmain(int argc, _TCHAR* argv[]){

    A* pA = new B();

    cout << "动态调用:";

    pA->Test();

    delete pA;

    return 0;

}

1

2

3

4

5

6

//示例2运行结果

A构造函数A::Test()

B构造函数B::Test()

动态调用:B::Test()

A析构函数A::Test()

请按任意键继续. . .

15 构造函数初始化列表

三种情况必须使用初始化列表:

数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数

类中有引用,const成员

子类初始化父类私有成员

类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,赋值和初始化是不同的,这样就体现出了效率差异。如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。

class   Test3{

private:

    int x,y;

public:

    void getData()    {

        cout<<"x="<<x<<",y="<<y<<endl;  //默认构造函数不对成员进行处理,根据编译器的不同,随机值或0

    }

};

int main(){

    Test3 test3;  //默认构造函数,栈方式调用默认构造函数

    test3.getData();

    return 0;

}

五 基本用法

1 下列程序段执行后,输出的结果为:

void  main()
{
    int a=1,b=0,c=-1,d=0;
    d=++a||++b&&++c;
    cout<<d<<endl;
}
解析: 短路原则:||操作符左操作数为真,即可停止判断,右操作数不再执行。因此本题最后结果a=2,b=0,c=-1,d=1。

2  以下代码输出是什么?

int a=1,b=32;
printf("%d,%d",a<<b,1<<32);
解析:这里考虑的是左移里一个比较特殊的情况,就是 当左移的位数超过该数值类型的最大位数时,编译器会用左移的位数去模类型的最大位数,然后按余数进行移位。
    所以a<<b  <==>  b为32超过最大位数32,所以有效移位数为b%32=0,也就是左移0位还是其本身,1

    而对于1<<32,会直接进行移位,有效移位数就是32,移位后为0。

 但是这个问题真正具体的话,在不同的编译器里会有不同的结果。
 

3  编译和执行下列c语言代码,输出结果是:

int main()
{
    char c='0';
    printf("%d  %d",sizeof(c),sizeof('0'));
    return 0;
}
解析:C语言:char a='0'    sizeof(a)=1     sizeof('0')=4       原因: C语言把 '0' 称为整形字符常量,被看成int类型,所以在32位机器上占4字节
    C++:char a='0'        sizeof(a)=1     sizeof('0')=1      原因:C++把‘0‘称为字符字面常量,被看成是char类型,所以占1字节

4  C++中,32位单精度浮点数能表示的十进制有效数字是多少位?

解析:单精度浮点数的有效位数是7位,双精度浮点型的有效位数是16位。

5  int a=5,则++(a++)的值是()

解析:++是一目运算符,只能用于变量,不能用于表达式,而(a++)是表达式,所以会编译错误。

6  下列程序的输出结果是

void f()
{
    static int i=15;
    i++;
    cout<<"i="<<i<<endl;
}
 
int main()
{
    for(int k=0;k<2;k++)
    {
        f();
    }
    cin.get();
    return 0;
}
解析: static修饰的变量只初始化一次,当下次执行到初始化语句时,直接跳过。所以输出16,17
 

7 C和C++中struct有什么区别?
C无Protection行为   不能定义函数

8 C++中的struct和class有什么区别?
【答案】从语法上讲,class和struct做类型定义时只有两点区别:
(1)默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;
(2)成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。 除了这两点,class和struct基本就是一个东西。语法上没有任何其它区别。

10构造函数声明为explicit,就可以避免隐式类型转换

11 STL线程安全

读是安全的,写操作要注意加锁

1.每次调用容器的成员函数的期间需要锁定。
2.每个容器返回迭代器的生存期需要锁定。
3.每个容器在调用算法的执行期需要锁定。

六 c++11

1 std::function,functor仿函数,函数指针的区别

std::function可调用普通函数、Lambda表达式、函数指针、类静态方法(非静态方法需用bind绑定),提供统一调用方式。

从功能上来说,function和函数指针没有差别。引入function主要有5点:

       1)泛型思想,与STL架构思想保持一致。

       2)function是一个对象,封装了数据和函数。而函数指针只能全局或局部变量。

       3)function可以支持多态

       4)functor是一个类,运算符重载(),一般轻量级代码,也可实现。

       5)函数指针非类型安全

2 std::bind

 std::bind函数定义在头文件functional中,是一个函数模板,它就像一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

std::bind绑定到虚函数时会表现出多态行为。

std::bind的函数参数默认使用的是拷贝, 如果需要使用引用,则需要配合std::ref。参数可以支持Placeholders::_1占位符。

3 lambda表达式 值传递,引用传递

4 左值/右值与左值引用/右值引用

int a=9;   

a左值,存储在内存中,可用&寻址,生命周期较长

9右值,存储在寄存器中,不一定能寻址,通常是个临时值,以常量形式存在,生命周期短。左值也能当成右值使用;例如int b=a;

左值引用声明时必须指向一个已经存在的内存地址;

例如 int &a = 3;    //错误,3存储在寄存器中

const int & a=3;   //正确,c++98/03标准允许常量左值引用操作右值和左值

右值引用

int && a=10;   //编译成功,非常量右值引用支持引用非常量右值

const int && a=10;   //成功,常量右值引用支持引用常量右值

int num = 100;

const int num2 = 100;

int &&a = num;   //失败,非常量右值引用不支持引用非常量左值

int &&a=num2;   //失败,非常量右值引用不支持引用常量左值。总之不支持引用左值

5 拷贝构造函数和移动构造函数

拷贝构造函数实现原理:为新对象复制一份和其他对象一样的数据,当类中有指针类型的成员变量时,拷贝构造函数需要以深拷贝方式复制该指针成员。

例如借书:拷贝构造函数步骤是,b拷贝一本新书给a,将原来的书再销毁

移动构造函数步骤是,b直接将书交给a(转移);

而move可以将左值转变为右值引用,减少临时对象的构造和析构,提高效率。

七  算法

链表逆序,

归并排序,

快速排序,

二叉树递归与非递归遍历,

kmp字串匹配

找出第k大的数:

方法1 :快速排序之后,取第k大的数,o(n*log n+k)

方法2 :利用快速排序的思想,从数组S中随机找出一个元素X,把数组分为两部分Sa和Sb。Sa中的元素大于等于X,Sb中元素小于X。这时有两种情况:

(a)  Sa中元素的个数小于k,则Sb中的第k-|Sa|个元素即为第k大数;

(b)  Sa中元素的个数大于等于k,则返回Sa中的第k大数。时间复杂度近似为O(n)
 

vector动态数组维护的是一个连续线性空间,支持随机访问。vector扩容时,新开辟capacity*2空间大小,并将原数据拷贝到新存储区,释放原空间。注意此时原数据地址已改变,指向原vector的迭代器都会失效

reserve只是当要开辟空间大于其原空间会开辟至需要的空间,而小于就不会更改其空间。

resize:若要开辟的空间的size大于其原来的size,那么resize之后要存放的数据就放在原size后的位置上。
若要开辟的空间小于原size则就保留前n个数据(之后的会自动的删除)

list,map,set,deque,stack底层实现原理

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++面试宝典 的相关文章

  • 无名飞控姿态解算和控制(三)

    继续码代码 上一篇主要写了自稳模式下的代码流程 xff0c 这次主要是飞控的定高和定点控制流程 首先是定高 控制模式在Main Leading Control里选择 定高模式代码 xff1a else if Controler Mode 6
  • 无名飞控框架梳理

    打开飞控的main c文件 首先是HardWave Init 飞控板内部资源 相关外设初始化 打开 include 34 Headfile h 34 Sensor Okay Flag Sensor Init Flag void HardWa
  • 无名飞控的时钟和延时

    首先是飞控里面调用了 SystemInit 时钟初始化这个里面 void SystemInit void Reset the RCC clock configuration to the default reset state for de

随机推荐

  • 谈谈bit位序的问题

    Linux内核里面有下面代码 struct iphdr if defined LITTLE ENDIAN BITFIELD u8 ihl 4 version 4 elif defined BIG ENDIAN BITFIELD u8 ver
  • 无名飞控的姿态解算和控制(四)

    上面几篇帖子已经写完控制流程还剩一点没说 if PPM Isr Cnt 61 61 100 PPM接收正常才进行传感器标定检测 Accel Calibration Check 加速度标定检测 Mag Calibration Check 磁力
  • 无名飞控的自抗扰控制

    无名飞控的自抗扰控制 自控制的算法可以见韩京清先生的书点击打开链接 为了自抗扰控制买了无名飞控 xff0c 现在看看它的自抗扰代码 首先初始化 xff1a 从代码上大致可以看出只对俯仰 横滚方向姿态内环角速度控制器采用ADRC自抗扰控制器
  • ARM寄存器与汇编指令详解

    介绍ARM寄存器之前 xff0c 先来介绍一下ARM处理的模式 xff1a 用户模式 User ARM处理器正常的程序执行状态 快速中断模式 FIQ 用于高速数据传输或通道处理 外部中断模式 IRQ 用于通用的中断处理 管理模式 Svc 操
  • STM32定时器---正交编码器模式详解

    编码器分类 xff1a 按工作原理 xff1a 光电式 磁电式和触点电刷式 按码盘的刻孔方式 xff1a 增量式和绝对式两类 由于博主接触面还不是很广 xff0c 一共就用过两个种类的编码器 xff0c 都是属于光电的 差分编码器 一般由8
  • VM虚拟机下给Ubuntu 目录分区增加容量的方法

    最近在编译androdi5 1代码的时候突然发现虚拟机容量不够了 xff0c 很是蛋疼 xff0c 只好摸索如何想办法给相应目录增加容量 xff0c 以下方法亲测可行 xff01 1 第一步当然是增加硬盘容量了 xff0c 这个需要用到VM
  • 1.uCOS-II简介及移植uCOS-II到STM32F103平台详细步骤

    I 说明 作者 xff1a WXP 翱翔云端的鸟 联系方式 328452854 64 qq com 13100610853 联系请注明 CSDN 申明 个人原创 xff0c 转载请先经过本人同意 xff01 要说的话 个人水平有限 写之前也
  • 1.nRF52832裸机教程--开发环境搭建

    I 说明 作者 xff1a WXP 翱翔云端的鸟 联系方式 328452854 64 qq com 13100610853 联系请注明CSDN 申明 个人原创 xff0c 转载请先经过本人同意 xff01 要说的话 个人水平有限 写之前也看
  • 3.nrf52832裸机教程--系统时钟

    I 说明 作者 xff1a WXP 翱翔云端的鸟 联系方式 328452854 64 qq com 13100610853 联系请注明CSDN 申明 个人原创 xff0c 转载请先经过本人同意 xff01 要说的话 个人水平有限 写之前也看
  • Docker中使用ROS-GUI

    Docker中使用ROS GUI比较麻烦 xff0c 好在有国外的大神解决了这个难题 下面 xff0c 我就教大家如何在Docker中使用ROS GUI 1拉取大神编写的镜像 docker pull ct2034 vnc ros kinet
  • Linux环境下搭建git服务器和TortoiseGit客户端 ssh key测试

    Linux环境下搭建git服务器和TortoiseGit客户端 ssh key测试 1 git的安装2 用户和 ssh目录创建3 本地创建公钥私钥4 在服务器端导入本地公钥5 开启服务器中的RSA认证6 创建仓库7 本地克隆 1 git的安
  • RealSense技术在SR300摄像头上的应用

    RealSense技术在SR300摄像头上的应用 一 实感摄像头 1 RealSense技术 在计算机的发展过程中我们始终没有抛弃键盘和鼠标 xff0c 前几年win8和触屏的配合形成 了一种新的电脑使用模式 xff0c 但是依然没有打破传
  • 自动控制理论(2)——控制系统的数学模型(微分方程、传递函数)

    系列文章目录 自动控制理论 xff08 1 xff09 自动控制理论概述 自动控制理论 xff08 3 xff09 控制系统的数学模型 xff08 系统框图和信号流图 xff09 文章目录 系列文章目录一 线性系统的微分方程1 微分方程的建
  • Android 8.1共享系统代理中的热点(LineageOS15.1)

    https github com Mygod VPNHotspot 下载安装这个软件 xff0c 需要ROOT 开发者选项 xff1a 关闭WLAN硬件加速 该软件设置 xff1a 关闭IPV6 打开 修复DHCP 开启手机自带的热点 该软
  • 浏览器页面滚动条美化(样式)

    浏览器页面滚动条美化 xff08 样式 xff09 最近测试反应我们的产品在浏览器中当页面宽高出现溢出的情况下页面滚动条太丑了 xff01 让我们美化一下 xff01 然后花了一点时间专研了一下关于滚动条样式的相关知识 xff0c 今天就在
  • Android串口工具

    参考Android系统实现方式 xff0c 串口操作采用native实现 xff0c 比java层用文件流的方式更高效 xff0c 不需要延时和睡眠处理 xff0c 大量自测不会出现读取不到数据等问题 特点 xff1a 1 提供基本的串口操
  • 【VINS-Fusion入门之一】让系统跑起来

    文章目录 简介配置执行单目 43 IMU双目 43 IMU双目相机双目 43 GPS 落地备注 xff1a 简介 VINS xff0c 英文缩写Visual Inertial Systems 是一个实时视觉SLAM框架 xff0c 2017
  • 【VINS-Fusion入门之二】基于优化的多传感器融合

    文章目录 简介特征参考论文 解读系统框图相机模型 xff1a 配置文件全局优化闭环优化状态估计 简介 VINS Fusion is an optimization based multi sensor state estimator whi
  • RealSense T265使用教程

    RealSense ROS 安装 https github com IntelRealSense realsense ros 安装教程 https www intelrealsense com get started tracking ca
  • c++面试宝典

    目录 一 多线程 二 指针 三 字符串 四 面向对象 五 基本用法 六 c 43 43 11 七 算法 c 43 43 面试必考多线程 xff0c 内存 xff08 智能指针 xff09 xff0c 常见算法 xff0c 设计模式 一 多线