c语言面试题

白水晶大约 27 分钟面试题面试题集合

1.变量的声明和定义的区别

变量定义:	为变量分配存储空间,还可以为变量指定初始值。程序中,变量有且只有一个定义
变量声明:	用于向程序表明变量的类型和名字。定义也是声明,当定义变量的时候我们声明了他的类型和名字。
			可以通过 extern 声明变量名而不定义它。extern 声明不是定义,它不分配存储空间。它只是告诉我们
			变量的定义在其他地方。程序中变量可以声明多次,但只能定义一次。

2.写出 bool、int、float、指针变量与 “零值”的比较的 if 语句

	if (flag)
		A;
	else
		B;
	
	if (0 != flag)
		A;
	else
		B;
	
	if (NULL != flag)
		A;
	else
		B;
	
	if (flag >= -NORM && flag <= NORM)
		A;
	else
		B;
/*特殊说明:float的表示方法的问题,float本身就是不精确的,精确度在小数点,float是 符号位+8bit的指数为+23bit的小数位组成的。那么这样就会有一个问题,如果数据中的
小数位超过了用来表示小数位的bit长度,就会有数据丢失,这个时候通常计算机会按照一定的规律进行转换得到一个非常接近的数值,例如13.765432有时候得到的值却是13.7654319。
这就是所谓的float类型不精确的原因。*/

3.sizeof 和 strlen 的区别

	sizeof:	是操作符	在编译的时候就计算出结果		参数可以是数据类型,也可以是变量	计算的是内存大小
	strlen:	是库函数	在程序运行的时候才计算出结果	参数是以'\0'结尾的字符串			计算的是字符串长度

4.static 关键字作用

C语言	static 关键字作用:
			1.修饰局部变量,相当于全局变量,延长了生命周期,直到程序运行结束后才释放。
			2.修饰全局变量,这个静态全局变量就只能在本文件中被访问,不能被其他文件访问,即使是 extern 外部声明也不行(全局变量可以被外部文件问)。
			3.修饰函数,这个函数就只能在本文件中被调用,外部文件不能被访问调用,外部文件的函数可以同名。
			static 修饰的局部变量存放在内存中的全局静态存储区
C++		static 关键字作用:
			1.修饰成员函数,静态成员之间可以相互访问,包括静态成员函数访问静态成员数据和访问静态成员函数。不能访问非静态成员函数和非静态成员数据。
				调用方法 类对象.静态成员函数  或者是类对象::静态成员函数。
				静态成员函数只属于类本身,随着类的加载而存在,不属于任何对象,是独立存在的。
			2.修饰成员数据,存储在全局静态区,静态数据成员定义时需要分配内存,所以无法在类声明中定义。不在类的声明中定义,是因为在类的声明中定义会
			导致每次类的初始化都包含该静态成员数据,与设计不符合。所有该类的实体对象(instance)都共用一个静态成员数据。
			3.protected 和 privated 修饰的静态函数无法被类外访问

5.malloc free 和 new delete 区别

C语言
	malloc、free 是库函数 	需要头文件支持	只是申请内存和释放内存,无法对自定义类型对象进行构造和析构
	malloc 	申请需要给定内存大小					成功返回 void * 需要强制转换		malloc 失败返回NULL	
	
new delete 是关键字		需要编译器支持	new 会先申请足够的内存(底层用malloc 实现),然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针
				delete 会先调用类型的析构函数,然后释放内存(底层是用free 实现)
				new		申请无需给定内存大小 编译器会自行计算	成功返回 对象类型指针 无需强制转换	new 失败会抛出异常

6.写一个标准宏 MIN

#define MIN(a, b) ((a) <= (b) ? (a) : (b))
//要注意这个情况
	int b = 2;		//b = 1
	int * p = NULL;
	p = (int *) malloc(sizeof(int));
   	*p = 1;
   	qDebug() << MIN(++*p, b); 			// p = 3 b = 2; p的值变化了2次
									// p = 2 b = 1; p的值变化了1次

