c++复习笔记
1、堆和栈的区别与联系
1、栈区由编译器自动分配内存释放;堆区一般由程序员手动分配释放,若不释放,结束时可能由OS回收
2、堆的分配可能会有碎片,栈不会有这个问题
3、堆都是动态分配的,栈有动态和静态两种分配方式
4、栈的效率比堆高(所以现在很多带gc的语言的一个优化方向就是尽量把内存分配在栈上,这样gc速度快效率高)
5、栈一般比堆小得多
2、内存池
在c++中,内存分成5个区,分别是堆、栈、自由存储区、全局/静态存储区和数据常量区
内存池可以用vecor<char*>实现,发现为空时分配固定大小的内存
3、重载和覆盖
重载是指不通函数使用相同的函数名,但函数的参数个数、类型或顺序不同。调用是根据函数的参数来区别不同的函数。仅仅是返回值不一样是构不成函数重载的。
覆盖是指派生类中重新对基类的虚函数重新实现,函数名和参数都一样,只是实现不一样。覆盖也叫重写。
4、多重继承是什么,优缺点
多重继承指的是一个类别可以同时从多于一个父类继承行为与特征
优点:可以继承多个类的行为,使代码更紧凑。
缺点:出现二义性,虚函数、访问权限、钻石继承等问题,容易产生混淆。
二义性问题:派生类和继承的几个基类中都有某个变量,在调用时要显式调用,比如c.A:m_var = 1;
钻石问题: 派生类中继承的几个父类可能继承自同一个基类,这时候需要保证构造派生类的时候基类只有一个对象被创建,这时就需要在定义派生类的时候加虚拟继承class Tiger : virtual public Animal { /* ... */ };
5、不用第三个变量交换两个int型的值
a = a + b;
b = a - b;
a = a - b;
或者用异或来实现:
a = a ^ b;
b = a ^ b;
a = a ^ b;
6、不用判断和比较语句求绝对值
其实就是位运算:a*(1-((a>>31)*2));
7、写一个程序,堆可以访问,栈不能访问;再写一个栈可以访问,堆不能访问
把构造函数设为private,就不能声明这个类的局部变量了,这样栈就不能访问了
把operator new设为private,就不能调用new来创建这个类的对象了,比如void* operator new (size_t);
8、tcp三次握手
1、建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
2、服务器收到syn包,必须确认客户的SYN(ack = j + 1),同时自己也发送一个SYN包(syn = k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
3、客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack = k + 1),此包发送完之后,客户端和服务器就进入ESTABLISHED状态,完成三次握手
9、线程池的线程一般开多少个合适
内网服务器之间的通信,一个线程足矣,网关的话最多和cpu核数相等。
线程池有一个“阻抗匹配原则”:T = C/P
T是线程的个数,因为P不一定准确,T的值可以上下浮动50%
C是系统cpu核心数,或者说是“分配给当前任务的CPU数目”。
P是线程执行任务时密集计算所占的时间比重,是一个百分比的值
10、进程间通信的方式
1、管道:半双工、数据单向流动、进程需要有亲缘关系(通常是父子关系)
2、命名管道:半双工、允许无亲缘关系的进程通信
3、消息队列:消息的链表、存放在内核中并由消息队列标识。克服了信号传递信息少、管道只能承载4、无格式字节流以及缓冲区大小受限等缺点。
5、信号量:是一个计数器,用来控制对共享资源的访问。常作为一种锁机制。
6、信号:信号是一种比较复杂的通信方式,用于通知接收某个事件已经发生。
7、共享内存:映射一段能被其他进程所访问的内存,这段内存由一个进程创建 ,但多个进程都可以访问。是最快的IPC方式,针对其他进程间通信方式效率低而设计。
8、套接字通信:可用于不同机器间的进程通信。
11、同步和互斥的关系?方式?
1、互斥:间接相互制约。一个线程在使用资源的时候其他线程都要等待。
2、同步:直接相互制约。一个线程在操作完某个数据给其它线程使用之前,其他使用该数据的线程都要等待。
3、同步包括互斥,互斥其实是一种特殊的同步。
方式:
1、关键段CS 关键段可以用于线程间的互斥,但不可以用于同步。因为有所有权的线程不受约束。关键段不能跨进程使用。
2、互斥量mutex 是内核对象,可以跨进程使用。互斥量和关键段一样都有线程所有权的概念。所以互斥量不能用于线程间的同步。不过因为有所有权的概念,互斥量可以处理“遗弃”的情况,即拥有资源的线程没有释放互斥量就终止了,这时候系统会自动释放互斥量。
3、事件 是内核对象,可以跨进程使用。事件可以解决同步和互斥问题。
4、信号量 是内核对象,可以跨进程使用。当前资源数量大于0,表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发
12、c++虚函数表
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
3)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
4)多重继承时每个父类都有自己的虚表。子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
5)任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。
6)子类虚函数是private时,基类指针可以调用到,这样就是强制程序员通过基类指针使用子类的虚函数方法。
13、网络通信API
若成功则为0,出错则为-1
服务端:
创建套接字 int socket(int family, int type, int protool);
绑定套接字 int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
监听套接字 int listen(int sockfd, int backlog); backlog根据系统的实现不同而不同,和未完成连接队列和已完成连接队列有关
等待套接字连接 int accept(int sockfd, struct sockaddr* cliaddr, socklen_t* addrlen);
客户端:
创建套接字 int socket(int family, int type, int protool);
请求连接 int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
发送数据 ssize_t send(int sockfd, void* buff, size_t nbytes, int flags);
接收数据 ssize_t recv(int sockfd, const void* buff, size_t nbytes, int flags);
关闭连接 int close(int fd); 有引用计数,要马上发fin的话得用shutdown()
IO复用:
int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptest, const struct timeval *timeout);
若有就绪描述符则返回其数目,若超时则为0, 若出错则为-1
使用描述符集,通常是一个整数数组,每个整数中的每一位对应一个描述符。用四个宏来控制。maxfdpl的值是待测试的最大描述符加1.
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
计时器可以精确到毫秒级
epoll
int epoll_create(int size); size用来告诉内核这个监听的数目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数events用来从内核得到事件的集合
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.LT 是 select/poll 使用的触发方式,比较低效;而 ET 是 epoll 的高速工作方式
14、STL使用过哪些,底层结构,哪些会有迭代器失效的情况,怎么处理?
vector 可扩容数组,动态增长。在用reserve申请大小时总是按指数边界来增大其内部缓冲区,动态重新分配时一般分配当前大小的1.5~2倍的新内存区。对vector的任何操作,一旦引起空间重新配置,原来的迭代器就失效了。
list 链表 list底层是一个环状双向链表,它的随机存取非常没有效率。
deque 双向链表 deque是动态地以分段连续空间组成,用map存储所有的内存指针
map 键值映射关系 所有元素都会根据元素的键值自动被排序。map 的所有元素都是 pair,同时拥有实值(value)和键值(key)。map底层是红黑树实现。红黑树的自动排序效果很好。
multimap 的特性以及用法与 map 完全相同,唯一的差别在于它允许键值重复。
在c++11以后,所有的size方法复杂度都是常量。
15. STL的代码从广义上讲分为三类:algorithm(算法)、Container(容器)和iterator(迭代器),各自的作用和之间的关系
容器:是一个保存其他对象的对象。可以存放任何类型的对象,是对数据结构的一种抽象,以类模板的形式实现而成。
算法:算法作用于容器,定义了对容器内容进行操作的方法,是对函数的一种抽象,采取函数模板实现。
迭代器:是类似指针的对象,提供了一种使容器与算法协同工作的机制。一个容器可以生成一对迭代器来定制一个元素系列。而算法是对该系列进行操作。采用这种方式,容器和算法可以紧密地协作,同时还可以保持彼此“不知情”。
16、什么是多态?如何实现多态?
多态是具有多种形态的能力的特征,在OO中是指,语言具有根据对象的类型以不同的方式处理。
实现方法:(1)c++的虚函数机制是按绝对位置/偏移位置查表
(2)smalltalk等语言的机制是按函数名称查表,这种有时要遍历所有继承结构,效率不是很高
17、placement new
placement new是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替
它的原型如下:
void *operator new( size_t, void *p ) throw() { return p; }
new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new,而不是new和delete。
MyClass * pClass=new(buf) MyClass;
对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。pClass->~MyClass();
显示调用构造函数的方法:
第一:pMyClass->MyClass::MyClass();
第二:new(pMyClass)MyClass();
18、指针和引用之间存在三大区别:
2.所有引用都要初始化;
3.一个引用永远指向用来对它初始化的那个对象。
4.指针指向一个对象的地址;引用是一个对象的别名。
5.指针可以改变指的对象;引用一旦指定某个对象就不能改变。
6.指针可以为空;引用不可为空。
19、X* const表示的是指向X的常量指针,本身的值不能被修改,指向的值可以被修改
const X*表示的是指向常量X的指针,本身的值可以被修改,指向的值不能被修改
20、仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符,比如:
class Func{
public:
void operator() (const string& str) const {
cout<<str<<endl;
}
};
Func myFunc;
myFunc("helloworld!");
>>>helloworld!
仿函数可以不带痕迹地传递上下文参数。而回调技术通常使用一个额外的void*参数传递。这也是多数人认为回调技术丑陋的原因。
21、怎么防止头文件被重复声明?
1、#pragma once
2、#ifdef XXX_H
#define XXX_H
#endif
22、c++的类给我们默认提供了哪些函数?
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符 需要的时候才提供
const Empty* operator&() const; // 取址运算符 const 需要的时候才提供
};
默认是inline和public的
23、线程和进程有什么区别和联系?
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.