C++面试 一 基础

1. 关键字的作用:

    extern:

        1. 用在变量和函数前面,表示定义在其他文件或模块中;

            经常出现extern的变量和函数,和声明的不一样,一般解决方法是,放到头文件中加extern,其他模块引用头文件

        2. extern C

            C++为了解决函数多态的问题,编译时会将函数名和参数合起来生成一个中间名称,extern C告诉编译器保持名称不变

    static:

        1. static变量和函数只在当前文件中可见

        2. 静态全局变量,在程序开始运行时初始化,未初始化的会自动初始化为0,在静态(全局)数据区分配内存

            静态局部变量,在程序第一次执行到时初始化,以后再调用不会初始化,再全局数据区分配内存,作用域为函数内部

            C++的static成员属于类成员,所有类对象共享,没有this指针,可以通过对象或类名引用

    volatile:

        是一种类型修饰符,表示变量是“易变”的,随时可能发生变化,用于防止编译器优化时,将变量从内存放入寄存器,如果是非volatile变量, 多线程时,有可能一个线程访问内存,一个线程访问寄存器,会造成错误,而volatile变量,每次使用时都首先从内存中获取。

        但是,volatile并不能解决多线程并发问题,两个非volatile变量,或一个非volatile一个volatile变量,编译器可能会优化代码执行顺序,两个volatile变量,编译器不会优化指令顺序,但是CPU却可能乱序执行,因此对于多线程,还是使用互斥、读写锁之类的方法,来保证线程同步。

    const:

        const int n = 10 const修饰的变量具有常属性,不能被修改,必须在声明时初始化

        const int * n = &a 或 int const * n = &a  const在*之前,常量指针,指针指向的内容是常量,不能改变内容

        int * const n = 1 const在*之后,指针常量,指针本身是个常量, 不能指向其他地址

        const int  &b = a; const修饰引用,可以修饰函数的参数,返回值

        const成员变量,只能在构造函数的初始化列表中赋值,不能在构造函数中赋值,在单个对象内是常量,但是对于类的多个对象,是可以为不同值的

        const成员函数,int getvalue() const {}表示函数内不能修改类对象的所有成员变量,也不能调用任何非const成员函数

        const修饰类成员,则位类常量对象,所有成员都不能改变,也不能调用非const成员函数

        const重载  void func(){}  和 void func() const{}  是两个函数

        const_cast运算符用来修改类型的const或volatile属性

2. const全局变量和宏的区别

    const常量便于类型检查,宏没有数据类型,防止意外修改,

    const节省空间,通常情况下,编译器不为其分配内存,只是将其保存在符号表中避免不必要的内存分配,给出的是对应的内存地址, 而宏给出的是立即数

3. 内联函数和宏的区别

    宏不是函数,是在编译的预处理阶段,将程序中的相关字符串简单替换

    内联函数是将代码直接插入到调用处,减少了普通函数调用时的资源消耗

    内敛函数可以在运行时调试,宏不行

    编译器会对内敛函数做类型检查或自动类型转换,宏定义不会

    类中声明同时定义的函数,自动转为内联函数

4. new和malloc的区别

    1. new是C++的操作符,malloc是库函数

    2. 申请的内存的位置,new在自由存储区上,自由存储区是C++基于new操作符的一个抽象概念,可以是堆或静态存储区,malloc是在内存池,也就是堆上分配内存空间

    3. 返回类型,成功时,new返回对象类型指针,不需要类型转换,malloc返回void*,需要类型转换,失败时new抛出bac_alloc异常,malloc返回NULL

    4. 参数,new不需要制定分配内存的大小,会根据类型自动计算,malloc需要指定分配内存的大小

    5. new会调用构造函数,malloc不会,构造函数三步,new申请内存,调用构造函数,返回类对象指针

    6. 对数组的处理, new[] 给出数组元素个数,malloc需要指定数组所有元素内存总和

    7. new和delete可以重载,可以基于malloc实现,malloc/free不能重载,不能调用new

    8. 重新分配内存, malloc分配的空间不足时,可以调用realloc实现内存扩充,new没有类似的功能,realloc先判断当前指针指向的地址是否有足够的连续内存空间,如果有则元原地扩充内存,返回原地址,如果连续空间不够,则重新分配指定大小的空间,将原数据拷贝到新空间,并释放原空间,返回新地址

