[C/C++]linux下c-c++语法知识点归纳和总结
1、c/c++申请动态内存
在c++中,申请动态内存是使用new和delete,这两个关键字实际上是运算符,并不是函数。
而在c中,申请动态内存则是使用malloc和free,这两个函数是c的标准库函数,使用它们必须包含stdlib.h,才能编译通过。
new/delete和malloc/free的相同之处在于,new和malloc都是手动申请动态内存,释放时new则需要delete释放内存,而malloc则需要free释放内存。
它们的不同之处在于,new和delete会自动调用对象的构造和析构函数,而malloc/free则只申请内存。
另外需要注意的是:new的不是数组的话,则直接delete就好,并且只会调用一次析构函数,而new[]的话,则需使用delete[]来释放,并且数组中每一个元素都会调用一次析构函数,调用完析构函数再释放内存。
2、c++继承的优缺点
优点:类继承是在编译时刻静态定义的,且类继承可以较方便地改变父类的实现,实现函数的重用。
缺点:首先,因为继承在编译时刻就定义了,所以无法在运行时改变从父类继承的实现,其次,父类一般至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为,也就是说,如果继承下来的实现不适合子类的问题,那么父类必须重写或者被其他的类替换,这种依赖关系限制了灵活性。
从以上对比看,同一种属性既可以是优点,从另外的方面来讲,又是缺点,就看个人在编程过程中的灵活运用了。
3、c++的三大特性
封装、继承、多态。
封装是一种技术,它使类的定义和实现分离;
继承,从广义上讲,继承有三种实现方式,其一,为实现继承,指使用基类的属性和方法而无需额外编码,其二,可视继承,即子窗体使用父窗体的外观和实现代码,其三,则为接口继承,即仅仅继承属性和方法,实现则滞后到子类去实现,也就是父类使用的是纯虚函数,或者重写父类接口方法,则是虚函数,例如多态的实现就使用了接口继承。
多态,简单来讲,就是父类定义了虚函数,子类重新实现该函数,那么当父类指针指向子类时,会调用子类的该方法,这,就是多态。
4、子类和父类调用构造函数和析构函数的先后顺序
子类对象定义时,先调用父类的构造函数,再调用子类的构造函数;
子类对象销毁时,先调用子类的析构函数,再调用父类的析构函数。
5、什么是引用
引用,其实就是给变量取了一个别名,声明引用时要切记初始化,且引用本身不占存储单元,纯粹就是变量多了一个名称而已。
6、将引用作为函数参数有哪些特点
一是,使用引用传递参数是直接对实参本身进行操作,当需要在函数内部修改传递进来的变量并传出去时,可使用引用;
二是,作为函数参数时,引用是无需重新分配存储空间的,那时引用只是作为别名使用,但指针却需要,所以有时使用引用会更有效率;
7、什么时候需要使用常引用
当既要使用引用提高程序的效率,又不能在函数内部修改实参的值时,可使用常引用。
8、将引用作为函数返回值类型的好处和需遵循的规则
好处:在内存中不产生被返回值的副本
需遵循的规则:
(1)不能返回局部变量的引用;
(2)不能返回函数内部动态分配的变量的引用,因为引用只是作为一个临时变量的出现,并未赋予一个实际的变量,该引用所指向的空间无法被释放;
(3)可以返回类成员的引用,但最好是const类型;
(4)为了保证连续使用流操作符(<< >>)重载返回值时,操作的是同一个对象,流操作符重载返回值应该声明为引用
(5)+-*/这四则运算符不能返回引用
9、引用和多态的关系
引用是c++中另外一种实现多态的手段,与指针一样,也是基类的引用可指向派生类的实例。
10、引用和指针的区别
指针通过某个指针变量指向某个对象后,对指针所指向的对象间接操作;
引用本身就是变量的别名,所以对引用操作就是直接对所指向的变量进行操作;
引用不会重新分配存储空间,但指针却需要重新分配存储空间;
11、关联、聚合、组合的区别
主要是在画uml类图时,有关联、聚合和组合的说法。
关联,是一种很弱的联系,指的是两个类之间有某种联系,比如一个类实例作为另一个类方法的参数;
聚合,指的是整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构。从而找出一些组成类,该整体类和组成类之间就形成了聚合关系。例如一个航母编队包括海空母舰、驱护舰艇、舰载飞机及核动力攻击潜艇等。需求描述中“包含”、“组成”、“分为…部分”等词常意味着聚合关系;
组合,也表示类之间整理和部分的关系,但是组合关系中部分和整体具有统一的生存期,一旦整体对象不存在,部分对象也将不存在;
聚合和组合的区别:就是聚合关系中部分事物和整体事物生存期无关,举个浅显的例子,国和家,国没了,家也就不存在了,这是组合关系,而计算机和它的外设之间,计算机没了,硬件设备还存在,这是聚合关系。
12、多态(也叫重写和覆盖)的作用
隐藏实现细节,使得代码能够模块化,进而扩展代码模块,实现代码的重用;
在继承的同时,每一个派生类都有属于自己的独特的方法,实现接口的重用。
13、什么情况下只能使用类构造函数初始化表而不能赋值
当类中含有const、reference(引用)成员变量时,类的构造函数都需要初始化表。
14、c++是否是类型安全的
不是,c++是可以进行强制类型转换的。
15、main函数执行以前会执行什么代码
全局对象的构造函数会在main函数之前执行。
16、描述内存分配方式以及它们的区别
一是从静态存储区域分配,内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,例如全局变量和static变量;
二是从栈上创建,一般是局部变量在栈上创建,当超过该变量的作用域时该变量被自动释放;
三是从堆上手动分配,一般动态分配内存都是在堆上创建。
17、struct和class的区别
struct的成员默认是公有的,class的成员默认是私有的;
当类中有很少的方法并且有公有数据时,应该使用struct关键字,否则使用class关键字。
18、在8086汇编下,逻辑地址和物理地址是怎样转换的
通用寄存器给出的地址,是段内编译地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。
19、请说出const和#define相比,有什么优点
const常量有数据类型,而宏定义没有数据类型;
部分调试工具可以对const进行调试,但是宏常量不行。
20、简述数组和指针的区别
数组要么在静态存储区创建,要么在栈上创建,指针可以随时指向任意类型的内存:
- 修改内容上的差别:
char a[] = “hello”;
a[0] = ‘X’;//正确,编译和运行都不报错
char *p = “hello”;//注意p指向常量字符串
p[0] = ‘X’;//不正确,编译不报错,运行时报错
(2)sizeof计算容量时,数组得出字节数,指针则是4个字节
21、类成员函数的重载、覆盖和隐藏的区别
重载即为函数重载,重载的特征:
(1)相同的范围,也就是在同一个类中
(2)函数名字相同
(3)参数不同
(4)virtual关键字无影响
覆盖是指派生类函数覆盖基类函数,覆盖的特征:
(1)不同的范围,即函数分别位于派生类和基类
(2)函数名字相同
(3)参数相同
(4)基类函数必须有virtual关键字
隐藏是指派生类的函数屏蔽了与其同名的基类函数,特征如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同,此时不论有没有virtual关键字,基类的函数都将被隐藏
(2)如果派生类的函数与基类的函数同名,参数也相同,但是基类函数没有virtual关键字,此时,基类的函数将被隐藏
总结:函数名相同,参数也相同的情况下,如果基类函数有virtual关键字,则是多态,否则就是隐藏;函数名相同,参数不同的情况下,如果函数位于同一个类中,则是重载,否则就是隐藏。
22、main主函数执行完毕后,是否可能会再执行一段代码
atexit函数,是注册终止函数,即main执行结束后调用的函数,注册以后函数将由exit函数自动调用,其中atexit注册的函数类型应该是不接受任何参数的void函数,exit调用这些注册函数的顺序与它们登记时候的顺序相反。
另外,atexit函数是包含在stdlib.h库中。
23、代码中特殊的注释技术-TODO, FIXME 和 XXX
TODO:如果代码中有该标识,说明在标识处有功能代码待编写, 待实现的功能在说明中会简略说明;
FIXME:如果代码中有该标识,说明标识处代码需要修正,甚至代码是错误的,不能工作,需要修复,如何修正会在说明中简略说明;
XXX:如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进,要改进的地方会在说明中简略说明。
24、代码中连用两个感叹号
c代码中连用两个感叹号表示非非,如果是0,那么还是0,如果原来是非0,则变为1.
25、友元类的说明
友元关系不能被继承,友元关系是单向的,友元关系不具有传递性。
26、多态的内部逻辑
(1)从包含虚函数的类派生一个类时,编译器就为该类创建一个VTABLE,其每一个表项是该类的虚函数地址;
(2)在定义该派生类对象时,先调用其基类的构造函数,然后再初始化VPTR,最后再调用派生类的构造函数(从二进制的视野来看,所谓基类子类是一个大结构体,其中this指针开头的四个字节存放虚函数表头指针,执行子类的构造函数的时候,首先调用基类构造函数,this指针作为参数,在基类的构造函数中填入基类的vptr,然后回到子类的构造函数,填入子类的vptr,覆盖基类填入的vptr,如此一来完成vptr的初始化)
(3)在实现动态绑定,不能直接采用类对象,而一定要采用指针或者引用,因为采用类对象传值方式,有临时基类对象的产生,而采用指针,则是通过指针来访问外部的派生类对象的vptr来达到访问派生类虚函数的结果。
27、虚析构函数的作用
只有当一个类被用作基类时才需要使用虚析构函数,这样做的作用是当一个基类的指针删除派生类的对象时,能确保派生类的析构函数会被调用。因为编译器它只知道基类指针,调用基类析构,并不会主动去调用派生类的析构函数,所以基类析构函数需为虚析构函数,这就相当于析构函数的多态。
28、#ifndef 和 #pragma once的区别
#ifndef是手动定义宏名来避免冲突,但#pragma once是编译器提供保证。
#ifndef是依赖于宏的名字不能起冲突,可以保证同一个文件不会被包含多次,但缺点是如果不同头文件的宏名不小心撞车了,可能就会导致头文件命名存在,但编译器却报找不到声明的情况。
#pragma once由编译器自动提供保障,同一个物理上的文件不会被包含多次,就是内容相同,但只要是两个文件,都会分别包含,但如果一个头文件被拷贝了多份,这种方法就不能保证文件不被重复包含。
#ifndef语法移植性好,#pragma once可以避免名字冲突。
29、__declspec(dllexcept) 和 __declspec(dllimport)
#ifdef ASIMOVLIB_EXPORTS
#define ASIMOVLIB_API __declspec(dllexport)
#else
#define ASIMOVLIB_API __declspec(dllimport)
#endif
__declspec(dllexport):
声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用。一般用于dll中省掉在DEF文件中手工定义导出哪些函数的一个方法。当然,如果你的DLL里全是C++的类的话, 你无法在DEF里指定导出的函数,只能用dllexport导出类。
__declspec(dllimport):
声明一个导入函数,是说这个函数是从别的dll导入,我要用,一般用于使用某个dll的exe中。
不适用__declspec(dllimport)也能正确编译代码,但使用__declspec(dllimport)使编译器可以生成更好的代码。编译器之所以能生成更好的代码,是因为它可以确定函数是否存在于DLL中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨DLL边界的函数调用中。但是,必须使用__declspec(import)才能导入DLL中使用的变量。
30、explicit关键字的作用
explicit用来防止由构造函数定义的隐式转换,比如:class Base base=10;即Base类只有一个int类型的变量,explicit使用了以后,就不允许这样写。
被声明为explicit的构造函数通常比非explicit的构造函数更受欢迎,因为它们禁止编译器执行非预期的类型转换。除非我有个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit。
31、printf输出时在%和字母之间插入数字表示场宽的规则?
当实际长度不够时, 右对齐;
如果字符串或者整数的长度超过说明的场宽, 则按其实际长度输出;
如果是浮点数, 若整数部分超过了说明的整数位场宽, 则按其实际长度输出, 若是小数部分超过了说明的小数位场宽, 则按说明的宽度以四舍五入输出。
32、逗号表达式?
例如:printf(“%d %d %d\n”, (a,b,c),b,c);
那么将输出才c,b,c这3个值,因为逗号表达式的值就是表达式中最后一个表达式的值;
即:表达式1,表达式2,表达式3…表达式n 就是表达式n的值。
33、c语言中标识符第一次字符必须是什么?
第一个字符必须是字母或者下划线,不能是数字。
34、数据流程图(DFD图)是什么
DFD图是结构化方法的需求分析工具。
35、hash_set和set的区别?
hast_set以hashtable为底层机制,而set以RB-tree(红黑果树)为底层机制;
set有元素自动排序功能,而hash_set没有;
set可在logN下完成查找、插入和删除等操作,hash_set可在常数时间复杂度下完成这些操作,但是取决于哈希表的负载情况;
hast_multiset则允许键值重复;
36、static的用途以及类中使用static的规则。
用途:
static限制变量的作用域;
static不显示的初始化时,会被隐式的初始化为0;
static设置变量的存储域,变量存储在静态区;
类中使用static的规则:
不能通过类名来调用类的非静态成员函数,可以调用静态成员函数;
类的对象可以使用静态成员函数和非静态成员函数;
类的静态成员函数中不能使用类的非静态成员,因为此时静态成员函数已经分配了存储空间,而非静态成员却还没有分配内存,相当于变量声明了但是未定义就直接使用;
类的非静态成员函数(包括常函数)可以使用类的静态成员函数和静态成员变量,并且非静态成员常函数可以修改静态成员变量;
类的静态成员变量必须初始化以后才能使用;
37、数据库三范式
- 第一范式:数据库表中的所有字段值都是不可分解的原子值,比如地址字段,根据需求拆分成省份和城市更方便
- 第二范式:在一个数据库表中,一个表中只能保持一种数据,不可以把多种数据保存在同一张数据库表中,比如订单信息和商品信息就要分为两个表
- 第三范式:每一列数据都和主键直接相关,而不能间接相关,就是说字段值要和主键有直接关系
巴斯-科德范式:第三范式的一个子集,在第一范式基础上,任何非主属性不能对主键子集依赖。
38、网络编程中设计并发服务器,使用多进程和多线程,请问有什么区别?
1)两者都可以提高程序的并发度,提高程序运行效率和响应时间
2)线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正相反。
39、可以用作switch的参数的类型
int、short、byte、char、long
基本上可以转换为整数的类型都可以用作switch的参数。
40、linux使用多线程的方法
互斥锁、信号量、条件变量、全局变量、读写锁。
- 互斥锁:当线程A锁定了互斥变量时,线程B再去锁定时就会被挂起,直到A解锁。
注意:当线程要不断的去轮询检查某个条件以判断是否可以操作需同步的数据时,可使用条件变量提高效率。
- 信号量:就是一个整数,两个线程对整数进行加减来实现信号量。
- 条件变量:经常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会解开相应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此变量阻塞的线程,这些线程将重新锁定互斥锁并重新测试条件是否满足。
pthread_cont_init()
pthread_cont_destroy()
pthread_cont_wait() //线程解开mutex指向的锁并被条件变量阻塞
pthread_cont_timedwait() //多了时间参数,当时间过了以后,即使条件变量不满足,阻塞也被解除
pthread_cont_signal()/pthread_cont_broadcast //唤醒被条件变量阻塞的线程。
- 读写锁:可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。
- 当读写锁是写加锁状态时,在这个锁被解锁前,所有试图对这个锁加锁的线程都会被阻塞;
- 当读写锁是读加锁状态时,其他线程可以读模式得到访问权,但是以写模式对它进行加锁的线程都将被阻塞;
- 当读写锁是在读模式加锁状态时,如果有其他线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,避免读模式锁长期占用,而写模式所长期阻塞;
读写锁适用于对数据读的次数比写的次数多的情况。
API接口:
初始化和销毁:
int pthread_rwlock_init();
int pthread rwlock_destroy();
读加锁、写加锁、解锁:
pthread_rwlock_rdlock();
pthread_rwlock_wrlock();
pthread_rwlock_unlock();
非阻塞获得读锁和写锁:
pthread_rwlock_tryrdlock();
pthread_rwlock_trywrlock();
41、linux系统进程间通信方式
管道、有名管道、信号量、消息队列、信号、共享内存、socket、文件
管道及有名管道:管道可用于具有亲缘关系进程间的通信,例如父子进程,但是有名管道允许无关系的进程间通信。管道其实就是建立一个FIFO文件,一个进程往里面写数据,另外的进程读取数据。
信号量:主要作为进程间以及同一进程不同线程之间的同步手段。
消息队列:也叫报文队列,消息队列是消息的链接表,包括Posix消息队列和SystemV消息队列,有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的信息,消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号:信号是比较复杂的通信方式,用于通知接收进程有某种事情发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信息语义函数signal外,还支持语义符合Posix 1标准的信号函数sigaction。
共享内存:共享内存是一种文件映射,使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其他通信机制,如信号量结合使用,来达到进程间的同步及互斥。
socket:也就是套接字,最普遍的进程间通信机制,可用于不同机器之间的进程间通信。
42、为什么要使用linux作为服务器。
首先,linux是免费的,而windows需要向微软购买正版授权;
其次,linux比windows灵活,可以实现很多定制化需求,因为linux可以修改系统内核;
再次,很多高端服务器组件对linux支持的更好,windows版本的可能功能都不是很完整;
最后,linux开源,所以很多人为它添砖加瓦,几乎你需要的功能都能找到linux的版本并且都是开源免费的。
43、mysql数据库的引擎
你能用的数据库引擎取决于mysql在安装的时候是如何被编译的。要添加一个新的引擎,就必须重新编译mysql,在缺省情况下,mysql支持三个引擎:ISAM、MYISAM和HEAP,另外两种类型INNODB和BERKLEY,也常常可以使用。
ISAM
ISAM是一个定义明确且历经时间考验的数据库表格管理方法,它在设计之时就考虑到数据库被查询的次数要远大于更新的次数。因此,ISAM执行读取操作的速度很快,而且不占用大量的内存和存储资源。ISAM的两个主要不足之处在于,它不支持事务处理,也不能够容错,如果你的硬盘崩溃了,那么数据文件就无法恢复了。如果你正在把ISAM用在关键任务应用程序里,那就必须经常备份你所有的实时数据,通过其复制特性,Mysql能够支持这样的备份应用程序。
MYISAM
MYISAM是MySQL的ISAM扩展格式和缺省的数据库引擎,除了提供ISAM里所没有的索引和字段管理的大量功能,MYISAM还使用一种表格锁定的机制,来优化多个并发的读写操作。其代价是你需要经常运行OPTIMIZE TABLE命令,来恢复被更新机制所浪费的时间。MYISAM还有一些有用的扩展,例如用来修复数据库文件的MYISAMCHK工具和用来恢复浪费空间的MYISAMPACK工具。
MYISAM强调了快速读取操作,这可能就是为什么MYSQL受到了WEB开发如此青睐的主要原因,在WEB开发中你所进行的大量数据操作都是读取操作。所以,大多数虚拟主机提供商和INTERNET平台提供商只允许使用MYISAM格式。
HEAP
HEAP允许只驻留在内存里的临时表格。驻留在内存使得HEAP比ISAM和MYISAM的速度都快,但是它所管理的数据是不稳定的,而且如果在关机之前没有进行保存,那么所有的数据都会丢失。在数据行被删除的时候,HEAP也不会浪费大量的空间,HEAP表格在你需要使用SELECT表达式来选择和操控数据的时候非常有用。要记住,用完表格后要删除表格。
INNODB和BERKLEYDB
INNODB和BERKLEYDB数据库引擎都是造就MYSQL的灵活性技术的直接产品,这项技术就是mysql++API。在使用mysql的时候,你所面对的每一个挑战几乎都源于ISAM和MYISAM数据库引擎不支持事务处理也不支持外来键。尽管要比ISAM和MYISAM引擎慢很多,但是INNODB和BERKLEYDB包括了对事务处理和外来键的支持,这两点都是前两个引擎所没有的。如前所述,如果你的设计需要这些特性中的一者或者两者,那你就要被迫使用后两个引擎中的一个了。
44、什么是红黑数?
红黑树,又叫RB树,是一种特殊的二叉查找树,可以自动排序,且红黑树的每个节点都有存储位表示节点的颜色,标识是红或者黑。
红黑树的特性:
每个节点或者是黑色,或者是红色;
跟节点是黑色;
每个叶子节点是黑色(这里叶子节点是指没有子节点的叶子节点);
如果一个节点是红色的,则它的子节点必为黑色的;
从一个节点到该节点子孙节点的所有路径上包含相同数目的黑节点。
45、C程序的内存分配方式
由上到下(地址从高到低):栈、动态链接库、堆、bbs(未初始化的全局变量)、数据段(存放初始化的全局变量)、文本段(存放代码)
46、select、poll、epoll的区别
select函数 int select(int n,fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);
第一个参数n代表最大的文件描述符+1
fd_set是一个集合,存放的是文件描述符
readfds表示我们要监视这些文件描述符里读变化
writefds表示我们要监视它所指向的集合里面的文件描述符的写变化
errorfds表示要监视文件描述符是否发生了错误异常
timeout若为NULL,则select置于阻塞状态,直到监视的某个文件描述符发生变化才返回
timeout若等于0秒0毫秒,则select是一个纯粹的非阻塞函数,不管文件描述符是否变化,立即返回。
timeout大于0时,则在timeout时间内阻塞,timeout时间内若文件描述符有变化则返回,如果超时则立即返回
返回值:负值 select发生错误
正值 有文件描述符发生变化
0 等待超时,没有可读写或者发生错误的文件描述符
poll
poll使用pollfd结构
epoll函数与select函数最大的不同在于它不会随着监听fd数目的增长而降低效率,并且select同时监听的fd数目是有限制的,默认最大是1024.
int epoll_create(int size);//创建epoll句柄,参数size告诉内核这个监听的数目有多大,但最新版本这个参数已经无用了
int epoll_ctl(); //epoll的事件注册函数,它要先注册需要监听的事件类型
int epoll_wait();//等待事件的发生
select是轮询fd,而epoll是先将文件描述符注册到内核,一旦文件描述符发生变化,内核会采用回调机制,激活这个文件描述符,这样epoll_wait便会知道。
epoll相对于select有点:
- 监视的文件描述符不受限制,具体多少根内存有关
- IO的效率不会随着监视fd的数量的增加而降低
- mmap加速内核与用户空间的信息传递,避免了多余的内存拷贝
47、linux下inode的说明
每个磁盘空间都有一个inode表,inode里面每一个节点存放该空间每个文件的信息,例如:文件的字节数、文件拥有者、所在的组、权限、时间、位置等。
istat 命令查看单个文件;
df –i查看磁盘空间inode的使用量。
48、IP、TCP、UDP数据包大小
MTU,普通局域网最大传输单元,为1500个字节;
IP数据包首部20个字节,所以IP数据包一般是1480个字节;
TCP数据包首部20个字节,所以数据包大小为1460个字节;
UDP数据包首部8个字节,所以数据包大小我1472个字节;
注意:这里说的首部是固定长度,但后面还有一些可选字段。
49、memcache的特性
- 服务器接到客户端请求会先检查memcached,如有则直接返回,不再检查DB
- 如memcache没有要找的数据,则检查DB,查到以后返回给客户端,并同时缓冲一份到memcache
- 更新DB同时更新memcache,保证数据的一致性
- 当分配给memcache内存空间用完之后,会根据LRU(最近最少使用)策略和到期失效策略,先替换失效数据,再替换最近最少使用数据
- memchche在内存中是一个巨大的hast表,它其实就是读数据库改为直接读内存,提高读取速度
50、什么是sql注入
sql注入:用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据。
应对办法:可以通过数据库防火墙实现对sql注入攻击的防范,因为sql注入攻击往往是通过应用程序来进攻,可以使用虚拟补丁技术实现,对注入攻击的sql特征识别,实现实时攻击阻断。
51、对称加密算法和非对称加密算法
对称加密才用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥,即加密密钥也可以用解密密钥,这就是对称加密算法,常见的有:DES、IDEA。
非对称加密算法需要两个密钥:公开密钥和私有密钥,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;反之,则用对应的公开密钥解密。非对称加密不适合对文件加密,只适合对少量数据进行加密,典型的应用是数字签名。
52、volatile关键字的作用
volatile变量是随时可能发生变化的,它告诉编译器,与volatile变量有关的运算,不要进行任何优化,每次读取volatile变量时都重新从内存读取。
53、c++中动态联编和静态联编
静态联编说的是在编译时就已经确定好了调用和被调用两者的关系;
动态联编说的是程序在运行时才确定调用者和被调用者的关系,典型的应用是虚函数实现的多态性。
54、深拷贝和浅拷贝
浅拷贝是指将对象内的数据完全一致的复制;
深拷贝不是这样,它可以将内部的数据按照需要用特殊的方法拷贝,比如说对象内部有一个动态数组,浅拷贝只拷贝指针,而深拷贝则重新申请空间将数据复制过来。
55、什么是柔性数组?
数组大小待定的数组就是柔性数组。
一般结构体的最后一个元素可以是大小未知的数组。
56、库函数和系统调用的区别?
库函数调用时面向应用开发的,与系统无关,移植性好。
系统调用是面向底层,偏向硬件,系统内核软中断实现,移植性差。
系统调用是用户程序和内核交互的接口,系统调用的过程如下:
- 执行用户程序
- 根据glibc的函数实现,取得系统调用号,并执行int$0x80产生中断
- 进行地址空间的转换和堆栈的切换,执行SAVE _ALL(进入内核模式)
- 进行中断处理,根据系统调用表调用内核函数
- 执行内核函数
- 执行RESTORE_ALL并返回用户模式
系统调用比库函数调用快。
57、构造函数里面”初始化列表”和”赋值”的区别
对于内置类型来说,没有区别,对于非内置类型则有区别,如下:
初始化列表只会调用一次构造函数,而赋值会先调用构造,再调用一次赋值函数。
什么情况下只能使用初始化列表:
没有默认构造函数的类;
类中存在const成员或者引用类型的成员(它们只能被初始化);
58、为什么要字节对齐?
自然对齐:一个变量的内存正好是它长度的整数倍。
需要字节对齐的根本原因在于CPU的效率问题,假设32位机器上int型变量地址是0x00000002,那么CPU取值时需要访问两次内存,一次是0x00000002-0x00000003的short,然后是0x00000004-0x00000005的short,而如果该int型变量的地址是0x00000003,那么CPU则要访问3次内存,即char-short-char,而如果变量是自然对齐的,则CPU访问一次内存就够了。
struct stu
{
char sex;
int length;
char name[10];
};
sizeof(struct stu) = 20;//结构体中每个数据类型都要对齐
什么情况下需要手动设置对齐:
- 设计不同CPU下的通信协议(设计一个结构体,32位和64位都用时)
- 编写硬件驱动程序时寄存器的结构
手动设置对齐方式有两种:
1)
#pragma pack(n)
#pragma pack()
2)
GNU编译时 #define GNUC_PACKED__attribut__((packed))
59、浅谈僵尸进程
僵尸进程:当子进程退出时,父进程没有调用wait函数或者waitpid()函数等待子进程结束,又没有显式忽略SIGCHLD信号,那么它将一直保持在僵尸状态,如果这时父进程结束了,init进程会自动接收这个子进程,为它收尸,但如果父进程是一个循环,不会结束,那么子进程就会一直保持僵死状态。
进程状态:
僵尸
休眠
不可中断的休眠
运行
停止式跟踪
Z
S
D
R
T
补救办法:杀死僵尸进程的父进程,让init进程来接手,清理掉子进程这个僵尸进程。
僵尸进程的状态:一个进程在调用exit()函数结束时,并没有真正的被销毁,而是留下一个称为僵尸进程的数据结构,僵尸进程放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保持一个位置,记载该进程的退出状态等信息。
避免僵尸进程的方法:
- 使用signal函数忽略SIGCHLD信号
- 调用wait或者waitpid()函数
- fork两次,父进程fork子进程后继续执行,子进程fork一个孙进程后退出,此时孙进程会被init进程接管,避免僵尸进程,当然子进程的退出还是要进行处理的。
60、fork函数浅析
pid_t fork();
返回值:若成功调用一次则返回两个值,子进程返回0,父进程返回子进程的进程id,否则,出错返回-1.
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意:子进程拥有的是副本,它跟父进程之间是不共享这些存储空间(但是共享代码段)的,因此子进程拥有独立的地址空间。
fork可能出错的原因:
当前进程数量已经达到了系统规定的上限;
系统内存不足;
61、for_each的用法?
for_each是C++STL中用来遍历容器的函数模板,有3个参数:
第一个是容器开始,例如:map.begin()
第二个是容器结束,例如:map.end();
第三个是operator(),仿函数,函数对象
当第三个函数有其他参数时,与bind1st和bind2nd一起使用。
例如:
void record(const int &n)
{
cout << n << endl;
}
int num [5] = {1,2,3,4,5};
for_each(num,num+sizeof(num)/sizeof(num[0]),record);
62、什么是事务以及事务包含哪些属性?
事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是不可分割的。
sql语言中,定义事务的语句有3条:
begin transaction;
… //这里嵌入sql语句
commit transaction;
rollback transaction;
事务的4个属性:
- 原子性:事务中的所有元素作为一个整体提交或回答,事务的每个元素是不可分的,事务是一个完整操作
- 一致性:事务完成时,数据必须是一致的,也就是不能破坏数据
- 隔离性:事务允许多个用户对同一个数据进行并发访问,而不破坏数据的完整性和正确性。同时,并行事务的修改必须与其他并行事务的修改相独立
- 持久性:事务结束后,事务处理的结果必须能够得到固化
63、c++强制转换
const_cast 用于去除const属性
static_cast 用于基本类型的强制转换
dynamic_cast 用于多态类型之间的类型转换
reinterpreter_cast 用于不同类型指针之间的转换,最常用的就是不同类型之间函数指针的转换
64、linux手动的让内核崩溃
cd /proc/sys/kernel
echo 1 > sysrq
cd /proc
echo c > sysrq-trigger
65、mutable关键字
mutable加在类型前面,表示即使是常量也可以修改
66、带外数据
OOB数据,也叫带外数据,send/sendto发送数据时以MSG_OOB标记。
带外数据只支持tcp,不支持udp
带外数据发送时不优先,接收时优先;
接收带外数据的系统会发送一个SIGURG信号
67、自定义类型作为map键时需注意什么?
- 重载 “<” 操作符,因为需要自动排序
- 无法重载时,用自定义仿函数代替map第三个参数
map 第一个参数是key
第二个参数是value
第三个参数是compare比较函数
第四个参数是内存配置对象
68、虚基类的用法
虚继承+多重继承时防止二义性问题。
从类A派生出B和C,类D又继承自B和C,此时类D的对象就包含了两个类A的对象,这样类D调用类A的成员变量和成员函数时就会产生二义性。
解决办法:B虚继承A,C也虚继承A,此时就不会再有二义性了。
69、析构函数能否为虚函数,为什么?什么情况下析构函数一定要是虚函数。
析构函数可以是虚函数,因为它是对象结束时才调用,不影响虚表构建。
构造函数不能是虚函数,因为调用构造函数时,虚表才建立。
当类有派生类时,析构函数要是虚函数。
删除指向派生类的基类指针时,只会删除基类对象,而不删除子类对象,造成内存泄露。
公有继承时,基类对派生类和对象的操作,只能影响那些继承下来的成员,如果要对非继承成员,例如子类析构函数进行操作,则这个函数必须定义为虚函数。
70、怎样防止类对象被拷贝和赋值?
- 拷贝构造函数和赋值函数定义为私有的
- 私有继承基类
71、能不能从构造函数调用虚函数,为什么?
可以的,只是虚函数会使用基类的虚函数。
72、什么时候可能会出现这种情况?设定的断点在main()函数的第一行,但是程序运行后没有执行到断点就崩溃退出了?
全局变量的构造函数崩溃。
73、如何处理一个析构函数失败,可以抛出一个异常?
最好不要抛出,如果一定要抛出,那要在析构函数内部处理。
74、如何处理构造函数失败?
抛出异常,若有动态分配内存,则要在抛异常之前手动释放。
75、friend违反封装原则了吗,为什么?
违反了,友元函数可以不受访问权限的限制而访问类的任何成员。
76、脏数据是怎么发生的?
数据被删除,但另一用户没有刷新,使用了该数据,这就是脏数据。
77、什么是模板的特例化?
模板的特例化是指将typename指定类型用一个已知类型替代了,用已知类型重新实现了模板函数和类,如果模板有2个参数,只实例化了一个参数,则是偏特化(局部特化)。
78、STL仿函数需要重载哪个操作符?
operator(),仿函数是指可以“使用小括号传递参数,来调用某个东西”。
79、c++中前置操作符和后置操作符有什么区别?哪个效率更高?
前置是先将自身改变再参与表达式运算,可以当作左值使用;
后置是先参与表达式计算再改变自身的值,返回的是右值,不能当作左值使用(i++=6 错误),前置效率高,因为后置会产生临时对象。
80、fork后父子进程的内存布局
调用fork之后,系统会为子进程建立父进程的副本,即子进程获得父进程数据空间、堆、栈的副本,父子进程之间并不共享这些存储空间部分,但父进程和子进程会共享正文段。
而因为现在fork以后通常会执行exec函数,所以现在一开始的时候子进程并没有完全获得父进程的存储空间的副本,作为替代,使用了"写时复制"技术,即这些区域开始由父进程和子进程共享,而且内核将它们的访问权限改为只读,如果父进程和子进程任一个试图修改这些区域,则内核会为修改区域的那块内存制作一个副本,通常是虚拟存储系统中的一个页。
调用fork()之后先执行哪个进程的是由Linux下专有文件/proc/sys/kernel/sched_child_runs_first的值来确定的(值为0父进程先执行,非0子进程先执行)。
81、unix中c程序的存储空间布局
c程序的存储空间通常由以下几部分组成:
1.正文段,也就是代码段,是由cpu执行的机器指令部分,正文段在存储器中只会有一个副本,并且正文段常常是只读的,以防止程序由于意外而修改其指令。
2.初始化数据段,也称为数据段,它包含了程序中需明确赋初值的变量(通常是函数外有初值的变量)。
3.未初始化数据段,也称为bbs段,在程序开始执行之前,内核将此段中的数据初始化为0或者空指针(通常是函数外没有初值的变量)。
4.栈。局部变量以及每次函数调用时所需保存的信息都存放在栈中。
5.堆。通常在此段中进行动态存储分配,一般来说,堆位于bbs段和栈之间。
程序执行以后才会产生堆和栈。
内核执行程序都会调用exec函数。
典型的程序逻辑布局:
地址从低到高:
正文段--》初始化数据段--》未初始化数据段--》堆--》栈
堆顶和栈顶之间会有很大的一段未使用的虚地址空间。
82、同步IO和异步IO
cpu执行的速度远远高于磁盘读写速度或者是网络传输速度,当代码执行遇到磁盘读写的时候,当前线程就会一直等待,直到磁盘读写完成返回后再继续执行下面的代码,这样就造成了cpu的资源严重浪费。
为了解决这种情况,我们可以使用多线程和多进程。
但是多进程开多了的话是很占用系统资源的,而多线程也是不能一直增加的,因为cpu要在线程之间切换来切换去,也是很耗费时间和资源的,这种情况下,我们可以使用异步IO。
异步IO,就是说当执行某段耗时的代码时,只发出指令,并不等待执行结果,然后就去执行其他代码,一段时间后,当结果返回时,再通知cpu进行处理。
那么根据上面的描述来讲,同步是阻塞的,异步是非阻塞的,异步IO需要一个消息循环,在消息循环中,主线程不断地重复读取消息,处理消息这一过程。
也就是说,在发出IO请求到IO返回结果的这一段时间里,同步IO模式下,主线程只能挂起等待,而异步IO模式下,主线程会继续去处理下一个消息,这样既没有占用过多的资源,也无需在线程之间进行切换,就会大大提升多任务处理程序的性能。
其实消息循环并不是说跳过io读写往下执行,而是不在等待,接着执行下一次循环,当IO读写完成后再接着往后执行。