关于C++有深度的面试题

关于C++有深度的面试题

  • 在已经分配好的内存空间里构建一个新的对象,如何实现?
  • C++实现多态的效率问题
  • 怎么通过别名调用一个类成员函数名?
  • 能自己写生产者消费者模型吗?
  • 能写线程安全的单例模式吗?
  • 能写自己实现shared_ptr吗?
  • 能写迪杰斯特拉算法吗?
  • malloc是怎么分配内存的?
  • C++ char*不能修改 C++中char[]能修改char*却不行?
  • thread_local怎么理解?
  • memcopy和strncopy的区别?strcopy和strncopy的区别?
  • 静态多态和动态多态
  • 智能指针有哪些?
  • 为什么抽象类无法实例化?
  • extern C的作用
  • volatile有什么作用?
  • 静态全局变量、静态局部变量、全局变量、局部变量的区别?
  • 如何让new申请失败不抛异常?
  • 如何确定主机的字节序?
  • 怎么只能在栈/堆上实例化对象?
  • 使用vector如何避免频繁的内存重新分配?
  • dynamic_cast和static_cast的应用场景
  • 内联函数是否有函数指针?
  • 宏和内联的区别
  • 怎么解决扩容时对所有的元素rehash?
  • 线程如何实现并发顺序运行?
  • 函数参数入栈为什么是从右往左的顺序?
  • va_list原理?
  • 编程实现给百度发送get请求?

在已经分配好的内存空间里构建一个新的对象,如何实现?

以下总结来自:https://www.cnblogs.com/alephsoul-alephsoul/archive/2012/10/17/2728019.html

首先说一下侯捷老师说的,C++new一个对象是分成三个步骤:

  1. 分配内存,返回内存首地址void指针
  2. 指针转型,将void类型转化为想要构建的对象类型
  3. 通过指针调用对象的构造函数。

C++的new有三重含义:

  1. new operator:也就是我们经常在C++代码中调用的new来实例化对象的操作符。
  2. operator new:在new operator中会调用,这时的new是一个运算符,是能够重载的。我们重载它,能够做一些内存分配前后的工作。
  3. placement new:通过指针调用想要构建的对象的构造函数。这也就理解为什么叫placement,因为他要初始化内存中的东西。

那么,在有上述知识的情况下,回答这个问题就是:跳过new operator和operator new,将已有的内存空间转型,通过首地址调用想要构造的对象的构造函数,完成初始化。

#include
char* buf = new char[sizeof(obj)];
obj* tmpObj = new(buf)obj();
		|
		|  编译器转换
		V
obj* pc;
void* mem = operator new(sizeof(obj), buf);
pc = static_cast<obj*>(mem);
pc->obj::obj();

C++实现多态的效率问题

虚函数是实现多态的关键。对于普通函数,在编译器我们就能够确定该函数的地址,编译器后面的指令就是放该函数中的后续汇编代码。但是多态是运行期动态绑定的,要调用的时候才去找地址(首先找虚表,然后在虚表中找函数地址),然后跳转。两次跳转,会让cpu中预取的指令作废,从而导致cpu cache命中率降低,从而导致运行效率下降。关键的是,多态是这个机制,还没有优化的机会。

怎么通过别名调用一个类成员函数名?

我的理解是使用函数指针,对于类静态成员函数:

class A{
public:
    static void add(int a, int b){
        cout << a+b << endl;
    }
};

int main(){
	void (*fPtr)(int, int);
    fPtr = A::add;
    auto* pp = *fPtr;
    pp(2, 3); 
}

即可以使用普通的函数指针。

对于普通成员函数:

class A{
public:
    void add(int a, int b){
        cout << a+b << endl;
    }
};

int main(){
	void (A::*fPtr)(int, int);
    fPtr = &A::add;
    A a;
    (a.*fPtr)(2,8);
}

需要说明指针的作用域,而且需要一个对象来调用。

能自己写生产者消费者模型吗?

#include
#include
#include
#include
#include

using namespace std;

pthread_cond_t cond_t;
pthread_cond_t cond_t_full;
pthread_mutex_t mutex_t;

int maxListNode = 20;

struct Node{
    int num;
    Node* next;
};

Node* head = NULL;

int getListNodeNum(){
    int num = 0;
    Node* cur = head;
    while(cur){
        num++;
        cur = cur->next;
    }
    return num;
}