5. C++多态

    1. C++的多态:在基类的函数前加virtual关键字,在子类中重写该函数,运行时根据对象的实际类型来调用相应的函数,父类对象调用父类函数,子类对象调用子类函数。

    2. 多态的原理就是虚函数和动态绑定:

        1>有虚函数的类都有一个一维虚函数表,类对象都有一个指向虚表开始的指针,续表和类对应,虚表指针和类对象对应。同一个类的多个对象,虚表是相同的。虚表存放在全局数据区,不占用类对象的内存空间。每个类对象都包含一个虚表指针,不同的对象,虚表指针不同。计算类对象的大小,需要注意是否有虚函数表指针。虚表指针在构造函数中完成初始化。

        2>动态绑定是利用虚函数,在子类中重写父类的函数, 运行时根据对象的实际类型调用相应的函数

    3. 纯虚函数是virtual void func() = 0; 纯虚函数只有声明没有定义,用来规范派生类的接口

    4. 抽象类是包含纯虚函数的类,不能创建实例,但是可以声明指向派生类的指针或引用

    5. 构造函数不能是虚函数,析构函数可以是虚函数,并且在包含虚函数的类中,析构函数一般都定义为虚函数,是为了防止只释放基类资源,比如使用基类指针指向子类对象时,如果析构函数不是虚函数,则delete基类指针时,调用的是基类的析构函数,不调用子类析构函数,可能造成内存泄漏。

    6. 构造函数可以抛出异常,异常后不会调用析构函数,可能造成内存泄漏,析构函数不应该抛出异常,会直接导致当前线程退出,解决办法是在析构函数中使用try catch吞掉异常

    7. 构造函数和析构函数从语法上可以调用虚函数,但都不适用动态联编,调用的是构造函数和析构函数本身所在类的版本

    8. 动态多态就是利用虚函数,在基类函数前加virtual,在子类中重写该函数,运行时根据对象的实际类型调用相应的函数,静态多态,是各个类不需要有相同的基类,只需要有相同的接口,然后将接口定义为模板,在编译时确定需要什么对象,就将对象作为模板的实参

    9. 必须在构造函数初始化列表里初始化的有:1) 成员是一个类对象,且这个类没有默认构造函数,只有带参数的构造函数,2)引用或const修饰的成员,3)子类初始化父类的私有成员时,必须也只能在初始化列表中显示的调用父类构造函数

必须在构造函数初始化列表里初始化的数据成员有哪些

6. sizeof结构体、带虚函数的类、指针、字符串、数组

    sizeof基本数据类型,32位系统中,int long都是4字节,double是8字节,64位系统中int为8字节

    sizeof字符串,需要考虑字符串结尾的'\0'

    sizeof指针,32位中为4字节,64位为8字节

    sizeof数组,是数组实际占用的字节总数,注意不是数组元素个数

    sizeof结构体,考虑字节对齐,#pragma pack(n)可以改变字节对齐数,空结构体大小为由编译器决定,一般为1

    sizeof类,和结构体一样,考虑字节对齐, 成员函数都不计算大小,只计算成员变量, 当类中有虚函数时,编译器会创建虚函数表,不计入类大小,但是会为每个对象保存一个虚表指针,因此带有虚函数的类,计算大小时需要加虚表指针的大小

7. 指针和引用的区别

    1. 指针是一个实体,占用内存空间,引用是变量的别名,从语言的角度不占内存,从汇编上看占用内存

    2. 指针可以不初始化,可以改变,引用必须在声明时初始化,且不能改变

    3. 指针可以为NULL,使用前必须判断是否为NULL,不存在空引用

    4. sizof指针得到指针本身的大小,sizeof引用得到引用指向的变量的大小

    5. 指针和引用的自增++,运算意义不一样

    6. 智能指针,auto_ptr是C99的内容,share_ptr,weak_ptr是C11的新内容,引用计数

9. C++内存管理

    1. 栈,编译器自动分配释放,windows默认2M,linux默认8M栈,ulimit -a 查看当前设置,ulimit -s 16384设置为16兆,可以修改系统配置文件

    2. 堆,由程序员使用malloc、free等分配释放,是操作系统的属于,由操作系统维护,不连续的空间,理论上是硬盘的大小

    3. 自由存储区,是C++使用new、delete分配和释放对象的抽象概念,基本上所有的C++编译器都是用堆实现自由存储

    4. 静态/全局数据区,分为初始化区和未初始化区两部分,大小2G,未初始化时系统自动置0

    5. 常量区,字符串常量等

    6. 代码区

    static全局变量和static局部变量,都存放在静态/全局区

    const全局变量存放在静态/全局数据区,编译器最初保存在符号表中,第一次使用时分配内存,程序结束时释放,const局部变量放在栈区,代码块结束时释放。

    全局变量存放在静态/全局数据区,编译时分配内存,程序结束时释放。

    局部变量存储在中,代码块结束时释放

    内存对齐的原则:

        1)结构体成员,第一个从offset=0处开始,以后每个成员都从其自身大小的整数倍开始存放,如int成员必须从0,4,8开始

        2)结构体中的结构体成员,要从其内部“最宽的基本类型“的整数倍开始存放

        3)sizeof结构体,必须是其内部最宽基本类型的整数倍

        4)sizeof联合体,是其内部最大成员的大小

        结构体成员的顺序不同,sizeof的结果可能不同

    内存对齐的作用:

        1)平台原因,有些硬件平台只能从特定地址读取特定类型的数据,便于移植

        2)性能原因,内存对齐后,CPU的内存访问速度会大大提升

