c++基础问题总结(一)

1、static

        全局静态变量、局部静态变量、静态函数、类的静态成员、类的静态函数

-----------c++其他修饰符:const、volatile(修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。)、restrict(由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。)

https://www.runoob.com/cplusplus/cpp-modifier-types.html


 

2、内存管理(内存分配)

        堆、栈、自由存储区、全局/静态存储区、常量存储区

        例子:

int a=0;    //全局初始化区
char *p1;   //全局未初始化区
main() {
	int b;//栈
	char s[]="abc";   //栈
	char *p2;         //栈
	char *p3="123456";   //123456 \0在常量区,p3在栈上。
	static int c=0;   //全局(静态)初始化区
	p1 = (char*)malloc(10);
	p2 = (char*)malloc(20);   //分配得来得10和20字节的区域就在堆区。
	strcpy(p1,"123456");   //123456\0放在常量区,编译器可能会将它与p3所向"123456"优化成一个地方。
}

3、线程池

       传统多线程方案中我们采用的服务器模型则是一旦接受到请求之后,即创建一个新的线程,由该线程执行任务。任务执行完毕后,线程退出,这就是是“即时创建,即时销毁”的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器将处于不停的创建线程,销毁线程的状态。

        因此线程池的出现正是着眼于减少线程池本身带来的开销。线程池采用预创建的技术,在应用程序启动之后,将立即创建一定数量的线程(N1),放入空闲队列中。这些线程都是处于阻塞(Suspended)状态,不消耗CPU,但占用较小的内存空间。当任务到来后,缓冲池选择一个空闲线程,把任务传入此线程中运行。当N1个线程都在处理任务后,缓冲池自动创建一定数量的新线程,用于处理更多的任务。在任务执行完毕后线程也不退出,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程都一直处于暂停状态,线程池自动销毁一部分线程,回收系统资源。 

线程池:https://www.cnblogs.com/cpper-kaixuan/p/3640485.html

线程池:https://www.cnblogs.com/yangang92/p/5485868.html

mutex简介:https://blog.csdn.net/WizardtoH/article/details/81452066


4、detch和join的区别

https://blog.csdn.net/liunan199481/article/details/83055901


5、condition_variable

       线程的同步方式:信号量、互斥量、条件变量

       notify_one():唤醒处于wait中的其中一个条件变量

       wait():可以让线程陷入休眠状态---------(与sleep的区别)

https://www.jianshu.com/p/c1dfa1d40f53


6、Hash、HashTable、Hash_map、map

https://blog.csdn.net/lyq_csdn/article/details/80630872


7、TCP三次握手和四次挥手

        为什么不是两次握手或者四次握手?窗口机制/快速重传?拥塞控制?慢启动、拥塞避免?与UDP区别?


8、string 与char* 和char[]区别及转化

  • string----->const char *   : .c_str()或者data()
  • string-----> char *  :copy()
  • char * 或者char[] ----->string : 直接赋值
  • string-----> char[]  : 循环赋值

https://blog.csdn.net/bestcleaner/article/details/81516771


9、指针和引用的区别

 指针引用
空间有自己的空间只是一个别名
sizeof4对象大小
初始化NULL/nullptr必须初始化为已有对象的引用
参数传递解引用直接对引用修改
const指针没有
指向可以指向其他只能引用一个
级数多级指针一级引用
++地址++(4字节)值++
动态内存分配必须使用指针可能内存泄露

10、智能指针

https://blog.csdn.net/zhou753099943/article/details/52412381

auto_ptr:管理权的转移 ->不建议使用

scoped_ptr ( unique_ptr ):防拷贝 ->简单粗暴

shared_ptr: 引用计数 ->增减引用计数,直到对象的引用计数为0时再释放

weak_ptr:辅助shared_ptr解决循环引用的问题(内存泄漏问题)。

        原理:智能指针实现技术是使用引用计数。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。

  • 每次创建类的新对象时,初始化指针就将引用计数置为1;
  • 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
  • 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;
  • 调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。