void* customer(void* args){
    // 消费者
    while(true){
        pthread_mutex_lock(&mutex_t);
        Node* tmp = head;
        if(head == NULL){
            // 说明当前没有产品供消费
            pthread_cond_wait(&cond_t, &mutex_t);
            pthread_mutex_unlock(&mutex_t);
        }else{
            // 当前有产品,将这个产品从链表移除
            head = head->next;
            // tmp->next = NULL;
            cout << "消费者消耗一件产品,已从链表删除\t 产品id为:" << tmp->num << ",当前仓库容量为:" << getListNodeNum() << endl;
            delete tmp;
            pthread_cond_signal(&cond_t_full);
            pthread_mutex_unlock(&mutex_t);
            usleep(100);
        }
        
    }
    return NULL;
}

void* producter(void* args){
    // 生产者
    while (true){
        pthread_mutex_lock(&mutex_t);
        if(getListNodeNum() < maxListNode){
            Node * newPro = (Node *) malloc(sizeof(Node));
            newPro->num = rand() % 10000;
            newPro->next = head;
            head = newPro;
            cout << "生产者生产一件产品,已插入链表\t 产品id为:" << newPro->num << ",当前仓库容量为:" << getListNodeNum() << endl;
            pthread_cond_signal(&cond_t);
            pthread_mutex_unlock(&mutex_t);
            usleep(100);
        }else{
            pthread_cond_wait(&cond_t_full, &mutex_t);
            pthread_mutex_unlock(&mutex_t);
            usleep(100);
        }
        
    }
    return NULL;
}

int main(){
    pthread_mutex_init(&mutex_t, NULL);
    pthread_cond_init(&cond_t, NULL);
    pthread_cond_init(&cond_t_full, NULL);
    // 5个消费者线程、5个生产者线程
    pthread_t prod[5];
    pthread_t cust[5];
    for(int i = 0; i < 5; i++){
        pthread_create(&prod[i], NULL, producter, NULL);
        pthread_create(&cust[i], NULL, customer, NULL);
    }
    for(int i = 0; i < 5; i++){
        pthread_detach(prod[i]);
        pthread_detach(cust[i]);
    }
    pthread_mutex_destroy(&mutex_t);
    pthread_cond_destroy(&cond_t);
    pthread_cond_destroy(&cond_t_full);
    pthread_exit(NULL);
    return 0;
}

能写线程安全的单例模式吗?

#include
#include
using namespace std;

pthread_mutex_t mt;
int ret = pthread_mutex_init(&mt, NULL);

class SingletonSafe{
public:
    int num;
    static SingletonSafe* getInstance(){
        pthread_mutex_lock(&mt);
        if(instance == NULL){
            instance = new SingletonSafe(2);
            cout << "空,实例化" << endl;
        }
        pthread_mutex_unlock(&mt);
        return instance;
    } 

private:
    static SingletonSafe* instance;
    SingletonSafe() = delete;
    SingletonSafe(int n):num(n){};
    SingletonSafe(SingletonSafe& obj) = delete;
    SingletonSafe(SingletonSafe&& obj) = delete;
};
SingletonSafe* SingletonSafe::instance = NULL;


template<class T>
class TemplateSingleton{
public:
    static T* GetInstance(){
        static T p;
        return &p;
    }
};

class A{
public:
    int a;
};


int main(){
    SingletonSafe* ptr1 = SingletonSafe::getInstance();
    SingletonSafe* ptr2 = SingletonSafe::getInstance();
    cout << ptr1->num << endl;
    cout << ptr2->num << endl;
    A* intP = TemplateSingleton<A>::GetInstance();
    intP->a = 3;
    cout << intP->a << endl;
    return 0;
}

能写自己实现shared_ptr吗?

#include

using namespace std;

template<class T>
class Shared{
public:
    T* ptr;
    int count=0;

    Shared(T* p)
        :ptr(p){
        count++;
        cout << "普通构造函数" << endl;
    }
    Shared(Shared& p){
        ptr = p.ptr;
        count = p.count;
        count++;
        cout << "拷贝构造函数" << endl;
    }
    void operator= (Shared& p){
        ptr = p.ptr;
        count  = p.count;
        count++;
        cout << "赋值构造函数" << endl;
    }
    ~Shared(){
        if(count == 1){
            delete ptr;
            cout << "count=1,析构,delete" << endl;
        }else{
            cout << "count!=1,当前引用计数为:" << count << endl;
        }
    }
    T* get(){return ptr;}
    T operator* (){return *ptr;}
    T* operator-> (){return ptr;}
    int use_count(){return count;}
};