7.一个指针可以是 volatile 吗

可以的,指针和普通变量一样,有时也有变化程序的不可控性。防止编译优化,每次编译都重新读值

8.a 和 &a 的区别

	int A[5] = {1,2,3,4,5};
    int *P = (int *) (&A + 1);			//P 的地址 :指向数组A的首地址 + sizeof(int) * 5

    qDebug() << A;			// 	0x44e9aff8d0
    qDebug() << P;			//	0x44e9aff8e4
    qDebug() << &A[0];		//	0x44e9aff8d0
    qDebug() << &A[4];		//	0x44e9aff8e0
    qDebug() << &A[5];		//	0x44e9aff8e4

    printf("%d  -- %d\n", *(A + 1), *(P - 1));		// 答案  -- 5
//	思考	int *P = (int *) (A + 1); 时 打印printf("%d  -- %d\n", *(A + 1), *(P - 1));是多少		2 -- 1

9.C语言内存分配

C语言	内存四区
			堆区	手动分配和释放 malloc/free 
			栈区	系统自动分配和释放,存放局部变量和函数参数
			全局区	程序运行时分配的内存
				细分:常量区 未初始化的全局数据区 初始化的全局数据区 静态变量也存放于此
			代码区	二进制代码存放的地方
堆和栈区别
		栈 自动分配和释放	速度快	效率高但无法控制			内存地址连续	系统设定好的最大容量,	 向低地址扩展
		堆 手动分配和释放	速度慢	效率低但能控制,容易产生碎片	内存地址不连续	系统用链表来存储空闲内存地址,大小受限于系统的有效虚拟内存			向高地址扩展

10.strcpy sprintf 和 memcpy 区别

	1.	操作对象不同:strcpy 是字符串,memcpy 可以是任意数据类型 sprintf 目的对象是字符串,但源对象可以任意基本数据类型
	2.	执行效率,memcpy 最高, strcpy 次之, sprintf 效率最低
	3.	strcpy : 字符串拷贝,不需要指定长度,它遇到 '\0' 结束符自动结束
		memcpy : 任意数据类型的拷贝,要指定长度,不会遇到 '\0' 结束符就自动结束
		sprintf: 用于拼接字符串和其他基本数据类型

11.设置地址为0x67a9的整形变量的值为0xaa66

	int *ptr;
	ptr = (int *)0x67a9;
	*ptr = 0xaa66;

12.数组和链表的区别

存储形式不同:数组是一块连续的空间,声明时就要确定数组长度, 链表是一块可以不连续的空间,每个节点会保存相邻节点的指针。
数据查找:数组线性查找速度快,查找操作直接用偏移量就行。链表需要按顺序检索节点,效率较低。
数据插入或删除:链表可以快速删除和插入节点,而数据需要移动大量数据进行插入和删除。
越界问题:数组有越界行为,链表没有
数组便于查询,链表便于插入删除,数组长度固定,链表长度不固定

13.单链表翻转(原地算法)

单链表反转:
void ReverseLinklist(struct Linklist *head)
{
	if (NULL = head || NULL == head->next || NULL == head->next->next)
		return ;
	struct Linklist *begin = head->next;
	struct Linklist *end = head->next->next;
	while(end != NULL)
	{
		begin->next = end->next;
		end->next = head->next;
		head->next = end;
		end = begin->next;
	}
}

14.指针和引用的区别

1.指针是一个变量,存储的是变量的地址,而引用是变量的别名
2.指针可以为空,引用不能为空,必须有实体
3.指针在初始化之后可以改变指向,而引用在初始化之后就不能改变
4.指针可以有多级,引用只有一级
5.sizeof 指针 得到的是指针的大小,而 sizeof 引用 得到的是引用所指向对象的大小
6.指针作为函数参数时,形参实际上是实参的一份拷贝,只是分别属于两个不同的指针变量,函数形参为引用时,能直接改变实参,他不拷贝实参,就是实参的一个别名
7.引用是类型安全,指针类型不安全(引用比指针多了类型检查)