10. STL标准库

    string:字符串类,封装char*,不用考虑内存释放

        三种遍历方式:数组方式str[i](可能越界异常)、at方法str.at(i)(越界会抛出异常)、迭代器string::iterator

        获取c字符指针const char* p = str.c_str();

        字符串拼接:重载+操作符s3 = s1 + s2;append方法s3 = s1.append(s2)

        查找:size_t index = str.find("hello", 0); 替换 str.replace(index, len, "welcome")

        删除:string& erase(pos, n) 插入:string& insert(pos, str)

    vector:封装数组,随机存取快,支持[]和at访问,在中间插入时,需要移动元素,效率慢

        基本方法:back() front() push_back() pop_back()

        初始化:vector<int> v1; v1.push_back(1);   vector<int> v2 = v1;   vector<int> v3(v1.begin(), v1.end()); vector<int> v4(3, 9);

        遍历:支持[]和at(),迭代器支持逆向,begin() - end()  rbegin() - rend();  遍历时删除要注意 it = v1.erase(it)

    list:封装双向链表,没有重载[],随机存取效率为O(n),比vector慢,插入删除效率比vector高,因为不需要移动元素,

    queue:底层一般用list或deque实现,FIFO先进先出,不用vector,是因为容量有限制,扩容耗时

    priority_queue:优先级队列,底层一般用vector为容器,使用堆为处理规则,

    stack:底层一般用list或deque实现,FILO先进后出,不用vector,是因为容量有限制,扩容耗时

    deque:deque<int> dq; 双端队列,内部维护一段连续的空间,其中每个元素都是一个指针,指向另一块连续的空间,存放实际的数据,支持随机访问,由于是若干段不连续空间组成,因此随机访问和遍历的效率比vector慢。两端都可以插入删除数据。

    map/multimap:底层数据结构为红黑树,键值对,有序,不重复map,可重复multimap

        map的key如果是自定义类型,则需要重载<操作符。因为是按key有序存储的

    set/multiset:底层数据结构为红黑树,集合,有序,不重复set,可重复multiset

    hash_set/hash_multiset:底层数据结构为hash表,无序,不重复hash_set,可重复hash_multiset

    hash_map/hash_multimap:底层数据结构为hash表,无序,不重复hash_map,可重复hash_multimap

    红黑树:是一种自平衡的二叉查找树,AVL是完全平衡树,节点的左右子树高度差不超过1,红黑树只要求局部平衡。

        1)每个节点都是红色或黑色

        2)根节点是黑色

        3)每个为NULL的叶子节点是黑色

        4)如果一个节点是红色,则它的子节点必须是黑色的

        5)从一个节点到这个节点的的叶子节点的所有路径上,黑色节点的个数相同

    stl常用算法:

        1. sort: sort(begin, end, compare)   sort(a, a+20)

            数据量大时,采用快速排序,将数据分段归并排序,对于每一段数据,如果数据量较小(比如小于16),为了避免快排递归调用带来的额外负荷,就采用插入排序,如果递归层次太深,就采用堆排序。

            关系型容器,如map、set等,采用红黑树,具有自动排序功能,不需要sort

            序列型容器,stack、queue、priority_queue有特定出入口,不允许用户排序,剩下vector和deque适合排序

        2. qsort:sort(in,100,sizeof(in[0]),cmp) 快速排序

    iterator中remove和erase的区别

        vector的remove是把等于value的元素放到vector的尾部,size不变, erase是删除某个位置或区域的元素,size变小

        list的remove是删除value元素,并释放资源,erase是删除pos位置的元素

        set只有erase,没有remove,有三种删除方法erase(pos)、erase(val)、erase(begin, end)

11. 原子操作

    原子操作是指一个操作(可能包含多个指令)是不可中断的,即使在多线程中,一个操作一旦开始,就不会被其他线程打断。

    i++和++i都不是原子操作,系统操作一个变量包括三步:1读,从内存读到寄存器,2改,在寄存器中改值,3写,从寄存器重新写入内存。因此在多线程中,对全局变量的操作,都是不安全的。

    初值i=0; 那么i++在两个线程中分别执行100次,能得到的最大值和最小值分别是多少?(2-200)

    C++ 11的原子变量: atomic头文件,std::atomic<int> i = 1;