class Foo{
public:
    int a;
};

int main(){
    Shared<Foo> sp1(new Foo());
    cout << sp1.use_count() << endl;
    Shared<Foo> sp2(new Foo());
    cout << sp2.use_count() << endl;
    Shared<Foo> sp3 = sp1;
    cout << sp1.use_count() << endl;
    cout << sp2.use_count() << endl;
    cout << sp3.use_count() << endl;
}

能写迪杰斯特拉算法吗?

#include
#include
using namespace std;

int getMinDist(vector<int>& curDist, vector<bool>& visited){
    int tmpMin = __INT_MAX__;
    int index = -1;
    for(int i = 0; i < curDist.size(); i++){
        if(curDist[i] >= 0 &&  curDist[i] <= tmpMin && visited[i] == false){
            index = i;
            tmpMin = curDist[i];
        }
    }
    visited[index] = true;
    return index;
}


vector<int> dijkstra(vector<vector<int>> graph){
    int numNode = graph.size();
    vector<int> curDist(numNode, __INT_MAX__);
    vector<bool> visited(numNode, false);
    curDist[0] = 0;
    while(true){
        // 首先要获得0号节点与其他节点之间的最小距离,以及访问情况
        int index = getMinDist(curDist, visited);
        if(index == -1){
            break;
        }else{
            vector<int> curNode = graph[index];
            for(int i=0; i<curNode.size(); i++){
                if(curNode[i] > 0 && visited[i] == false) curDist[i] = min(curDist[index]+curNode[i], curDist[i]);
            }
        }
    }
    return curDist;
}

int main(){
    vector<vector<int> > graph={
        {0,4,2,3,-1},
        {4,0,1,1,5},
        {2,1,0,2,-1},
        {3,1,2,0,7},
        {-1,5,-1,7,0}
    };
    vector<int> dist =  dijkstra(graph);//计算0节点到所有节点的最短路径
    for(int i : dist){
        cout << i << " ";
    }
    cout << endl;
}

malloc是怎么分配内存的?

C++默认的分配器是那个内存池分配器,使用malloc申请小于128k内存,直接在内存池中取,free的时候还回内存池;对于申请大于128k内存的情况,使用mmap区申请,free的时候需要还回操作系统。

使用内存池,不需要陷入内核,这能提高效率。

C++ char不能修改 C++中char[]能修改char却不行?

https://www.qb5200.com/article/405141.html

一个字符串是存在常量数据区的,不允许修改。对于char*的字符串,如果想通过下标直接修改字符串中的值,会报错;char[]的字符串,通过下标修改字符串,虽然也会去访问常量数据区,但是在修改之前,会将这段字符串复制一份,在这个复制字符串的基础上进行修改。

thread_local怎么理解?

这个声明变量是存储期变量,对象的存储时在线程开始时分配,现成结束时回收,每个线程都有该对象自己的实例。在一个线程的for循环中声明thread_local就相当于声明一个线程的static变量。

memcopy和strncopy的区别?strcopy和strncopy的区别?

strncpy只能应用字符类型的复制,而memcpy应用范围更广,任何类型都可以。在拷贝相同的字符串,且字节数相同(包括‘\0’)的情况下,strcpy效率比memcpy效率更快。

strcpy一定会拷贝字符串结尾符’\0’,strncopy可以指定长度。

静态多态和动态多态

多态的实现主要分为静态多态和动态多态,静态多态主要是重载和模板,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。

智能指针有哪些?

  1. unique_ptr:独享被管理对象指针所有权,实现方法是无赋值、拷贝构造函数,但是可以通过move来对所有权进行转移。

  2. shared_ptr配合weak_ptr:shared_ptr使用引用计数来管理指向一个对象的指针数,当指针数为0的时候就delete掉;使用weak_ptr来解决循环引用的问题,指向一个对象不增加引用计数,通过智能指针来构造weak_ptr。

  3. auto_ptr:带有缺陷的设计,被弃用

为什么抽象类无法实例化?