15.&& 和 & , || 和 | 有什么区别

& 和 | 是对操作数进行求值运算,&& 和 || 是判断逻辑关系的
&& 在判断左操作数失败时就不会判断右操作数
|| 在判断左操作数成功时就不会判断右操作数

16.const关键字

C语言	
    int a = 10;
   	const int * num = &a;	//常量指针	不能通过这个指针改变常量 a  但是能通过引用来改变变量的值 a = 11; 这个指针能指向改变指向,指向别的地址
	举例:
		int a=5;
		int b=6;
		const int* n=&a;
		n=&b;	//正确

		int a=5;
		const int* n=&a;
		a=6;	//正确

		const char * str = "12345";
		str = "qwert";		//正确,还是指向常量,常量指针
		str[2] = 'x';		//错误,不能改变常量

	int * const num = &a;	//指针常量	指针常量指向的地址不能改变,但是地址保存的数据可以改变	这个指针不能改变指向 *num = 11;这是允许的
	举例:
		int a=5;
		int *p=&a;
		int* const n=&a;
		*p=8;	//正确

		char *const name = "abc";
		name[2] = 'Z';       //这种是正确的,改变值
		name = "bcde";		//  这种是错误的,name不能指向其他常量




	修饰函数形参,修饰函数返回值(返回值只能赋值给被const 修饰的指针) 修饰全局变量(全局变量作用域是整个文件,一旦某个函数修改了管局变量的值,会影响到其他地方的使用,正常)
	const #define 区别
        #define 是预处理,在编译的时候就进行简单替换,不能进行类型检查 占用代码区空间,
        const 关键字 是在编译运行时起作用 被修饰的就变成了常量

17.TCP 和 UDP

TCP:传输控制协议(TCP,Transmission Control Protocol)面向连接的、可靠的、基于字节流的传输层协议
UDP:Internet 协议提供支持一个无连接的传输协议,称为(UDP,User Datagram Protocol)

区别:
	1.TCP 面向连接,通过三次挥手建立连接,通过四次挥手解除连接; UDP是无连接的,即发送数据之前不需要建立连接,这种方式为 UDP 带来了高效的传输速率,但是也导致无法保证数据的发送成功。
	2.TCP 面向字节流,实际上是 TCP 把数据看成一串无结构的字节流,由于连接问题,当网络出现波动时,连接可能出现响应问题。 UDP 是面向报文的,UDP 没有拥塞控制,因此网络出现拥塞不会影响原主机的发送速率。
	3.每一个 TCP 连接都是点对点的。 UDP 不建立连接,可以支持一对一,多对一,多对多交互通信。
	4.TCP 是可靠的通信方式。 TCP 连接传输的数据,TCP 通过超时重传,数据校验等手段来保证数据无差错,不丢失,不重复。 UDP 由于不需要建立连接, 将会以最大速度进行传输,但不保证交付可靠,可能会出现数据丢失,重复等问题。
	5.TCP 的逻辑通信通道是全双工的可靠信道,而 UDP 是不可靠信道。
	6.TCP 需要建立连接, UDP 不需要建立连接。
	
	TCP 三次握手 
		1.clint 发送一个 SYN 包,告诉 server 端我的初始序列号是 X(seq=X);Clinet 进入 SYN-SNET(同步已发送状态)状态
		2.server 收到 Clinet 的SYN 包,会回复一个 ACK包(确认包ACK=X+1),告诉 Client 已经收到了;Server 进入SYN-RCVD(同步收到状态)状态;接着 server 也告诉client自己的初始序列号,发送一个SYN包告诉Clinet自己的序列号是Y(seq=Y)
		3.client 收到后,回复一个ACK包(确认包ACK=Y+1);确认收到,之后clinet 和 server 就进入ESTABLISHED(已建立连接状态)状态
	TCP 四次挥手
		1.client 发送断开 TCP 连接的请求报文(FIN = 1 表示要断开 TCP 连接, seq = x 初始序列号), clinet 进入FIN-WAIT-1(终止等待1)状态
		2.server 回复 client 发送的 TCP 断开报文 (ACK = 1 seq = y初始序列号 ack = x + 1),server 进入CLOSE-WAIT(关闭等待)状态,此时server处于半关闭,client 及时没有数据要发送了, 但是server要是发送数据,client仍要接收
		3.client 收到server发送的确认请求后,client 进入 FIN-WAIT-2(终止等待2)状态,等待server发送断开报文
		4.server 向 client 发送断开报文(FIN = 1表示要断开, ack = x + 1 seq = z 新的序列号) server 进入 LAST—ACK(最后确认状态)状态
		5.client 收到 server 断开报文,必须发出确认报文(ACK = 1, ack = z + 1, seq = x + 1) client 进入 TIME-WAIT(时间等待状态)状态,进过 2* MSL(最长报文寿命)的时间后,client 进入 close 状态
		6.server 收到 client 的确认报文,立即进入 close 状态
		四次挥手 server 会比 client 结束 TCP 连接时间早一些	