12. 如何限制对象只能建立在堆上 或者 栈上

1. 对象只能在堆上建立,就是不能声明局部变量,只能通过new创建对象

将析构函数设置为私有,对象就无法创建在栈上了

2. 对象只能在栈上建立

禁用类的new运算符,重载operator new() 为私有,并且要重载delete

13. 线程安全的单例模式

分为懒汉模式和饿汉模式

1. 饿汉模式:

template<class T>

class singleton

{

protected:

singleton(){}

private:

singleton(const singleton&){} //禁止拷贝

singleton& operator=(const singleton&) //禁止赋值

static T* m_instance;

public:

static T* getInstance();

}

template<class T>

T* singleton<T> :: getInstance(){

return m_instance;

}

template<class T>

T* singleton<T> :: m_instance = new T();

在还未使用变量时,已经对m_instance进行初始化赋值了,不存在多线程实例化问题

2. 懒汉模式

template<class T>

T* singleton<T>::getInstance(){

if(m_instance == NULL){

m_instance = new T();

}

return m_instance;

}

初始化时m_instance先为空,在调用获取实例的方法时,判断是否需要创建对象,所以这种需要加锁:

template<class T>

T* singleton<T>::getInstance(){

pthread_mutex_lock(&m_mutex);

if(m_instance == NULL){

m_instance = new T();

}

pthread_mutex_unlock(&m_mutex)

return m_instance;

}

每次进来都需要加锁,影响效率,因此有了 “双检锁机制”

template<class T>

T* singleton<T>::getInstance(){

if(m_instance == NULL){

pthread_mutex_lock(&m_mutex);

if(m_instance == NULL){

m_instance = new T();

}

pthread_mutex_unlock(&m_mutex)

}

return m_instance;

}

又有可能调用new T()时,初始化还没有完成m_instance已经有值了,导致另一个线程获取到还没有初始化完成的指针,添加一个临时变量即可

T* temp = new T();

m_instance = temp;

linux中还有一种实现,

static pthread_once m_once;

void singleton<T>::Init(){

m_instance = new T();

}

T* singleton<T>::getInstance(){

pthread_once(&m_once, Init);

return m_instance;

}

pthread_once_t singleton<T>::m_once = PTHREAD_ONCE_INIT;

14. fread和read fopen和open fwrite和write的区别

1. fread是C标准库函数,read是系统调用

2. fread带有缓冲,是通过read实现的,read不带缓冲,可以直接访问硬件

3. fread返回一个FILE结构指针,read返回的是int文件号

4. 如果文件大小时8K,read和write只分配了2K缓存,则需要调用4次系统调用来从磁盘上实际读写,如果用fread和fwrite,系统会自动分配缓存,读取文件只需要一次调用

5. 处理文件用fread,处理套接字,管道等,用read

15. c++11中的智能指针

1. 智能指针的作用

1. 智能指针实质是一个对象,行为表现像一个指针

2. 防止未调用delete和程序异常进入catch忘记释放内存,多次释放同一个指针等

3. 把值语义转为引用语义

2. 智能指针的使用,在c++11之后,<memory>头文件中,是一个模板类

1. shared_ptr,多个指针指向同一个对象,使用引用计数,每一个shared_ptr的拷贝都指向同一块内存,每使用他一次,内部引用计数加1,每析构一次,内部引用计数减1,减为0时,自动释放堆内存,shared_ptr内部引用计数是线程安全的,但是对象读取需要加锁。

int a;

std::shared_ptr<int> ptra = std::make_shared<int>(a);

std::shared_ptr<int> ptrb(ptra);

cout << ptra.use_count() << endl;

int* pb = ptrb.get() //获取原始指针

注意不要用一个原始指针初始化多个shared_ptr,会造成重复释放

要避免循环引用

2. unique_ptr同一时刻,只能有一个unique_ptr指向给定的对象

std::unique_ptr<int> uptr(new int(10)); 绑定动态对象

std::unique_ptr<int> uptr2 = uptr 错误,不能赋值,不能拷贝

std::unique_ptr<int> uptr2 = std::move(uptr) 转移所有权

uptr2.release() 释放所有权

超过作用域,自动释放内存

3. weak_ptr,为了配合shared_ptr,像旁观者一样,它的构造不会引起指针引用计数增加。

std::shared_ptr<int> sptr = std::make_shared<int>(10);

std::weak_ptr<int> wp<sptr>;

wp.expired() 表示引用计数

std::shared_ptr<int> sptr2 = wp.lock(); 获取一个可用的shared_ptr对象

weak_ptr 可以解决循环引用的问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值