抽象类中包含着纯虚函数,纯虚函数无函数体,无法被分配内存空间,也无法被编译器调用。

extern C的作用

作用是:在C++中按照C语言的标准去编译

为什么需要这个东西?

因为:C++支持函数重载的原理是,编译器在编译函数的过程中会将函数的参数类型+函数名加到编译后的代码中;而 C只加函数名;两者都不加返回值。

volatile有什么作用?

一个定义为volatile的变量,表明该变量和能在意想不到的地方会发生变化,因此,需要编译器每次都从该变量的内存地址去取值,而不是从寄存器中取值。因为,在多线程中,编译器可能会做优化,会直接从寄存器中取值,而另外的线程又对这个变量做了修改并且写回了内存,这个时候从寄存器取值就会发生错误。

静态全局变量、静态局部变量、全局变量、局部变量的区别?

全局变量(函数体外定义): 全局作用域,可以通过extern(引入C的那个)作用于其他非定义的源文件;都会一直存在,直到程序结束。

静态全局变量 : 全局作用域+文件作用域,所以无法在其他文件中使用。

局部变量: 局部作用域,比如函数的参数,函数内的局部变量等等;它从进入作用域遇到该变量的时候开始出现,在离开的时候销毁。

静态局部变量 : 局部作用域,只被初始化一次,直到程序结束。

如何让new申请失败不抛异常?

#include
auto p = new (std::nothrow) type;

如何确定主机的字节序?

  1. 通过指针来判断:
#include   
#include 
//用指针的方式检测机器的大小端模式
int small_port()
{
	int a = 1 ; 
	char b = *((char *)(&a)) ;
	return b ;
}
int main(void)
{
	int i = small_port();
	if(1 == i)
		printf("小端模式\n");
	else
		printf("大端模式\n");
	return 0 ;	
} 
  1. 通过联合体来判断:
bool check()
{
    unionu {
    	int a;
    	char b;
	} c;

	c.a =1;
	return(c.b ==1);//小端返回true, 大端返回false
}

怎么只能在栈/堆上实例化对象?

代码来自https://blog.csdn.net/cx2479750196/article/details/80781873

只能在栈上创建:

将operator new和operator delete设成私有

class AA{
private:
    void* operator new(size_t size);
    void  operator delete(void * p)public:
     AA(){
     	cout<<"AA()"<<endl;
     }
     ~AA(){
     	cout<<"~AA()"<<endl;
     }
     int _a;
}

只在堆上创建:

有点像单例,但又不是

class AA{
publicstatic getobj(){
   		return new AA;
    }  
private: 
  	 int _a;
 	 AA(const AA& a);
 	 AA& operator=(const AA& a);
};
int main(){
 	AA* p=AA::getobj();//堆上
	// AA* q(p);//栈上
	return 0;
}

使用vector如何避免频繁的内存重新分配?

移动构造、reverse