18.OSI七层模型 和 TCP/IP 模型


OSI 从上到下	
    应用层		http协议
    表示层
    会话层
    传输层		TCP协议
    网络层		IP协议
    数据链路层
    物理层
TCP/IP 
	应用层
	传输层
    网络层
    网络接口层(数据链路层)

19.打开一个网页都经过什么

1.DNS协议(应用层) 域名解析,获取相对应的IP
2.TCP协议 负责传输,与服务建立连接 传输层
3.IP协议 发送的数据在网络层使用IP协议
4.OPSF协议 IP数据包在路由器之间,路由器使用OPSF协议
5.ARP协议 路由器在与服务器通信时,将IP地址转换成MAC地址,需要使用ARP协议
6.http协议 建立连接成功后,使用HTTP协议访问网络,获取数据,收到数据后,通过HTTP协议来解析。

20.进程和线程

进程:操作系统资源分配的最小单位
线程:操作系统能够进行运算调度的最小单位(包含在进程中)
区别:
	1.根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元
	2.进程包含线程,线程属于进程,线程不包含进程
	3.开销:进程的创建、销毁、切换都远大于线程
	4.每个进程都拥有自己的内存和资源,而线程是要共享资源的
	
进程间通信方式
	1.管道
	2.信号量
	3.套接字
	4.消息队列
	5.共享内存
线程间通信方式
	1.锁机制,互斥锁、读写锁、条件锁(条件变量)、自旋锁
	
单线程和多线程:
	单线程 按照顺序执行,如果发生阻塞,会影响到后续操作。
	多线程 
	
多线程优点:提升效率 ,避免阻塞,充分使用CPU(避免CPU空转)
多线程缺点:复杂的同步机制和加锁控制机制,要考虑竞争和同步问题,线程崩溃会影响程序(一个进程崩溃不影响其他进程,子进程崩溃也不会影响到主进程,因为每个进程有独立的系统资源。多线程比较脆弱,一个线程崩溃很可能影响到整个程序,因为多个线程是在一个进程里一起合作干活的。)

21.C语言源代码编译过程

1.预处理	生成 .i 文件
	1.处理 #define 宏定义 将其替换掉
	2.处理条件编译, #if、#ifdef、#elif、#else、#endif 等
	3.处理#include 将被包含文件的内容插入到该命令所在的位置,这与复制粘贴的效果一样
	4.删除注释
	5.添加行号,文件名标识,方便调试
	6.保留所有的#pragma命令,因为编译器需要使用它们
2.编译	生成 .s 文件
	把预处理完的 .i 文件 转化成汇编代码文件