11、虚函数

         11.1什么是虚函数?

        https://www.cnblogs.com/jianyungsun/p/6361670.html

        实现多态所必须的,基类指针指向派生类的实例,执行的时候会执行派生类中的定义。(否则只会执行基类部分)

         11.2析构函数可以是虚函数吗?

        如果有派生类的话,析构函数必须是虚函数,否则会派生类无法析构引起内存泄露。

         11.3多态的实现?

        简而言之编译器根据虚函数表找到恰当的虚函数。对于一个基类对象指针类型变量,如果给他赋基类对象的指针,那么他就调用基类中的函数,如果给他赋派生类对象的指针,他就调用子类中的函数。函数执行之前查表

         11.4虚函数表是针对类还是针对对象的?

        虚函数表是针对类的,一个类的所有对象的虚函数表都一样。

         11.5纯虚函数和虚函数有什么区别?

        纯虚函数就是定义了一个虚函数但并没有实现,原型后面加"=0"包含纯虚函数的类都是抽象类,不能生成实例。

         11.6构造函数可以是虚函数吗?

        每个对象的虚函数表指针是在构造函数中初始化的,因为构造函数没执行完,所以虚函数表指针还没初始化好,构造函数的虚函数不起作用。  所以在构造函数中调用虚函数,和调用一般的成员函数一样。

         11.7析构函数中可以调用虚函数吗?

        析构函数中调用虚函数也不起作用,调用虚函数同调用一般的成员函数一样。析构函数的顺序是先派生类后基类,有可能内容已经被析构没了,所以虚函数不起作用。

         11.8虚继承和虚基类?

        虚继承是为了解决多重继承出现菱形继承时出现的问题(钻石问题)。例如:类B、C分别继承了类A。类D多重继承类B和C的时候,类A中的数据就会在类D中存在多份。通过声明继承关系的时候加上virtual关键字可以实现虚继承。

        虚继承是一种机制,类通过虚继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。构造时先构造虚基类,再构造非虚基类。


12、fork函数

        成功调用fork()函数会创建一个新的进程,子进程中成功调用返回0,父进程返回子进程的pid;如果出现负值,返回一个负值。


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

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


14、重写和重载的区别

        重载:函数名相同,参数列表不同,在同一作用域;

        重写:子类继承父类,子类重新定义了父类的虚函数;