dynamic_cast和static_cast的应用场景

  • static_cast
    用法:static_cast <类型说明符> (变量或表达式)
    它主要有如下几种用法:
    (1)用于类层次结构中基类和派生类之间指针或引用的转换
    进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
    进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
    (2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
    (3)把空指针转换成目标类型的空指针
    (4)把任何类型的表达式转换为void类型
    注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。
    static_cast:可以实现C++中内置基本数据类型之间的相互转换。

  • dynamic_cast
    用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。
    向上转换:指的是子类向基类的转换
    向下转换:指的是基类向子类的转换
    它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。

内联函数是否有函数指针?

有,因为内联函数是与其他函数一样的函数,所以指向它们是函数可以执行的操作之一.

宏和内联的区别

1、内联函数在编译时展开而宏在预编译时展开
2、在编译时,内联函数直接被嵌入到目标代码中,而宏只是进行简单的文本替换
3、内联函数可以进行诸如安全检查、语句是否正确等辨义功能,宏不具有这样的功能
4、宏不是函数,而inline是函数
5、宏在定义时,要小心处理参数时,需要用括号括起来,防止出现二义性。而内联函数不会出现二义性
6、inline可以不展开,而宏一定要展开,因为inline对于编译器来说只是一个建议,编译器可以选择忽略该建议,不对该函数进行展开。

怎么解决扩容时对所有的元素rehash?

https://zhuanlan.zhihu.com/p/258340429
https://xiaolincoding.com/redis/data_struct/data_struct.html#rehash
渐进式rehash是一种解决方案。

线程如何实现并发顺序运行?

代码来自https://blog.csdn.net/qq_36704378/article/details/113933506

#include 
class Foo {
private:
    sem_t firstDone;
    sem_t secondDone;
public:
    Foo() {
        sem_init(&firstDone,0,0);
        sem_init(&secondDone,0,0);
    }

    void first(function<void()> printFirst) {
        
        // printFirst() outputs "first". Do not change or remove this line.
        printFirst();
        sem_post(&firstDone);
    }

    void second(function<void()> printSecond) {
        sem_wait(&firstDone);
        // printSecond() outputs "second". Do not change or remove this line.
        printSecond();
        sem_post(&secondDone);
    }

    void third(function<void()> printThird) {
        sem_wait(&secondDone);
        // printThird() outputs "third". Do not change or remove this line.
        printThird();
    }
};

函数参数入栈为什么是从右往左的顺序?

  1. 从右向左压栈,不是规定,也不是因为栈先进后出的特性。在《c和指针》中已经说明了从右向左压栈的原因,这样可以保证生成汇编语言时这些参数相对于BP指向的栈位置的偏移量是固定的,因为程序员有时为函数传递的参数会或多或或少,如果从左向右压栈,则从BP指向的位置到参数的偏移量就会根据用户传入的参数数量而发生改变,这时编译器的识别难度就会大大增加,因此才会从右向左压栈。其实从我们平时的程序中就可以发现,如果我们的C程序函数参数比较少的话,传进去的前几个参数是可以正常接收的;如果参数传递地多的话,多余的参数也会丢弃,就是这个原理,你也会发现,从右向左可以非常好地降低编译器的操作难度。

  2. 因为C++支持可变长函数参数。正是这个原 因使得C语言函数参数入栈顺序为从右至左。具体原因为:C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。C 程序栈底为高地址,栈顶为低地址。函数最左边确定的参数在栈上的位置必须是确定的,否则意味着已经确定的参数是 不能定位和找到的,这样是无法保证函数正确执行的。衡量参数在栈上的位置,就是离开确切的 函数调用点(call f)有多远。已经确定的参数,它在栈上的位置,不应该依 赖参数的具体数量,因为参数的数量是未知的!

va_list原理?

typedef char * va_list; // TC中定义为void*
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //ap指向第一个变参的位置,即将第一个变参的地址赋予ap
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /获取变参的具体内容,t为变参的类型,如有多个参数,则通过移动ap的指针来获得变参的地址,从而获得内容/
#define va_end(ap) ( ap = (va_list)0 ) //清空va_list,即结束变参的获取

编程实现给百度发送get请求?

#include
#include
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;



int main(){
    // 1.创建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);

    // 2.使用DNS找到百度对应的IP
    char *host = "www.baidu.com";
	struct addrinfo *result = NULL;
	struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL};
    getaddrinfo(host, NULL, &hints, & result);
    char* ip = inet_ntoa(((struct sockaddr_in*)(result->ai_addr))->sin_addr);
    cout << host << " : " << ip << endl;
    
    // 3.创建地址结构体,指定IP地址,协议族,端口
    struct sockaddr_in baiduAddr;
    baiduAddr.sin_family = result->ai_family;
    inet_pton(result->ai_family, ip, &baiduAddr.sin_addr.s_addr);
    baiduAddr.sin_port = htons(80);
    
    // 4.连接百度
    int ret = connect(fd, (struct sockaddr*)&baiduAddr, sizeof(baiduAddr));
    cout << "connect返回值:" << ret << endl;

    // 5.连接成功,发送get请求
    char requestData[] = "GET / HTTP/1.0\r\n"
                        "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n"
                        "Accept-Encoding: gzip, deflate\r\n"
                        "Proxy-Connection: keep-alive\r\n\r\n";
    // char requestData[] = "GET / HTTP/1.0\r\n\r\n";
    ret = write(fd, &requestData, strlen(requestData));
    cout << "发送返回值:" << ret << endl;

    // 6.接收数据
    string recvBuf;
    recvBuf.resize(4096);
    int len = read(fd, &recvBuf[0], sizeof(recvBuf));
    cout << len << " " << recvBuf << endl;
}

你可能感兴趣的:(C++面试题汇总,c++,开发语言)