3.汇编	生成 .o 文件
	将汇编代码文件转化成机器指令 
4.链接
	目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,程序不能执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。

22.静态库 和 动态库

linux 下 
静态库 libXXXXX.a  动态库 libXXXXXX.so

windows 下
静态库 .lib 动态库 .dll

静态库:在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
	在编译后的执行程序不再需要外部的函数库支持,运行速度相对快些
	如果所使用的静态库发生更新改变,你的程序必须重新编译
	静态库的代码是在编译过程中被载入程序中。
	
静态库:动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入
	动态库的代码是当程序运行到相关函数才调用动态库的相应函数
	动态库的改变并不影响你的程序,所以动态函数库升级比较方便;
	因为函数库并没有整合进程序,所以程序的运行环境必须提供相应的库。

23.大小端字节序

数值例如0x2266使用两个字节存储;高位字节是0x22,低位字节是0x66
	大端字节序:高位字节在前,低位字节在后,人类读写数值的方法
	小端字节序:低位字节在前,高位字节在后,即以0x6622形式存储
网络字节序:大端
主机字节序:小端

24.字节对齐

struct A{
int i;
double b;
char c;
}

struct B{
int i;
char b;
double c;
}

struct C{
int i;
double b;
char c[7];
}
struct D{
int i;
double b;
char c[9];
}

union E{
int i;
double b;
char c[7];
}
union F{
int i;
double b;
char c[9];
}
enum G{
AAA,
BBB,
CCC
}

cout << sizeof(A) << endl;	//24
cout << sizeof(B) << endl;	//16
cout << sizeof(C) << endl;	//24
cout << sizeof(D) << endl;	//32
cout << sizeof(E) << endl;	//8
cout << sizeof(F) << endl;	//16
cout << sizeof(G) << endl;	//4

25.cp -f

-f 选项:覆盖同名文件或目录时不进行提醒,直接强制覆盖。
但是有时候加了 -f 仍会问你是否覆盖,原因是什么?
	因为有些Linux 发行版的cp命令 是 "cp -i" 的别名, -i:  -f 选项相反,在覆盖目标文件之前给出提示,要求用户确认是否覆盖,回答 y 时目标文件将被覆盖
	所以只有 用 alias 命令 把 cp 改回 "cp" 就行了 alias cp="cp"

26.which 、whereis、locate和find四者区别

which:	查找系统 PATH 目录下的可执行文件。其实就是查找那些已经安装好的可以直接执行的命令。
whereis:	定位某个命令的二进制文件、源码和帮助页文件;通过文件索引数据库来查找的而非PATH来查找的,所以查找的面比which要广
find:	通过搜索硬盘的方式查找

27.xargs 命令

echo "--help" | xargs cat // 相当于 cat --help
xargs -d 选项:
	默认情况下xargs将其标准输入中的内容以空白(包括空格、Tab、回车换行等)分割成多个之后当作命令行参数传递给其后面的命令,并运行之,我们可以使用 -d 命令指定分隔符,例如:
echo '11@22@33' | xargs echo 
输出:
11@22@33
默认情况下以空白分割,那么11@22@33这个字符串中没有空白,所以实际上等价于 echo 11@22@33 其中字符串 '11@22@33' 被当作echo命令的一个命令行参数

echo '11@22@33' | xargs -d '@' echo 
输出:
11 22 33
指定以@符号分割参数,所以等价于 echo 11 22 33 相当于给echo传递了3个参数,分别是11、22、33

-p 选项
	使用该选项之后xargs并不会马上执行其后面的命令,而是输出即将要执行的完整的命令(包括命令以及传递给命令的命令行参数),询问是否执行,输入 y 才继续执行,否则不执行。这种方式可以清楚的看到执行的命令是什么样子,也就是xargs传递给命令的参数是什么,例如:
echo '11@22@33' | xargs -p -d '@'  echo 
输出:
echo 11 22 33
 ?...y      ==>这里询问是否执行命令 echo 11 22 33 输入y并回车,则显示执行结果,否则不执行
 11 22 33   ==>执行结果
 