15、IO多路复用:select、poll、epoll(都是同步I/O

        IO多路复用:通过一种机制,一个进程监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小;

select 调用后select函数会阻塞,直到有描述符就绪(或者超时),函数返回,返回后遍历fdset;

优点:跨平台支持;

缺点:①单个进程监视的文件描述符数量存在上限;

           ②轮询的方式效率低;

           ③需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

poll:本质上和select没有区别,它将用户传入的数组拷贝到内核空间;轮询fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。

优点:没有最大连接数限制(以为是基于链表存储的)

缺点:①大量的fd的数组被整体复制于用户态和内核地址空间之间 ;

           ②“水平触发”------如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

epoll:epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

优点:①没有最大连接数设置

           ②效率提升,不是轮询的方式

           ③epoll使用mmap减少复制开销

模式:LT(level trigger)和ET(edge trigger)

  • LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
  • ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

IO复用:https://www.cnblogs.com/wt645631686/p/8528912.html

select、poll、epoll:https://www.cnblogs.com/jeakeven/p/5435916.html


16、同步、异步、阻塞、非阻塞概念:

  • 同步:指进程调用接口时,需要等待接口处理完数据,进程才能继续执行。
  • 异步:指进程调用接口后,不必等待数据准备完成,进程可以继续执行。后续数据准备好通过一定的方式获得,例如回调。服务器也必须支持异步操作,否则无法返回数据。
  • 阻塞:指的是进程调用接口后,如果接口没有准备好数据,那么这个进程会被挂起,直到有数据返回时唤醒。
  • 非阻塞:就是进程调用接口后,如果接口没有准备好数据,进程也能处理后续的操作。但是需要不断的去轮询检查数据是否已经处理完成。

同步异步针对的是,如果接收不到数据,当前代码逻辑的状态。
阻塞非阻塞针对的是,如果接收不到数据,当前进程的状态。


17、map、unordered_map和set、unordered_set的区别

 mapunordered_mapsetunordered_set
底层实现红黑树哈希表红黑树哈希表
存储结构K-VK-VKK
迭代器可修改V 不可修改 
下标操作支持(K作为下标) 不支持 
查找复杂度O(logN)O(1)O(logN)O(1)
哈希冲突   链地址法
遍历打印顺序逆序顺序逆序
  • map使用[]将K值作为下标去执行查找,若K值不存在,则插入一个具有该K值和默认V值的元素至map中,对于不需要查找时插入的情况,应尽量使用find。
  • 解决哈希冲突:开放地址法、链地址法、再哈希法、建立公共溢出区。

 


18、一致性哈希:常用于负载均衡       

  1. 随机策略:无论是时间效率还是空间效率都非常不好。                   
  2. hash取模:容错性(服务器不可用)和扩展性(增加服务器)不好。
  3. 一致性哈希:服务节点的增减不会造成大量哈希重定位

一致性哈希:

①将整个哈希值空间组织成一个虚拟的圆环;

②下一步将各个服务器使用H进行一个哈希;

③将数据使用相同的H计算出哈希值h,确定此数据在环上的位置,顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。

④服务节点太少的时候,分布不均匀,采用虚拟节点技术;

 https://www.cnblogs.com/moonandstar08/p/5405991.html

https://leileiluoluo.com/posts/consistent-hashing-and-high-available-cluster-proxy.html


19、STL迭代器

  • 为什么有了指针还要有迭代器?

        迭代器提供了一种方法顺序访问一个聚合对象的各个元素,而不需要暴露该对象的内部结构;

  • 迭代器与指针的区别?

        迭代器是类模板,表现地像指针。模拟了指针的一些功能,重载了->、*、++、--等。

  • 迭代器如何删除元素?

        对于顺序容器vector、deque来说,使用erase后,后边的每个迭代器都会失效,后边的每个元素都会往前移动一个位置,erase返回下一个有效的迭代器。

        对于关联容器map、set来说,使用erase后,当前的迭代器失效,但其结构是红黑树,删除当前元素不会影响到下一个元素的迭代器,所以在调用erase之前记录下一个元素的迭代器即可。

        对于list来说,它使用了不连续分配的内存,以上两种方法都可以。


20:类成员的访问权限

  • public:可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问;
  • protected:可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问;
  • private:只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问; 

水平控制权限:

类中属性publicprotectedprivate
内部可见性
外部可见性××

 

垂直控制权限:   

基类属性publicprotectedprivatepublicprotectedprivatepublicprotectedprivate
继承方式publicprotectedprivate
内部可见性publicprotected不可见protectedprotected不可见protectedprotected不可见
外部可见性××××××××

派生类对基类成员的访问形式主要有以下两种:

①内部访问:由派生类中新增的成员函数对基类继承来的成员的访问。

②外部访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问。

https://blog.csdn.net/weixin_42018112/article/details/82427071


21、四种cast转换

static_cast          dynamic_cast           const_cast           reinterpret_cast

static_cast:用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;

dynamic_cast:用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

const_cast:用于将const变量转为非const

reinterpret_cast:几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;

-----为什么不使用C的强制转换?

C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

https://www.cnblogs.com/ider/tag/C%2B%2B/

 


22、多态

  • 静态多态(编译期绑定):通过模板和函数重载来实现;
  • 动态多态(运行期绑定):通过继承、虚函数、指针来实现;

23、指针和const组合用法

对于const与指针组合在一起,可以将const理解为内容,*理解为指针,誰在前面谁不能改变,誰在前面先读谁

  • const int *a与int const *a:const在前面所以内容不可以改变,但是指针指向可以改变。也就是常量指针 ;
  • int *const a:表示的是指针指向不可改变,但是指针所存放的内容可以改变,也即是指针常量;
  • const int &ri=i:定义的是引用是另一变量的别名,它本身就是一个常量,也就是说不能再让一个引用成为另一个变量的别名。 我们不能通过ri去改变它所代表的内存区域;

ps:

        const作用:①用于定义常量 ②保护传参时参数不被修改 ③节约内存空间(只分配一次内存,define分配多次)④类中使用const修饰函数防止修改非static类成员变量⑤修饰指针⑥修饰函数返回值⑦修饰类的成员变量⑧限制作用域(本文件使用)


24、类String的构造函数、析构函数和赋值函数

//普通构造函数
String::String(const char *str) {
	if(str==NULL) {
		m_data = new char[1]; // 对空字符串自动申请存放结束标志'\0'的空
                              //对m_data加NULL 判断
		*m_data = '\0';
	} else {
		int length = strlen(str);
		m_data = new char[length+1];
		strcpy(m_data, str);
	}
}
//String的析构函数
String::~String(void) {
	delete [] m_data; // 或delete m_data;
}
//拷贝构造函数
String::String(const String &other)     { //输入参数为const型
	int length = strlen(other.m_data);
	m_data = new char[length+1];
	    
	strcpy(m_data, other.m_data);
}
//赋值函数
String & String::operator =(const String &other) { // 输入参数为const型
	if(this == &other)   //检查自赋值
		return *this;
	delete [] m_data;
	    //释放原有的内存资源
	int length = strlen( other.m_data );
	m_data = new char[length+1];
	 
	strcpy( m_data, other.m_data );
	return *this;          //返回本对象的引用
}

25、STL的allocaotr

        new 一个对象的时候两个动作是:先调用::operator new 分配一个对象大小的内存,然后在这个内存上调用构造函数构造对象。同样,当你 delete 一个对象的时候两个动作是:先调用析构函数析构掉对象,再调用::operator delete将对象所处的内存释放。为了精密分工,STL 将allocator决定将这两个阶段分开。分别用 4 个函数来实现:

  1.内存的配置:alloc::allocate();

  2.对象的构造:::construct();

  3.对象的析构:::destroy();

  4.内存的释放:alloc::deallocate();

        考虑到小型区块可能造成内存破碎问题(即形成内存碎片),SGI STL 设计了双层级配置器。第一层配置器直接使用malloc() 和 free().第二层配置器则视情况采用不同的策略:但配置区块超过 128 bytes时,调用第一级配置器。当配置区块小于 128 bytes时,采用复杂的 memory pool 方式。

        SGI 第二层配置器定义了一个 free-lists,这个free-list是一个数组,数组的元素都是指针,用来指向16个链表的表头(16个链表上分别挂着大小为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块)。

        SGI 第二层配置器两个重要函数:ROUND_UP():申请的内存字节数上调为8的倍数;refill():没有可用的free list时调用。

PS:malloc的原理

        Malloc函数用于动态分配内存。为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址。

        当进行内存分配时,Malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。

        Malloc在申请内存时,一般会通过brk或者mmap系统调用进行申请。其中当申请内存小于128K时,会使用系统函数brk在堆区中分配;而当申请内存大于128K时,会使用系统函数mmap在映射区分配。

https://www.cnblogs.com/zhuwbox/p/3699977.html


26、内存泄漏

内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的分类:

1. 堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.

2. 系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

3. 没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。


27、new 和 malloc的区别

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

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

3、new不仅分配一段内存,而且会调用构造函数,malloc不会。

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

5、new是一个操作符可以重载,malloc是一个库函数。

6、malloc分配的内存不够的时候,可以用realloc扩容。new没用这样操作。

7、new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。

8、申请数组时: new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。


28、模板及可变参数模板

  • 函数模板:针对仅参数类型不同的函数;
//例子
template<typename T> void swap(T& a,T& b){};
template<classT> T swap(T& a,T& b){};
  • 类模板:仅数据成员和成员函数类型不同的类;
//例子
template<class T> class A {
	public:
		T a;
		T b;
		T hy(T c, T &d);
};
使用模板类创建对象:A<int> m

c++11新特征:可变参数模板

//例子
Template<class ... T> void func(T ... args) {
		cout<<"num is"<<sizeof ...(args)<<endl;
}

通过递归来实现真正的可变长:

#include<iostream>
using namespace std;
double Sum2() { // 边界条件
	return 0;
}
template<typename T1, typename... T2> double Sum2(T1 p,T2...arg) {
	double res = p + Sum2(arg...);
	return res;
}
int main() {
	cout<<Sum2(1.0,2.0,3.0,4.0)<<endl;
	cout<<Sum2()<<endl;
	return 0;
}

 


29、右值引用

c++中引入了右值引用移动语义,可以避免无谓的复制,提高程序性能。

  • 如何区分左值和右值:看能不能对表达式取地址,如果能,则为左值,否则为右值
  • 右值引用使用的符号是&&。
//例子:
int&& a = 1;
int b = 1;
int && c = b; //编译错误! 不能将一个左值复制给一个右值引用

        通过右值引用,该右值又重获新生,其生命期将与右值引用类型变量a的生命期一样,只要a还活着,该右值临时变量将会一直存活下去。实际上就是给那个临时变量取了个名字。

        注意:这里a类型是右值引用类型(int &&),但是如果从左值和右值的角度区分它,它实际上是个左值。因为可以对它取地址,而且它还有名字,是一个已经命名的右值。

https://www.jianshu.com/p/d19fc8447eaa


30、lambda

#include <algorithm>
#include <cmath>
using namespace std;
//例子1
void abssort(float *x, unsigned N)
{
  std::sort(x,
            x + N,
            [](float a, float b) { return abs(a) < abs(b); });
}

//例子2:自动推断返回类型
std::cout << [](float f) { return std::abs(f); } (-3.5);
//例子3:指定返回类型
std::cout << [](float f) -> int { return std::abs(f); } (-3.5);
//例子4:以传值的形式捕获同范围内的变量
float f0 = 1.0;
std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);
//例子5:以传引用的形式捕获同范围内的变量
std::cout << [&](float f) { return f0 + std::abs(f); } (-3.5);
//例子6:以混合的形式捕获同范围内的变量
float f0 = 1.0f;
float f1 = 10.0f;
std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
std::cout << '\n' << f0 << '\n';

https://www.cnblogs.com/langzou/p/5962033.html

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值