-n 选项
该选项表示将xargs生成的命令行参数,每次传递几个参数给其后面的命令执行,例如如果xargs从标准输入中读入内容,然后以分隔符分割之后生成的命令行参数有10个,使用 -n 3 之后表示一次传递给xargs后面的命令是3个参数,因为一共有10个参数,所以要执行4次,才能将参数用完。例如:

echo '11@22@33@44@55@66@77@88@99@00' | xargs -d '@' -n 3 echo 
输出结果:
11 22 33
44 55 66
77 88 99
00
等价于:
echo 11 22 33
echo 44 55 66
echo 77 88 99
echo 00
实际上运行了4次,每次传递3个参数,最后还剩一个,就直接传递一个参数。

-E 选项,有的系统的xargs版本可能是-e  eof-str
该选项指定一个字符串,当xargs解析出多个命令行参数的时候,如果搜索到-e指定的命令行参数,则只会将-e指定的命令行参数之前的参数(不包括-e指定的这个参数)传递给xargs后面的命令
echo '11 22 33' | xargs -E '33' echo 
输出:
11 22

可以看到正常情况下有3个命令行参数 11、22、33 由于使用了-E '33' 表示在将命令行参数 33 之前的参数传递给执行的命令,33本身不传递。等价于 echo 11 22 这里-E实际上有搜索的作用,表示只取xargs读到的命令行参数前面的某些部分给命令执行。

注意:-E只有在xargs不指定-d的时候有效,如果指定了-d则不起作用,而不管-d指定的是什么字符,空格也不行。

echo '11 22 33' | xargs -d ' ' -E '33' echo  => 输出 11 22 33
echo '11@22@33@44@55@66@77@88@99@00 aa 33 bb' | xargs -E '33' -d '@' -p  echo  => 输出 11 22 33 44 55 66 77 88 99 00 aa 33 bb

28.https 建立通信原理

1.client发送一个https的请求到服务端
2.server申请配置好的证书,包含公私钥
3.server将证书发送给client
4.client 解析完成,判断是否有异常,无异常的话,生成一个随机值(用于对称加密),用服务端公钥进行非对称加密随机值生成密文,client发送密文给server
5.server使用私钥非对称解密,得到随机值(建立成功)
6.双方用随机值当做共享秘钥进行对称加密明文 进行数据传输

29.多线程 锁

普通锁:互斥锁

读写锁:
	写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。
	读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。
	
	特性:
	1.同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。
	2.读写锁出于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。
	3.读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁。
	
自旋锁:
普通的锁,如果条件不达成就会进入睡眠,自旋锁,它不会放弃CPU时间片,而是不停的尝试获取锁,直到成功,适用于并发度不高,代码指向短的场景。
	好处:阻塞和唤醒线程需要高昂的开销,在代码不复杂的情况下可能比转换线程带来的开销要大很多
	坏处:最大坏处就是虽然避免了线程切换的开销,但是它会带来新的开销,因为他会不停的尝试去获得锁,如果这个锁一直没被释放,那这种尝试就是无效的,会浪费资源
	自死锁:已经请求了自旋锁并得到,然后又请求锁
	
	和信号量区别:
	信号量:linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已经被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠,这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务被唤醒,从而可以获得此信号量。
	
	1.自旋锁不会引起调用者睡眠,如果自旋锁已被别的执行单元保持,调用者就一直循环查看该自旋锁的保持者是否已释放了锁。
    2.信号量会引起调用者睡眠,它会把进程从运行队列上拖出去让其睡眠,除非获得锁。
    
    
    需求			建议的加锁方法
低开销加锁		    优先使用自旋锁
短期锁定		     优先使用自旋锁
中断上下文加锁		   使用自旋锁
长期加锁			优先使用信号量
持有锁时需要睡眠	  使用信号量

30.设计模式

三大类:
	创建型模式:对类的实例化过程进行抽象, 能够将软件模块中对象的创建和对象的使用分离
		简单工厂模式,工程方法模式,抽象工厂模式,单例模式,建造者模式
	结构性模式:关注于对象的组成以及对象之间的依赖关系,描述如何将类和对象如何结合在一起形成更大的结构,就像 搭积木 ,可以通过简单积木的组合形成复杂的、功能强大的结构。
		适配器模式,装饰者模式,代理模式,外观模式,桥接模式,组合模式,享元模式
	行为型模式:关注于对象的行为问题,是对在不同的对象之间划分责任和算法的抽象化;不仅仅关注类和对象的结构,而且重点关注他们之间的相互作用。
		策略模式,模板方法模式,观察者模式,迭代器模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。
		
六大原则
	总原则	开闭原则:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭
		单一职责原则:一个类应该只有一个发生变化的原因。
		里氏替换原则:所有引用基类的地方必须能透明地使用其子类的对象
		依赖倒置原则:上层模块不应该依赖底层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象
		接口隔离原则:客户端不应该依赖它不需要的接口;类间的依赖关系应该建立在最小的接口上。
		迪米特法则(最少知道原则):一个类对自己依赖的类知道的越少越好。无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
		合成复用原则:尽量使用对象组合/聚合,而不是继承关系达到软件复用的目的

31.动态库导入导出

在生成dll的时候我们希望将我们的符号导出(符号就是程序中定义的变量或者方法),在使用dll时则时希望导入符号。通常由两种方式来实现符号表的导入导出。

_declspec(dllimport) 导入  和 __declspec(dllexport) 导出

 _declspec(dllexport)与_declspec(dllimport)是相互呼应,只有在DLL内部用dllexport作了声明,才能 在外部函数中用dllimport导入相关代码。实际上,在应用程序访问DLL时,实际上就是应用程序中的导入函数与DLL文件中的导出函数进行链接。而 且链接的方式有两种:隐式迎接和显式链接。
 
 隐式链接是指通过编译器提供给应用程序关于DLL的名称和DLL函数的链接地址,面在应用程序中不需要显式地将DLL加载到内存,即在应用程序中使用dllimport即表明使用隐式链接。不过不是所有的隐式链接都使用dllimport。

显式链接刚同应用程序用语句显式地加载DLL,编译器不需要知道任何关DLL的信息


为什么标准头文件都有类似以下的结构?

#ifndef __INCvxWorksh  
#define __INCvxWorksh

#ifdef __cplusplus  
extern "C" {
#endif

#ifdef __cplusplus 
}
#endif

#endif

显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。

被extern "C"限定的函数或变量是extern类型的;
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用
被extern "C"修饰的变量和函数是按照C语言方式编译和连接的
    
不修饰直接C++ 直接使用C会报错,

32.路由和交换的概念

路由谋短,交换求快。

交换机工作于数据链路层,用来隔离冲突域,连接的所有设备同属于一个广播域(子网),负责子网内部通信。

路由器工作于网络层,用来隔离广播域(子网),连接的设备分属不同子网,工作范围是多个子网之间,负责网络与网络之间通信。

工作层次不同:

拿OSI七层模型来说,从底往上以此是:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
而交换机主要工作在数据链路层(第二层)
路由器工作在网络层(第三层)。
转发依据不同:
交换机转发所依据的对象时:MAC地址。(物理地址)
路由转发所依据的对象是:IP地址。(网络地址)
主要功能不同:
交换机主要用于组建局域网,
而路由主要功能是将由交换机组好的局域网相互连接起来,或者接入Internet。
交换机能做的,路由都能做。
交换机不能分割广播域,路由可以。
路由还可以提供防火墙的功能。
路由配置比交换机复杂。
价格不同

交换机是看门大爷,路由是邮差。

上次编辑于:
贡献者: wucq@infogo