AIRobot

AIRobot quick note


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

Linux socket

发表于 2019-03-19 分类于 linux
本文字数: 19k 阅读时长 ≈ 17 分钟

一、socket编程

网络功能是Uinux/Linux的一个重要特点,有着悠久的历史,因此有一个非常固定的编程套路。

基于TCP的网络编程:

基于连接, 在交互过程中, 服务器和客户端要保持连接, 不能断开。重发一切出错数据、数据验证, 保证数据的正确性、完整性和顺序性,缺点是消耗的资源比较大。

基于UDP的网络编程:

无连接协议, 在网络交互过程中不保持连接, 只需要在发送数据时连接一下, 不重发、验证数据。优点是资源消耗少, 数据的可靠性完整性顺序性得不到保证。

二、编程步骤:

服务器:

  • ① 创建socket(套接字) socket()

  • ② 准备通信地址

  • ③ 将创建的socket和通信地址绑定 bind()

  • ④ 监听端口 listen()

  • ⑤ 等待客户端连接 accpet()

  • ⑥ 通信双方收发数据 read()/write() send()/recv()

  • ⑦ 关闭socket

客户端:

  • ① 创建socket(套接字) socket()

  • ② 准备通信地址

  • ③ 连接服务器 connect()

  • ④ 收发数据 read()/write() send()/recv()

  • ⑤ 关闭socket  

三、API详解

① socket()函数

    int socket(domain, type, protocol)

    domain:

        AF_UNIX/AF_LOCAL/AF_FILE: 本地通信

        AF_INET: 网络通信 ipv4

        AF_INET6: 网络通信 ipv6

        注:如果AF换成PF效果一样

    type, 选择通信类型, 主要包括:

        SOCK_STREAM: TCP

        SOCK_DGRAM : UDP

    protocol, 本来应该指定通信协议, 但现在基本废弃, 因为协议已经在前面两个参数指定完成,给0即可

② bind()函数

    int bind(int sockfd, struct sockaddr *addr, size)

    sockfd: 要绑定的套接字描述符

    size: 第二个参数占据的内存空间大小

    addr: 涉及三个数据结构struct sockaddr, sockaddr_un, sockaddr_in

      sockaddr, 主要用于函数参数, 不负责存储数据

      sockaddr_un, 当着本地通信时, 用于本地通信使用的地址 (sys/un.h)

      sockaddr_in, 当着网络通信时, 负责存储网络通信的地址数据

      struct sockaddr_in {

          sin_family; //用于指定协议族, 和socket()的参数保持一致

          sin_port; //网络通信使用的端口号

          sin_addr; //存储网络通信的ip地址 

      }          

③ htons

④ inet_aton         

⑤ listen()函数

    int listen(int sockfd, int backlog)

     sockfd: 将sockfd参数所标识的套接字为被动模式, 使之可以接受连接请求

     backlog: 表示未决连接请求队列的最大长度, 即允许最多有多少个未决连接请求存在。若服务器的未决连接请求已达到该值, 则客户端通过 connect()连接服务器的操作将返回-1,且error为ECONNREFUSED

⑥ accpet()函数

    int accpet(sockfd, struct sockaddr* addr, socklen_t *addrlen)

     从sockfd参数所标识套接字对应未决连接请求队列中取出的一个连接请求, 同时创建一个新的套接字,用于该连接通信, 返回套接字的描述符

     addr和addrlen 用于输出连接请求发起者的地址信息

     返回值: 为新创建用于和客户端通信的套接字描述符 失败-1, error  

⑦ inet_ntoa

⑧ recv()函数  

   int recv(int sockfd, buf, len, flags)

     flags, 通常取0: 阻塞收取数据

        O_NONBLOCK: 不阻塞, 如果未收到数据, 返回错误信息

     返回值:

        >0, 实际接受数据字节数

        -1 , 出错, error

         0 , 通信的另一端关闭

⑨ send()函数

   int send(int sockfd, buf, len, flags)

     flags: 通常取0, 阻塞发送

     O_NONBLOCK: 不阻塞, 如果未收到数据, 返回错误信息

⑩ connect()函数

   int connect(int sockfd, addr, addr_len)

     参数参考bind() 

TCP简易scp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//
// Created by AIRobot on 2018/5/20.
//

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>


const int HostAddrMaxSize = 256;
const int PathMaxSize = 2048;
const int ServerPort = 2222;
const int MSGBufferSize = 1024;
const int BufferSize = 2048;
const int FileNameMaxSize = 512;

/*
* method = 0 put
* method = 1 get
* ctype = 0 path
* ctype = 1 file
*/
typedef struct
{
int method;
int ctype;
size_t len;
char buf[MSGBufferSize];
}MSG;

char host[HostAddrMaxSize], remote_path[PathMaxSize], local_path[PathMaxSize];

int parse(char *first, char *second) {
char *local, *remote;
int mode = 0;
char *split_char;
local = first;
remote = second;
split_char = strstr(remote, ":");
if (!split_char) {
local = second;
remote = first;
split_char = strstr(remote, ":");
if (!split_char) {
puts("wrong path!");
return -1;
}
mode = 1;
}

int host_length = (int) (split_char - remote);
int remote_path_length = (int) (strlen(remote) - host_length);
int local_path_length = (int) strlen(local);
strncpy(host, remote, host_length);
strncpy(remote_path, split_char + 1, remote_path_length);
strncpy(local_path, local, local_path_length);
host[host_length] = '\0';
remote_path[remote_path_length] = '\0';
local_path[local_path_length] = '\0';
return mode;
}

void split2name(char *path, char *name)
{
char *p = path;
char *t;
while((t = strstr(p, "/")))
p = t+1;
int len = strlen(p);
strncpy(name, p, len);
name[len] = '\0';
}

//

void path_join(char *path, char *file_name, char *new_path)
{
size_t length = strlen(path);
size_t len = strlen(file_name);
strncpy(new_path, path, length);
if(new_path[length-1] != '/')
{
strncpy(&new_path[length], "/", 1);
strncpy(&new_path[length+1], file_name, len);
new_path[length+len+1] = '\0';
}
else
{
strncpy(&new_path[length], file_name, strlen(file_name));
new_path[length+len] = '\0';
}
}

int main(int argc, char **argv) {
if (argc != 3) {
puts("usage: myscp src dest");
return 0;
}
if(sizeof(MSG)>BufferSize)
{
perror("message buffer size > buffer size");
exit(1);
}

int mode, on = 1;
int socket_descriptor;
struct sockaddr_in server_addr;
mode = parse(argv[1], argv[2]);
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(ServerPort);
// puts(local_path);
// puts(host);
// puts(remote_path);

if (inet_aton(host, &server_addr.sin_addr) < 0) {
perror("IP error\n");
exit(1);
}
if ((socket_descriptor = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Error opening socket\n");
exit(1);
}
if (connect(socket_descriptor, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) {
perror("Error connecting to socket\n");
exit(1);
}
puts("connected");
if (mode == 0) // to remote
{
char file_name[FileNameMaxSize+1], new_path[PathMaxSize];
split2name(local_path, file_name);
path_join(remote_path, file_name, new_path);

MSG first_msg;
first_msg.method = 0;
first_msg.ctype = 0;
first_msg.len = strlen(new_path)+1;
strcpy(first_msg.buf, new_path);

send(socket_descriptor, &first_msg, sizeof(first_msg), 0);
int fd = open(local_path, O_RDONLY);
if (fd < 0) {
perror("open file failed\n");
exit(-1);
}
int total;
MSG msg;
msg.method = 0;
msg.ctype = 1;
while ((total = read(fd, msg.buf, sizeof(msg.buf))) > 0) {
msg.len = total;
if (send(socket_descriptor, &msg, sizeof(MSG), 0) < 0) {
perror("send error\n");
exit(1);
}
}
if (total < 0) {
perror("read error");
exit(1);
}
puts("sent");
close(fd);
} else if (mode == 1) // to local
{
char file_name[FileNameMaxSize+1];
char new_path[PathMaxSize];
int total;
split2name(remote_path, file_name);
path_join(local_path, file_name, new_path);
int fd = open(new_path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open file failed\n");
exit(-1);
}

MSG first_msg;
first_msg.method = 1;
first_msg.ctype = 0;
first_msg.len = strlen(remote_path)+1;
strcpy(first_msg.buf, remote_path);
send(socket_descriptor, &first_msg, sizeof(first_msg), 0);

MSG msg;
char buf[sizeof(MSG)];
while ((total = recv(socket_descriptor, buf, sizeof(MSG), 0)) > 0) {
memcpy(&msg, buf, sizeof(MSG));
if (write(fd, msg.buf, msg.len) < 0) {
perror("write error");
exit(1);
}
}
if (total < 0) {
perror("recv error");
exit(1);
}
puts("got");
close(fd);
}
close(socket_descriptor);


return 0;
}

server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//
// Created by AIRobot on 2018/5/20.
//

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

const int ServerPort = 2222;
const int BufferSize = 2048;
const int MSGBufferSize = 1024;
const int FileNameMaxSize = 512;

typedef struct
{
int method;
int ctype;
size_t len;
char buf[MSGBufferSize];
}MSG;

int main() {
struct sockaddr_in sin, pin;
int sock_descriptor, temp_sock_descriptor;
socklen_t address_size;
int i, len, pid, on = 1;

sock_descriptor = socket(AF_INET, SOCK_STREAM, 0);
if (sock_descriptor == -1) {
perror("call to socket");
exit(1);
}
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(ServerPort);

setsockopt(sock_descriptor, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(sock_descriptor, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
perror("call to bind");
exit(1);
}
if (listen(sock_descriptor, 20) == -1) {
perror("call to listen");
exit(1);
}
printf("Accepting connections ...\n");

while(1) {
temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin, &address_size);
if (temp_sock_descriptor == -1) {
perror("call to accept");
exit(1);
}
if ((pid=fork()) < 0){
perror("fork");
exit(1);
} else if (pid == 0) { //child
int length;
close(sock_descriptor);
char buf[sizeof(MSG)];
if((length = recv(temp_sock_descriptor, buf, sizeof(MSG), 0)) > 0) {
MSG first_msg;
memcpy(&first_msg, buf, sizeof(MSG));
char path[FileNameMaxSize];
if(first_msg.method==1&&first_msg.ctype==0) // to client
{
puts(first_msg.buf);
strcpy(path, first_msg.buf);
int fd = open(path, O_RDONLY);
if(fd < 0)
{
perror("open failed");
exit(1);
}
int len;
MSG msg;
msg.method = 0;
msg.ctype = 1;
while((len = read(fd, msg.buf, sizeof(msg.buf))) > 0)
{
msg.len = len;
if(send(temp_sock_descriptor, &msg, sizeof(MSG), 0) < 0)
{
perror("send failed");
exit(1);
}
}
if(len < 0)
{
perror("read failed");
exit(1);
}
// puts(path);
puts("sent");
close(fd);
}else if(first_msg.method==0&&first_msg.ctype==0) // to server
{
strcpy(path, first_msg.buf);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open file failed\n");
exit(1);
}
int len;
MSG msg;
while((len = recv(temp_sock_descriptor, buf, sizeof(MSG), 0)) > 0)
{
memcpy(&msg, buf, sizeof(MSG));
if(write(fd, msg.buf, msg.len) < 0)
{
perror("write failed");
exit(1);
}
}
if(len < 0)
{
perror("recv failed");
exit(1);
}
puts("store");
close(fd);
}else
{
perror("unsupport");
exit(1);
}
}
else
{
perror("call to recv");
exit(1);
}

// sleep(10);
close(temp_sock_descriptor);
exit(0);
}
close(temp_sock_descriptor);
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//
// Created by AIRobot on 2018/5/20.
//

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

const int ServerPort = 2222;
const int BufferSize = 2048;
const int FileNameMaxSize = 512;

int main() {
struct sockaddr_in sin, pin;
int sock_descriptor, temp_sock_descriptor;
socklen_t address_size;
int i, len, pid, on = 1;
char buf[16384];

sock_descriptor = socket(AF_INET, SOCK_STREAM, 0);
if (sock_descriptor == -1) {
perror("call to socket");
exit(1);
}
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(ServerPort);

setsockopt(sock_descriptor, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(sock_descriptor, (struct sockaddr *) &sin, sizeof(sin)) == -1) {
perror("call to bind");
exit(1);
}
if (listen(sock_descriptor, 20) == -1) {
perror("call to listen");
exit(1);
}
printf("Accepting connections ...\n");

while(1) {
temp_sock_descriptor = accept(sock_descriptor, (struct sockaddr *)&pin, &address_size);
if (temp_sock_descriptor == -1) {
perror("call to accept");
exit(1);
}
if ((pid=fork()) < 0){
perror("fork");
exit(1);
} else if (pid == 0) { //child
int length;
close(sock_descriptor);
bzero(buf, sizeof(buf));
if((length = recv(temp_sock_descriptor, buf, sizeof(buf), 0)) > 0) {
send(temp_sock_descriptor, "ack", 4, 0);
char path[FileNameMaxSize], tmp_buf[BufferSize];
bzero(tmp_buf, sizeof(tmp_buf));
bzero(path, sizeof(path));
recv(temp_sock_descriptor, path, sizeof(path), 0);
send(temp_sock_descriptor, "ack", 4, 0);
puts("path:");
puts(path);
if(strcmp(buf, "get")==0)
{
int fd = open(path, O_RDONLY);
if(fd < 0)
{
perror("open failed");
exit(1);
}
int len;
while((len = read(fd, tmp_buf, sizeof(tmp_buf))) > 0)
{
if(send(temp_sock_descriptor, tmp_buf, len, 0) < 0)
{
perror("send failed");
exit(1);
}
}
if(len < 0)
{
perror("read failed");
exit(1);
}
// puts(path);
puts("sent");
close(fd);
}else if(strcmp(buf, "put")==0)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open file failed\n");
exit(1);
}
int len;
while((len = recv(temp_sock_descriptor, tmp_buf, sizeof(tmp_buf), 0)) > 0)
{
if(write(fd, tmp_buf, len) < 0)
{
perror("write failed");
exit(1);
}
}
if(len < 0)
{
perror("recv failed");
exit(1);
}
puts("store");
close(fd);
}else
{
perror("unsupport");
exit(1);
}
}
else
{
perror("call to recv");
exit(1);
}

// sleep(10);
close(temp_sock_descriptor);
exit(0);
}
close(temp_sock_descriptor);
}

return 0;
}

UDP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 12345
int main()
{
/*1 创建socket*/
int sd = socket(PF_INET, SOCK_DGRAM, 0);
if(sd == -1)
{
perror("socket failed");
exit(-1);
}
/*2 准备地址*/
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(PORT);
inet_aton("192.168.1.100", &addr.sin_addr);
/*3 进行通信*/
char *str = "Message";
sendto(sd, str, strlen(str), 0,
(struct sockaddr *)&addr,
sizeof(addr));
char buf[100] = {0};
int len = sizeof(addr);
recvfrom(sd, buf, sizeof(buf), 0,
(struct sockaddr *) &addr,
&len);
printf("Server:%s\n", buf);
close(sd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 12345
int main()
{
/*1 创建socket*/
int sd = socket(PF_INET, SOCK_DGRAM, 0);
if(sd == -1)
{
perror("socket failed");
exit(-1);
}
/*2 准备地址*/
struct sockaddr_in addr;
addr.sin_family = PF_INET;
addr.sin_port = htons(PORT);
inet_aton("192.168.1.100", &addr.sin_addr);
/*3 socket addr 绑定*/
int res = bind(sd, (struct sockaddr *)&addr,
sizeof(addr));
if(res == -1)
{
perror("bind failed");
exit(-1);
}
/*4 进行通信*/
while(1)
{
char buf[100] = {0};
struct sockaddr_in fromaddr;
int len = sizeof(fromaddr);
recvfrom(sd, buf, sizeof(buf), 0,
(struct sockaddr *) &fromaddr,
&len);
printf("Client %s:%s\n",
inet_ntoa(fromaddr.sin_addr),
buf);
char *str = "server message";
sendto(sd, str, strlen(str), 0,
(struct sockaddr *)&fromaddr,
sizeof(fromaddr));
}
close(sd);

}

常见C++面试题

发表于 2019-03-17 分类于 interview
本文字数: 27k 阅读时长 ≈ 24 分钟

声明

  • 本文包含三篇转自互联网的文章
  • 不同文章中有可能出现相同问题,也许解释相似,或许不同,建议仔细推敲,暂时因时间关系无法筛选
  • 文章中或许有未解释问题,若别的文章里没有,有空时我会更新
  • 略有改动,不影响阅读

C++ 面试出现频率最高的 30 道题目

  • 作者:王世晖
  • 原文:
    • C++ 面试出现频率最高的 30 道题目(一)
    • C++ 面试出现频率最高的 30 道题目(二)
    • C++ 面试出现频率最高的 30 道题目(三)

new、delete、malloc、free 关系

  • delete 会调用对象的析构函数,和 new 对应 free 只会释放内存,new 调用构造函数。
  • malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++ 的运算符。
  • 它们都可用于申请动态内存和释放内存。
  • 对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。
  • 对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
  • 由于 malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free。
  • 因此 C++ 语言需要一个能完成动态内存分配和初始化工作的运算符 new,以及一个能完成清理与释放内存工作的运算符 delete。
  • 注意 new/delete 不是库函数。

delete 与 delete[]区别

  • delete 只会调用一次析构函数,而 delete[] 会调用每一个成员的析构函数。
  • 在 More Effective C++ 中有更为详细的解释:“当 delete 操作符用于数组时,它为每个数组元素调用析构函数,然后调用 operator delete 来释放内存。”
  • delete 与 new 配套,delete[] 与 new[] 配套
  • delete 和 delete[] 功能是相同的。对于自定义的复杂数据类型,delete 和 delete[]不能互用。delete[] 删除一个数组,delete 删除一个指针。
  • 简单来说,用 new 分配的内存用 delete 删除;用 new[] 分配的内存用 delete[] 删除。elete[] 会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用 delete 时没用括号,delete 就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。

C++有哪些性质(面向对象特点)

封装,继承和多态。

子类析构时要调用父类的析构函数吗?

  • 析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了。
  • 定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。

多态,虚函数,纯虚函数

  • 多态:是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面:
    • 在程序运行时的多态性通过继承和虚函数来体现;
    • 在程序编译时多态性体现在函数和运算符的重载上;
  • 虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
  • 纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。
    • 从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
    • 抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。

求下面函数的返回值(微软)

1
2
3
4
5
6
7
8
9
10
int func(x) 
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}

假定 x = 9999。 答案:8
思路:将 x 转化为 2 进制,看含有的 1 的个数。

什么是“引用”?申明和使用“引用”要注意哪些问题?

  • 引用就是某个目标变量的“别名” (alias),对应用的操作与对变量直接操作效果完全相同。
  • 申明一个引用的时候,切记要对其进行初始化。
  • 引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。
  • 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。
  • 不能建立数组的引用。

将“引用”作为函数参数有哪些特点?

  1. 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
  2. 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
  3. 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用”*指针变量名”的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

在什么时候需要使用“常引用”? 

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

常引用声明方式:const 类型标识符 &引用名 = 目标变量名;

  • 例1
1
2
3
4
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确
  • 例2
1
2
string foo();
void bar(string & s);

那么下面的表达式将是非法的:

1
2
bar(foo());
bar("hello world");

原因在于 foo() 和 “hello world” 串都会产生一个临时对象,而在 C++ 中,这些临时对象都是 const 类型的。因此上面的表达式就是试图将一个 const 类型的对象转换为非 const 类型,这是非法的。引用型参数应该在能被定义为 const 的情况下,尽量定义为 const。

将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?

  • 格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
  • 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
  • 注意事项:
    • 不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了”无所指”的引用,程序会进入未知状态。
    • 不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
    • 可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
  • 流操作符重载返回值申明为“引用”的作用:
    • 流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。
    • 可选的其它方案包括:返回一个流对象和返回一个流对象指针。
    • 但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。
  • 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
int &put(int n);
int vals[10];
int error=-1;
int main()
{
put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}
  • 在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1] 的 Item23 详细的讨论了这个问题。主要原因是这四个操作符没有 side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个 new 分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第 2、3 两个方案都被否决了。静态对象的引用又因为 ((a+b)==(c+d)) 会永远为 true 而导致错误。所以可选的只剩下返回一个对象了。

结构与联合有和区别?

  1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
  2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

试写出程序结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int  a=4;

int &f(int x)
{ a=a+x;
return a;
}

int main()
{
int t=5;
cout<<f(t)<<endl; //a = 9
f(t)=20; //a = 20
cout<<f(t)<<endl; //t = 5,a = 20 -> a = 25
t=f(t); //a = 30 t = 30
cout<<f(t)<<endl; //t = 60
}

重载 ( overload ) 和重写( overried,有的书也叫做“覆盖”)的区别?

  • 从定义上来说:

    • 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
    • 重写:是指子类重新定义父类虚函数的方法。
  • 从实现原理上来说:

    • 重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
    • 重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

有哪几种情况只能用 intialization list 而不能用 assignment ?

当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。

C++ 是不是类型安全的?

不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。

main 函数执行以前,还会执行什么代码?

全局对象的构造函数会在main 函数之前执行。

描述内存分配方式以及它们的区别?

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

分别写出 bool,int,float,指针类型的变量 a 与“零”的比较语句。

  • bool : if(!a) or if(a)
  • int : if(a==0) 或 if(!a)
  • float : const EXPRESSION EXP=0.000001 if (a<EXP && a>-EXP)
  • pointer : if (a != NULL) or if(a == NULL)

请说出 const 与 #define 相比,有何优点?

const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

  1. const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
  2. 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

简述数组与指针的区别?

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

  1. 修改内容上的差别

    1
    2
    3
    4
    char a[] = “hello”;
    a[0] = ‘X’;
    char *p = “world”; // 注意p 指向常量字符串
    p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
  2. 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    char a[] = "hello world";
    char *p = a;
    cout<< sizeof(a) << endl; // 12 字节
    cout<< sizeof(p) << endl; // 4 字节
    //计算数组和指针的内存容量
    void Func(char a[100])
    {
    cout<< sizeof(a) << endl; // 4 字节而不是100 字节
    }

int (*s[10])(int) 表示的是什么?

int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函数。

栈内存与文字常量区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char str1[] = "abc";
char str2[] = "abc";

const char str3[] = "abc";
const char str4[] = "abc";

const char *str5 = "abc";
const char *str6 = "abc";

char *str7 = "abc";
char *str8 = "abc";

cout << ( str1 == str2 ) << endl;//0 分别指向各自的栈内存
cout << ( str3 == str4 ) << endl;//0 分别指向各自的栈内存
cout << ( str5 == str6 ) << endl;//1指向文字常量区地址相同
cout << ( str7 == str8 ) << endl;//1指向文字常量区地址相同
  • 结果是:0 0 1 1
  • 解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;而str5,str6,str7,str8是指针,它们指向相同的常量区域。

将程序跳转到指定内存地址

要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?

1
2
3
4
5
6
7
8
*((void (*)( ))0x100000) ( );
//首先要将0x100000强制转换成函数指针,即:
(void (*)())0x100000
//然后再调用它:
*((void (*)())0x100000)();
//用typedef可以看得更直观些:
typedef void(*)() voidFuncPtr;
*((voidFuncPtr)0x100000)();

int id[sizeof(unsigned long)]; 这个对吗?为什么?

正确 这个 sizeof是编译时运算符,编译时就确定了 ,可以看成和机器有关的常量。

引用与指针有什么区别?

  1. 引用必须被初始化,指针不必。
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。

const 与 #define 的比较,const有什么优点?

  1. const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应) 。
  2. 有些集成化的调试工具可以对 const 常量进行调试,但是不能对宏常量进行调试。

复杂声明

1
2
3
void * ( * (*fp1)(int))[10];
float (*(* fp2)(int,int,int))(int);
int (* ( * fp3)())[10]();

分别表示什么意思?

  1. void * ( * (*fp1)(int))[10];

    fp1是一个指针,指向一个函数,这个函数的参数为int型,
    函数的返回值是一个指针,这个指针指向一个数组,
    这个数组有10个元素,每个元素是一个void*型指针。

  2. float (( fp2)(int,int,int))(int);

    fp2是一个指针,指向一个函数,这个函数的参数为3个int型,
    函数的返回值是一个指针,这个指针指向一个函数,
    这个函数的参数为int型,函数的返回值是float型。

  3. int (* ( * fp3)())[10]();

    fp3是一个指针,指向一个函数,这个函数的参数为空,
    函数的返回值是一个指针,这个指针指向一个数组,
    这个数组有10个元素,每个元素是一个指针,指向一个函数,
    这个函数的参数为空,函数的返回值是int型。

内存的分配方式有几种?

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

基类的析构函数不是虚函数,会带来什么问题?

派生类的析构函数用不上,会造成资源的泄漏。

全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?

生命周期不同:

  • 全局变量随主程序创建和创建,随主程序销毁而销毁;
  • 局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;

使用方式不同:

  • 通过声明后全局变量程序的各个部分都可以用到;
  • 局部变量只能在局部使用;分配在栈区。

操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。

C++面试集锦( 面试被问到的问题 )

  • 作者:Y1
  • 原文:C++面试集锦( 面试被问到的问题 )

C 和 C++ 区别

  • C 语言是面向过程的语言
  • C++ 是基于 C 语言开发的面向对象的语言,兼具面向对象与面向过程

const 有什么用途

主要有三点:

  1. 定义只读变量,即常量
  2. 修饰函数的参数和函数的返回值
  3. 修饰函数的定义体,这里的函数为类的成员函数,被 const 修饰的成员函数代表不修改成员变量的值

指针和引用的区别

  1. 引用是变量的一个别名,内部实现是只读指针
  2. 引用只能在初始化时被赋值,其他时候值不能被改变,指针的值可以在任何时候被改变
  3. 引用不能为 NULL,指针可以为 NULL
  4. 引用变量内存单元保存的是被引用变量的地址
  5. “sizeof 引用” = 指向变量的大小 , “sizeof 指针”= 指针本身的大小
  6. 引用可以取地址操作,返回的是被引用变量本身所在的内存单元地址
  7. 引用使用在源代码级相当于普通的变量一样使用,做函数参数时,内部传递的实际是变量地址

C++中有了 malloc/free , 为什么还需要 new/delete

  1. malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
  2. 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。
    1. 对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
    2. 由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
  3. 因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

编写类 String 的构造函数,析构函数,拷贝构造函数和赋值函数

多态的实现

单链表的逆置

堆和栈的区别

一个由c/C++编译的程序占用的内存分为以下几个部分

  1. 栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

  2. 堆区(heap)― 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收 。

    注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

  3. 全局区(静态区)(static)― 全局变量和静态变量的存储是strcpy放在一块的,

    初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域
    程序结束后有系统释放

  4. 文字常量区 ― 常量字符串就是放在这里的。 程序结束后由系统释放

  5. 程序代码区―存放函数体的二进制代码。

不调用 C/C++ 的字符串库函数,编写 strcpy

strcpy函数的实现

1
2
3
4
5
6
7
8
9
char * strcpy(char * strDest,const char * strSrc)
{
if ((strDest==NULL)||strSrc==NULL))
return NULL;
char * strDestCopy=strDest;
while ((*strDest++=*strSrc++)!='\0');
*strDest = '\0';
return strDestCopy;
}

关键字 static 的作用

  1. 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量, 该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值
  2. 在模块内的 static 全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问
  3. 在模块内的 static 函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内
  4. 在类的 static 成员变量属于整个类所拥有,对类的所以对象只有一份拷贝
  5. 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量

介绍它最重要的一条:隐藏。(static函数,static变量均可) –> 对应上面的 2、3 项

当同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。

举例来说明。同时编译两个源文件,一个是 a.c,另一个是 main.c。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//a.c
char a = 'A'; // global variable
void msg()
{
printf("Hello\n");
}
//main.c
int main()
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}

程序的运行结果是:
A Hello

为什么在 a.c 中定义的全局变量 a 和函数 msg能在 main.c 中使用?

  • 前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a 是全局变量,msg是函数,并且都没有加 static 前缀,
  • 因此对于另外的源文件 main.c 是可见的。
  • 如果加了 static,就会对其它源文件隐藏。例如在 a 和 msg 的定义前加上 static,main.c 就看不到它们了。
  • 利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static 可以用作函数和变量的前缀,对于函数来讲,static 的作用仅限于隐藏

在 C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”

C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同

假设某个函数原型为:

void foo(int x, inty);
  • 该函数被C编译器编译后在库中的名字为: _foo
  • 而C++编译器则会产生像: _foo_int_int 之类的名字。
  • 为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern “C”。

头文件种的 ifndef/define/endif 是干什么用的

防止头文件被重复包含

线程和进程的联系和区别

秒杀多线程第一篇 多线程笔试面试题汇总

线程有哪几种状态

秒杀多线程第一篇 多线程笔试面试题汇总

进程间的通信方式

管道、有名管道、信号、共享内存、消息队列、信号量、套接字、文件.

线程同步和线程互斥的区别

秒杀多线程第一篇 多线程笔试面试题汇总

线程同步的方式

Linux: 互斥锁、条件变量和信号量

Linux 线程同步的三种方法

网络七层

物理层-数据链路层-网络层-运输层-会话层-表示层-应用层

TCP 和 UDP 有什么区别

  • TCP—传输控制协议,提供的是面向连接、可靠的字节流服务。
    • 当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。
    • TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
  • UDP—用户数据报协议,是一个简单的面向数据报的运输层协议。
    • UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
    • 由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

编写 Socket 套接字的步骤

TCP 三次握手和四次挥手, 以及各个状态的作用

TCP三次握手四次挥手详解

HTTP 协议

  • http(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式,
  • HTTP1.1版本中给出一种持续连接的机制,绝大多数的Web开发,都是构建在HTTP协议之上的Web应用。
  • TCP 和 HTTP区别:TCP和Http的区别

使用过的 shell 命令

  • pwd ,man ,cd
  • cp ,mv ,rm ,mkdir ,touch ,ls ,cat ,tail ,less ,df ,du ,find
  • top ,kill ,sudo

使用过的 vim 命令

  • wq!, dd , dw , yy , p , i
  • %s/old/new/g
    • /abc 向后搜索字符串ab
    • ?abc 向前搜索字符串abc

使用过的 gdb 命令

比较全面的gdb调试命令

常见算法

快速排序、堆排序和归并排序

  • 堆排序原理及算法实现(最大堆)
  • 白话经典算法系列之六 快速排序 快速搞定
  • 排序算法稳定性

C 库函数实现

静态链表和动态链表的区别

静态链表和动态链表

大并发 (epoll)

  • 优点:深入linux网络编程(三):异步阻塞IO —— epoll
  • 实例:Linux IO多路复用之epoll网络编程(含源码)

海量数据处理的知识点(Hash表,Hash统计)

  • hash表
  • 教你如何迅速秒杀掉:99%的海量数据处理面试题

什么时候要用虚析构函数

通过基类的指针来删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。

一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,从而千万内存泄漏。

原因:

在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。
如果想要用基类对非继承成员进行操作,则要把基类的这个操作(函数)定义为虚函数。
那么,析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。

注意:

如果不需要基类对派生类及对象进行操作,则不能定义虚函数(包括虚析构函数),因为这样会增加内存开销。

C++ 怎样让返回对象的函数不调用拷贝构造函数

拷贝构造函数前加 “explicit” 关键字

孤儿进程和僵尸进程

孤儿进程与僵尸进程-总结

请用简单的语言告诉我 C++ 是什么?

  • C++ 是在 C 语言的基础上开发的一种面向对象编程语言,应用广泛。
  • C++ 支持多种编程范式 --面向对象编程、泛型编程和过程化编程。
  • 其编程领域众广,常用于系统开发,引擎开发等应用领域,是最受广大程序员受用的最强大编程语言之一
  • 支持类、封装、重载等特性!

C 和 C++ 的区别?

  • C++ 在 C 的基础上增添类,C 是一个结构化语言,它的重点在于算法和数据结构。
  • C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制)
  • 而对于 C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

    什么是面向对象(OOP)?

    面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。

什么是多态?

  • 多态是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。
  • 不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态。

设计模式懂嘛,简单举个例子?

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

  • 比如单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    适用于:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时;当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

  • 比如工厂模式,定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

    适用于:当一个类不知道它所必须创建的对象的类的时候;当一个类希望由它的子类来指定它所创建的对象的时候;当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

STL 库用过吗?常见的STL容器有哪些?算法用过哪几个?

STL 包括两部分内容:容器和算法。(重要的还有融合这二者的迭代器)

  • 容器,即存放数据的地方。比如 array 等。
    • 在 STL 中,容器分为两类:序列式容器和关联式容器。
    • 序列式容器,其中的元素不一定有序,但都可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue、slist;
    • 关联式容器,内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap。
    • 下面各选取一个作为说明。
      • vector:它是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。
      • set:其内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值。
  • 算法,如排序,复制……以及个容器特定的算法。这点不用过多介绍,主要看下面迭代器的内容。
  • 迭代器是STL的精髓,我们这样描述它:
    迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构。
    它将容器和算法分开,好让这二者独立设计。

数据结构会吗?项目开发过程中主要用到那些?

数据结构中主要会用到数组,链表,树(较少),也会用到栈和队列的思想。

const 知道吗?解释其作用。

  1. const 修饰类的成员变量,表示成员常量,不能被修改。
  2. const 修饰函数承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数。
  3. 如果 const 构成函数重载,const 对象只能调用 const 函数,非 const 对象优先调用非 const 函数。
  4. const 函数只能调用 const 函数。非 const 函数可以调用 const 函数。
  5. 类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。。

类的 static 变量在什么时候初始化?函数的 static 变量在什么时候初始化?

类的静态成员变量在类实例化之前就已经存在了,并且分配了内存。函数的 static 变量在执行此函数时进行初始化。

堆和栈的区别?堆和栈的生命周期?

  1. 堆栈空间分配区别:
    1. 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
    2. 堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收,分配方式倒是类似于链表
  2. 堆栈缓存方式区别:
    1. 栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放
    2. 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些
  3. 堆栈数据结构区别:
    1. 堆(数据结构):堆可以被看成是一棵树,如:堆排序
    2. 栈(数据结构):一种先进后出的数据结构

解释下封装、继承和多态?

  1. 封装:
    1. 封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)
    2. 封装的意义在于保护或者防止代码(数据)被我们无意中破坏
  2. 继承:
    1. 继承主要实现重用代码,节省开发时间
    2. 子类可以继承父类的一些东西
  3. 多态
    1. 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
    2. 在运行时,可以通过指向基类的指针,来调用实现派生类中的方法

指针和引用的区别?

  1. 指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用仅是个别名;
  2. 引用使用时无需解引用(*),指针需要解引用;
  3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  4. 引用没有 const,指针有 const;
  5. 引用不能为空,指针可以为空;
  6. “sizeof 引用” 得到的是所指向的变量(对象)的大小,而 “sizeof 指针” 得到的是指针本身的大小;
  7. 指针和引用的自增 (++) 运算意义不一样;
  8. 指针可以有多级,但是引用只能是一级(int **p 合法 而 int &&a 是不合法的)
  9. 从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?

  • 用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
  • 使用的时候要记得指针的长度。
  • malloc 的时候得确定在那里 free.
  • 对指针赋值的时候应该注意被赋值指针需要不需要释放.
  • 动态分配内存的指针最好不要再次赋值.

常用的排序算法有哪些?简单描述几个排序算法的优缺点?

选择、冒泡、快速、希尔、归并、堆排等。

  1. 快排:是冒泡排序的一种改进。
    1. 优点:快,数据移动少
    2. 缺点:稳定性不足
  2. 归并:分治法排序,稳定的排序算法,一般用于对总体无序,但局部有序的数列。
    1. 优点:效率高O(n),稳定
    2. 缺点:比较占用内存
    3. new 和 malloc 的区别?

  3. malloc 与 free 是 C++/C 语言的标准库函数,new/delete 是 C++ 的运算符。它们都可用于申请动态内存和释放内存。
  4. 对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
  5. 由于 malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free。因此 C++ 语言需要一个能完成动态内存分配和初始化工作的运算符 new,以一个能完成清理与释放内存工作的运算符 delete。注意 new/delete 不是库函数。
  6. C++ 程序经常要调用 C 函数,而 C 程序只能用 malloc/free 管理动态内存。
  7. new 可以认为是 malloc 加构造函数的执行。new 出来的指针是直接带类型信息的。而 malloc 返回的都是 void 指针。

TCP 和 UDP 通信的差别?什么是 IOCP?

  1. TCP 面向连接, UDP 面向无连接的
  2. TCP 有保障的,UDP 传输无保障的
  3. TCP 是效率低的,UDP 效率高的
  4. TCP 是基于流的,UDP 基于数据报文
  5. TCP 传输重要数据,UDP 传输不重要的数据
  6. IOCP 全称I/O Completion Port,中文译为 I/O 完成端口。
  7. IOCP 是一个异步 I/O 的 API,它可以高效地将 I/O 事件通知给应用程序。

与使用 select() 或是其它异步方法不同的是,一个套接字 [socket] 与一个完成端口关联了起来,然后就可继续进行正常的 Winsock 操作了。然而,当一个事件发生的时候,此完成端口就将被操作系统加入一个队列中。然后应用程序可以对核心层进行查询以得到此完成端口。

同步 IO 和异步 IO 的区别?

  1. 同步

    所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
    按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等)。
    但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
    最常见的例子就是 SendMessage。
    该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。
    当对方处理完毕以后,该函数才把消息处理函数所返回的值返回给调用者。

  2. 异步

    异步的概念和同步相对。
    当一个异步过程调用发出后,调用者不会立刻得到结果。
    实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用。

解释 C++ 中静态函数和静态变量?

  • 类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在,不属于任何对象,而非静态类成员变量则是属于对象所有的。类静态数据成员只有一个拷贝,为所有此类的对象所共享。
  • 类静态成员函数属于整个类,不属于某个对象,由该类所有对象共享。
  1. static 成员变量实现了同类对象间信息共享。

  2. static 成员类外存储,求类大小,并不包含在内。

  3. static 成员是命名空间属于类的全局变量,存储在 data 区的 rw 段。

  4. static 成员只能类外初始化。

  5. 可以通过类名访问(无对象生成时亦可),也可以通过对象访问。

  6. 静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。

  7. 静态成员函数只能访问静态数据成员。

    原因:非静态成员函数,在调用时 this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。

说下你对内存的了解?

  1. 栈 - 由编译器自动分配释放
  2. 堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
  3. 全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
  4. 另外还有一个专门放常量的地方。- 程序结束释放
  5. 程序代码区,存放 2 进制代码。

在函数体中定义的变量通常是在栈上,用 malloc,calloc,realloc 等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了 static 修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的 static 变量表示在该文件中有效,不能 extern 到别的文件用,在函数体内定义的 static 表示只在该函数体内有效。另外,函数中的 “adgfdf” 这样的字符串存放在常量区。

C++ 面试 100 经典

  • 作者:未知
  • 链接:C++面试100经典(非原文)

面向对象的程序设计思想是什么?

把数据结构和对数据结构进行操作的方法封装形成一个个的对象。

什么是类?

把一些具有共性的对象归类后形成一个集合,也就是所谓的类。

对象都具有的两方面特征是什么?分别是什么含义?

  • 对象都具有的特征是:静态特征和动态特征。
  • 静态特征是指能描述对象的一些属性(成员变量),动态特征是指对象表现出来的行为(成员函数)

在头文件中进行类的声明,在对应的实现文件中进行类的定义有什么意义?

这样可以提高编译效率,因为分开的话只需要编译一次生成对应的 .obj 文件后,再次应用该类的地方,这个类就不会被再次编译,从而大大的提高了编译效率。

在类的内部定义成员函数的函数体,这种函数会具备那种属性?

这种函数会自动为内联函数,这种函数在函数调用的地方在编译阶段都会进行代码替换。

成员函数通过什么来区分不同对象的成员数据?为什么它能够区分?

通过this指针指向对象的首地址来区分的。

C++ 编译器自动为类产生的四个缺省函数是什么?

默认构造函数,拷贝构造函数,析构函数,赋值函数。

拷贝构造函数在哪几种情况下会被调用?

  1. 当类的一个对象去初始化该类的另一个对象时;
  2. 如果函数的形参是类的对象,调用函数进行形参和实参结合时;
  3. 如果函数的返回值是类对象,函数调用完成返回时。

构造函数与普通函数相比在形式上有什么不同?(构造函数的作用,它的声明形式来分析)

  • 构造函数是类的一种特殊成员函数,一般情况下,它是专门用来初始化对象成员变量的。
  • 构造函数的名字必须与类名相同,它不具有任何类型,不返回任何值。

什么时候必须重写拷贝构造函数?

当构造函数涉及到动态存储分配空间时,要自己写拷贝构造函数,并且要深拷贝。

构造函数的调用顺序是什么?

  1. 先调用基类构造函数
  2. 按声明顺序初始化数据成员
  3. 最后调用自己的构造函数。

哪几种情况必须用到初始化成员列表?

  1. 类的成员是常量成员初始化;
  2. 类的成员是对象成员初始化,而该对象没有无参构造函数。
  3. 类的成员为引用时。

什么是常对象?

常对象是指在任何场合都不能对其成员的值进行修改的对象。

静态函数存在的意义?

  • 静态私有成员在类外不能被访问,可通过类的静态成员函数来访问;
  • 当类的构造函数是私有的时,不像普通类那样实例化自己,只能通过静态成员函数来调用构造函数。

在类外有什么办法可以访问类的非公有成员?

友元,继承,公有成员函数。

什么叫抽象类?

不用来定义对象而只作为一种基本类型用作继承的类。

运算符重载的意义?

为了对用户自定义数据类型的数据的操作与内定义的数据类型的数据的操作形式一致。

不允许重载的5个运算符是哪些?

  1. .* (成员指针访问运算符号)
  2. :: 域运算符
  3. Sizeof 长度运算符号
  4. ?: 条件运算符号
  5. . (成员访问符)

运算符重载的三种方式?

普通函数,友元函数,类成员函数。

流运算符为什么不能通过类的成员函数重载?一般怎么解决?

因为通过类的成员函数重载必须是运算符的第一个是自己,而对流运算的重载要求第一个参数是流对象。所以一般通过友元来解决。

赋值运算符和拷贝构造函数的区别与联系?

  • 相同点:都是将一个对象 copy 到另一个中去。
  • 不同点:拷贝构造函数涉及到要新建立一个对象。

在哪种情况下要调用该类的析构函数?

对象生命周期结束时。

对象间是怎样实现数据的共享的?

通过类的静态成员变量来实现对象间的数据共享。静态成员变量占有自己独立的空间不为某个对象所私有。

友元关系有什么特性?

单向的,非传递的,不能继承的。

对对象成员进行初始化的次序是什么?

它的次序完全不受它们在初始化表中次序的影响,只有成员对象在类中声明的次序来决定的。

类和对象之间的关系是什么?

类是对象的抽象,对象是类的实例。

对类的成员的访问属性有什么?

public,protected,private

const char *p和char * const p; 的区别

如果 const 位于星号的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量;
如果 const 位于星号的右侧,const 就是修饰指针本身,即指针本身是常量。

是不是一个父类写了一个 virtual 函数,如果子类覆盖它的函数不加 virtual ,也能实现多态?

  • virtual 修饰符会被隐形继承的。
  • virtual 可加可不加,子类覆盖它的函数不加 virtual ,也能实现多态。

函数重载是什么意思?它与虚函数的概念有什么区别?

  • 函数重载是一个同名函数完成不同的功能,编译系统在编译阶段通过函数参数个数、参数类型不同,函数的返回值来区分该调用哪一个函数,即实现的是静态的多态性。但是记住:不能仅仅通过函数返回值不同来实现函数重载。
  • 而虚函数实现的是在基类中通过使用关键字 virtual 来申明一个函数为虚函数,含义就是该函数的功能可能在将来的派生类中定义或者在基类的基础之上进行扩展,系统只能在运行阶段才能动态决定该调用哪一个函数,所以实现的是动态的多态性。它体现的是一个纵向的概念,也即在基类和派生类间实现。

构造函数和析构函数是否可以被重载,为什么?

构造函数可以被重载,析构函数不可以被重载。因为构造函数可以有多个且可以带参数,而析构函数只能有一个,且不能带参数。

如何定义和实现一个类的成员函数为回调函数?

  • 所谓的回调函数,就是预先在系统的对函数进行注册,让系统知道这个函数的存在,以后,当某个事件发生时,再调用这个函数对事件进行响应。
  • 定义一个类的成员函数时在该函数前加 CALLBACK 即将其定义为回调函数,函数的实现和普通成员函数没有区别。

虚函数是怎么实现的?

简单说来使用了虚函数表.

抽象类不会产生实例,所以不需要有构造函数。

错

从一个模板类可以派生新的模板类,也可以派生非模板类。

对

main 函数执行以前,还会执行什么代码?

全局对象的构造函数会在main 函数之前执行。

当一个类 A 中没有生命任何成员变量与成员函数,这时 sizeof(A) 的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)

肯定不是零。举个反例,如果是零的话,声明一个 class A[10] 对象数组,而每一个对象占用的空间是零,这时就没办法区分 A[0],A[1]… 了。

delete 与 delete[]区别:

delete 只会调用一次析构函数,而 delete[] 会调用每一个成员的析构函数。

子类析构时要调用父类的析构函数吗?

会调用。析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了

继承的优缺点。

  • 优点
    1. 类继承是在编译时刻静态定义的,且可直接使用,
    2. 类继承可以较方便地改变父类的实现。
  • 缺点
    1. 因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现
    2. 父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为
    3. 如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

解释堆和栈的区别。

  • 栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
  • 堆(heap)一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收 。

一个类的构造函数和析构函数什么时候被调用,是否需要手工调用?

构造函数在创建类对象的时候被自动调用,析构函数在类对象生命期结束时,由系统自动调用。

何时需要预编译:

  • 总是使用不经常改动的大型代码体。
  • 程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

多态的作用?

  1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
  2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用

虚拟函数与普通成员函数的区别?内联函数和构造函数能否为虚拟函数?

  • 区别:虚拟函数有 virtual 关键字,有虚拟指针和虚函数表,虚拟指针就是虚拟函数的接口,而普通成员函数没有。
  • 内联函数和构造函数不能为虚拟函数。

构造函数和析构函数的调用顺序? 析构函数为什么要虚拟?

  • 构造函数的调用顺序:基类构造函数—对象成员构造函数—派生类构造函数;
  • 析构函数的调用顺序与构造函数相反。
  • 析构函数虚拟是为了防止析构不彻底,造成内存的泄漏。

C++ 中类型为 private 的成员变量可以由哪些函数访问?

只可以由本类中的成员函数和友元函数访问

请说出类中 private,protect,public 三种访问限制类型的区别

  • private 是私有类型,只有本类中的成员函数访问;
  • protect 是保护型的,本类和继承类可以访问;
  • public 是公有类型,任何类都可以访问.

类中成员变量怎么进行初始化?

可以通过构造函数的初始化列表或构造函数的函数体实现。

在什么时候需要使用“常引用”?

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

引用与指针有什么区别?

  1. 引用必须被初始化,指针不必。
  2. 引用初始化以后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针。

描述实时系统的基本特性

在特定时间内完成特定的任务,实时性与可靠性。

全局变量和局部变量在内存中是否有区别?如果有,是什么区别?

全局变量储存在静态数据区,局部变量在堆栈中。

堆栈溢出一般是由什么原因导致的?

没有回收垃圾资源

什么函数不能声明为虚函数?

构造函数(constructor)

IP地址的编码分为哪俩部分?

答 IP 地址由两部分组成,网络号和主机号。

不能做switch()的参数类型是:

switch 的参数不能为实型。

如何引用一个已经定义过的全局变量?

可以用引用头文件的方式,也可以用 extern 关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用 extern 方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错

对于一个频繁使用的短小函数,在 C 语言中应用什么实现,在 C++ 中应用什么实现?

C 用宏定义,C++ 用 inline

C++ 是不是类型安全的?

不是。两个不同类型的指针之间可以强制转换(用 reinterpret cast)

当一个类 A 中没有生命任何成员变量与成员函数,这时 sizeof(A) 的值是多少,请解释一下编译器为什么没有让它为零。

为1。举个反例,如果是零的话,声明一个 class A[10] 对象数组,而每一个对象占用的空间是零,这时就没办法区分 A[0],A[1]… 了。

简述数组与指针的区别?

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

  1. 修改内容上的区别

    char a[] = “hello”;
    a[0] = ‘X’;
    char *p = “world”; // 注意p 指向常量字符串
    p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

  2. 用运算符 sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是 p 所指的内存容量。

C++ 函数中值的传递方式

三种方式:值传递、指针传递、引用传递

内存的分配方式

分配方式有三种

  1. 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
  2. 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。
  3. 堆上分配,也称动态分配,如我们用 new,malloc 分配内存,用 delete,free 来释放的内存。

extern“C” 有什么作用?

  • Extern “C” 是由 C++ 提供的一个连接交换指定符号,用于告诉 C++ 这段代码是 C 函数。这是因为 C++ 编译后库中函数名会变得很长,与C生成的不一致,造成 C++ 不能直接调用C函数,加上 extren “c” 后,C++ 就能直接调用 C 函数了。
  • Extern “C” 主要使用正规DLL函数的引用和导出 和 在 C++ 包含C函数或C头文件时使用。使用时在前面加上 extern “c” 关键字即可。可以用一句话概括 extern “C” 这个声明的真实目的:实现 C++ 与 C 及其它语言的混合编程。

用什么函数开启新进程、线程。

  • 线程:CreateThread/AfxBeginThread 等
  • 进程:CreateProcess 等

SendMessage 和 PostMessage 有什么区别

  • SendMessage 是阻塞的,等消息被处理后,代码才能走到 SendMessage 的下一行。
  • PostMessage 是非阻塞的,不管消息是否已被处理,代码马上走到 PostMessage 的下一行。

CMemoryState 主要功能是什么

查看内存使用情况,解决内存泄露问题。

#include <filename.h> 和 #include “filename.h” 有什么区别?

  • 对于 #include <filename.h> ,编译器从标准库路径开始搜索 filename.h
  • 对于 #include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h

处理器标识 #error 的目的是什么?

编译时输出一条错误信息,并中止继续编译。

#if!defined(AFX_…_HADE_H) #define(AFX_…_HADE_H) … #endif 作用?

防止该头文件被重复引用。

在定义一个宏的时候要注意什么?

定义部分的每个形参和整个表达式都必须用括号括起来,以避免不可预料的错误发生

数组在做函数实参的时候会转变为什么类型?

数组在做实参时会变成指针类型。

系统会自动打开和关闭的 3 个标准的文件是?

  1. 标准输入—-键盘— stdin
  2. 标准输出—-显示器— stdout
  3. 标准出错输出—-显示器— stderr

在 Win32 下 char, int, float, double 各占多少位?

  1. Char 占用8位
  2. Int 占用32位
  3. Float 占用32位
  4. Double 占用64位

strcpy() 和 memcpy() 的区别?

strcpy() 和 memcpy() 都可以用来拷贝字符串,strcpy() 拷贝以’\0’结束,但 memcpy() 必须指定拷贝的长度。

说明define和const在语法和含义上有什么不同?

  1. #define 是 C 语法中定义符号变量的方法,符号常量只是用来表达一个值,在编译阶段符号就被值替换了,它没有类型;
  2. Const 是 C++ 语法中定义常变量的方法,常变量具有变量特性,它具有类型,内存中存在以它命名的存储单元,可以用 sizeof 测出长度。

说出字符常量和字符串常量的区别,并使用运算符sizeof计算有什么不用?

字符常量是指单个字符,字符串常量以 ‘\0’ 结束,使用运算符 sizeof 计算多占一字节的存储空间。

简述全局变量的优缺点?

全局变量也称为外部变量,它是在函数外部定义的变量,它属于一个源程序文件,它保存上一次被修改后的值,便于数据共享,但不方便管理,易引起意想不到的错误。

总结static的应用和作用?

  1. 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
  2. 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
  3. 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
  4. 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
  5. 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量。

总结 const 的应用和作用?

  1. 欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
  2. 对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const;
  3. 在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
  4. 对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量;
  5. 对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不为“左值”

什么是指针?谈谈你对指针的理解?

  • 指针是一个变量,该变量专门存放内存地址;
  • 指针变量的类型取决于其指向的数据类型,在所指数据类型前加*
  • 指针变量的特点是它可以访问所指向的内存。

什么是常指针,什么是指向常变量的指针?

  • 常指针的含义是该指针所指向的地址不能变,但该地址所指向的内容可以变化,使用常指针可以保证我们的指针不能指向其它的变量,
  • 指向常变量的指针是指该指针的变量本身的地址可以变化,可以指向其它的变量,但是它所指的内容不可以被修改。指向长变量的指针定义,

函数指针和指针函数的区别?

  • 函数指针是指向一个函数入口的指针;指针函数是函数的返回值是一个指针类型。

简述 Debug 版本和 Release 版本的区别?

  • Debug 版本是调试版本,Release 版本是发布给用户的最终非调试的版本,

指针的几种典型应用情况?

  • int *p[n]; -指针数组,每个元素均为指向整型数据的指针。
  • int (*)p[n]; -p为指向一维数组的指针,这个一维数组有n个整型数据。
  • int *p(); -函数带回指针,指针指向返回的值。
  • int (*)p(); -p为指向函数的指针。

static 函数与普通函数有什么区别?

static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

struct (结构)和 union (联合)的区别?

  1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
  2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

class 和 struct 的区别?

struct 的成员默认是公有的,而类的成员默认是私有的。

简述枚举类型?

枚举方便一次定义一组常量,使用起来很方便;

assert() 的作用?

ASSERT() 是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为 FALSE (0), 程序将报告错误,并终止执行。如果表达式不为 0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。

局部变量和全局变量是否可以同名?

能。局部会屏蔽全局。要用全局变量,需要使用 “::”(域运算符)。

程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

在什么时候使用常引用?

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

类的声明和实现的分开的好处?

  1. 起保护作用;
  2. 提高编译的效率。

windows 消息系统由哪几部分构成?

3 部分组成:

  1. 消息队列:操作系统负责为进程维护一个消息队列,程序运行时不断从该消息队列中获取消息、处理消息;
  2. 消息循环:应用程序通过消息循环不断获取消息、处理消息。
  3. 消息处理:消息循环负责将消息派发到相关的窗口上使用关联的窗口过程函数进行处理。

什么是消息映射?

消息映射就是让程序员指定MFC类(有消息处理能力的类)处理某个消息。然后由程序员完成对该处理函数的编写,以实现消息处理功能。

什么是 UDP 和 TCP 的区别是什么?

  • TCP 的全称为传输控制协议。这种协议可以提供面向连接的、可靠的、点到点的通信。
  • UDP 全称为用户报文协议,它可以提供非连接的不可靠的点到多点的通信。
  • 用 TCP 还是 UDP,那要看你的程序注重哪一个方面?可靠还是快速?

面试基础知识仓库链接整理

发表于 2019-03-16 分类于 interview
本文字数: 211 阅读时长 ≈ 1 分钟

互联网求职面试题、知识点和面经整理。

笔试面试知识整理

技术面试必备基础知识

2018/2019/校招/春招/秋招/算法/机器学习(Machine Learning)/深度学习(Deep Learning)/自然语言处理(NLP)/C/C++/Python/面试笔记

本项目曾冲到全球第一,干货集锦见本页面最底部,另完整精致的纸质版《编程之法:面试和算法心得》已在京东/当当上销售

C/C++面试基础知识总结

C/C++

当。。。时发生了什么

发表于 2019-03-16 更新于 2019-04-02
本文字数: 12k 阅读时长 ≈ 11 分钟

Link

当···时发生了什么?

这个仓库试图回答一个古老的面试问题:当你在浏览器中输入 google.com 并且按下回车之后发生了什么?

不过我们不再局限于平常的回答,而是想办法回答地尽可能具体,不遗漏任何细节。

这将是一个协作的过程,所以深入挖掘吧,并且帮助我们一起完善它。仍然有大量的细节等待着你来添加,欢迎向我们发送 Pull Requset!

这些内容使用 Creative Commons Zero_ 协议发布。

目录

.. contents::
:backlinks: none
:local:

按下”g”键

接下来的内容介绍了物理键盘和系统中断的工作原理,但是有一部分内容却没有涉及。当你按下“g”键,浏览器接收到这个消息之后,会触发自动完成机制。浏览器根据自己的算法,以及你是否处于隐私浏览模式,会在浏览器的地址框下方给出输入建议。大部分算法会优先考虑根据你的搜索历史和书签等内容给出建议。你打算输入 “google.com”,因此给出的建议并不匹配。但是输入过程中仍然有大量的代码在后台运行,你的每一次按键都会使得给出的建议更加准确。甚至有可能在你输入之前,浏览器就将 “google.com” 建议给你。

回车键按下

为了从零开始,我们选择键盘上的回车键被按到最低处作为起点。在这个时刻,一个专用于回车键的电流回路被直接地或者通过电容器间接地闭合了,使得少量的电流进入了键盘的逻辑电路系统。这个系统会扫描每个键的状态,对于按键开关的电位弹跳变化进行噪音消除(debounce),并将其转化为键盘码值。在这里,回车的码值是13。键盘控制器在得到码值之后,将其编码,用于之后的传输。现在这个传输过程几乎都是通过通用串行总线(USB)或者蓝牙(Bluetooth)来进行的,以前是通过PS/2或者ADB连接进行。

USB键盘:

  • 键盘的USB元件通过计算机上的USB接口与USB控制器相连接,USB接口中的第一号针为它提供了5V的电压

  • 键码值存储在键盘内部电路一个叫做”endpoint”的寄存器内

  • USB控制器大概每隔10ms便查询一次”endpoint”以得到存储的键码值数据,这个最短时间间隔由键盘提供

  • 键值码值通过USB串行接口引擎被转换成一个或者多个遵循低层USB协议的USB数据包

  • 这些数据包通过D+针或者D-针(中间的两个针),以最高1.5Mb/s的速度从键盘传输至计算机。速度限制是因为人机交互设备总是被声明成”低速设备”(USB 2.0 compliance)

  • 这个串行信号在计算机的USB控制器处被解码,然后被人机交互设备通用键盘驱动进行进一步解释。之后按键的码值被传输到操作系统的硬件抽象层

虚拟键盘(触屏设备):

  • 在现代电容屏上,当用户把手指放在屏幕上时,一小部分电流从传导层的静电域经过手指传导,形成了一个回路,使得屏幕上触控的那一点电压下降,屏幕控制器产生一个中断,报告这次“点击”的坐标

  • 然后移动操作系统通知当前活跃的应用,有一个点击事件发生在它的某个GUI部件上了,现在这个部件是虚拟键盘的按钮

  • 虚拟键盘引发一个软中断,返回给OS一个“按键按下”消息

  • 这个消息又返回来向当前活跃的应用通知一个“按键按下”事件

产生中断[非USB键盘]

键盘在它的中断请求线(IRQ)上发送信号,信号会被中断控制器映射到一个中断向量,实际上就是一个整型数 。CPU使用中断描述符表(IDT)把中断向量映射到对应函数,这些函数被称为中断处理器,它们由操作系统内核提供。当一个中断到达时,CPU根据IDT和中断向量索引到对应的中断处理器,然后操作系统内核出场了。

(Windows)一个 WM_KEYDOWN 消息被发往应用程序

HID把键盘按下的事件传送给 KBDHID.sys 驱动,把HID的信号转换成一个扫描码(Scancode),这里回车的扫描码是 VK_RETURN(0x0d)。 KBDHID.sys 驱动和 KBDCLASS.sys (键盘类驱动,keyboard class driver)进行交互,这个驱动负责安全地处理所有键盘和小键盘的输入事件。之后它又去调用 Win32K.sys ,在这之前有可能把消息传递给安装的第三方键盘过滤器。这些都是发生在内核模式。

Win32K.sys 通过 GetForegroundWindow() API函数找到当前哪个窗口是活跃的。这个API函数提供了当前浏览器的地址栏的句柄。Windows系统的”message pump”机制调用 SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, lParam) 函数, lParam 是一个用来指示这个按键的更多信息的掩码,这些信息包括按键重复次数(这里是0),实际扫描码(可能依赖于OEM厂商,不过通常不会是 VK_RETURN ),功能键(alt, shift, ctrl)是否被按下(在这里没有),以及一些其他状态。

Windows的 SendMessage API直接将消息添加到特定窗口句柄 hWnd 的消息队列中,之后赋给 hWnd 的主要消息处理函数 WindowProc 将会被调用,用于处理队列中的消息。

当前活跃的句柄 hWnd 实际上是一个edit control控件,这种情况下,WindowProc 有一个用于处理 WM_KEYDOWN 消息的处理器,这段代码会查看 SendMessage 传入的第三个参数 wParam ,因为这个参数是 VK_RETURN ,于是它知道用户按下了回车键。

(Mac OS X)一个 KeyDown NSEvent被发往应用程序

中断信号引发了I/O Kit Kext键盘驱动的中断处理事件,驱动把信号翻译成键码值,然后传给OS X的 WindowServer 进程。然后, WindowServer 将这个事件通过Mach端口分发给合适的(活跃的,或者正在监听的)应用程序,这个信号会被放到应用程序的消息队列里。队列中的消息可以被拥有足够高权限的线程使用 mach_ipc_dispatch 函数读取到。这个过程通常是由 NSApplication 主事件循环产生并且处理的,通过 NSEventType 为 KeyDown 的 NSEvent 。

(GNU/Linux)Xorg 服务器监听键码值

当使用图形化的 X Server 时,X Server 会按照特定的规则把键码值再一次映射,映射成扫描码。当这个映射过程完成之后, X Server 把这个按键字符发送给窗口管理器(DWM,metacity, i3等等),窗口管理器再把字符发送给当前窗口。当前窗口使用有关图形API把文字打印在输入框内。

解析URL

  • 浏览器通过 URL 能够知道下面的信息:

    • Protocol “http”
      使用HTTP协议
    • Resource “/“
      请求的资源是主页(index)

输入的是 URL 还是搜索的关键字?

当协议或主机名不合法时,浏览器会将地址栏中输入的文字传给默认的搜索引擎。大部分情况下,在把文字传递给搜索引擎的时候,URL会带有特定的一串字符,用来告诉搜索引擎这次搜索来自这个特定浏览器。

转换非 ASCII 的 Unicode 字符

  • 浏览器检查输入是否含有不是 a-z, A-Z,0-9, - 或者 . 的字符
  • 这里主机名是 google.com ,所以没有非ASCII的字符;如果有的话,浏览器会对主机名部分使用 Punycode_ 编码

检查 HSTS 列表

  • 浏览器检查自带的“预加载 HSTS(HTTP严格传输安全)”列表,这个列表里包含了那些请求浏览器只使用HTTPS进行连接的网站
  • 如果网站在这个列表里,浏览器会使用 HTTPS 而不是 HTTP 协议,否则,最初的请求会使用HTTP协议发送
  • 注意,一个网站哪怕不在 HSTS 列表里,也可以要求浏览器对自己使用 HSTS 政策进行访问。浏览器向网站发出第一个 HTTP 请求之后,网站会返回浏览器一个响应,请求浏览器只使用 HTTPS 发送请求。然而,就是这第一个 HTTP 请求,却可能会使用户受到 downgrade attack_ 的威胁,这也是为什么现代浏览器都预置了 HSTS 列表。

DNS 查询

  • 浏览器检查域名是否在缓存当中(要查看 Chrome 当中的缓存, 打开 chrome://net-internals/#dns <chrome://net-internals/#dns>_)。

  • 如果缓存中没有,就去调用 gethostbyname 库函数(操作系统不同函数也不同)进行查询。

  • gethostbyname 函数在试图进行DNS解析之前首先检查域名是否在本地 Hosts 里,Hosts 的位置 不同的操作系统有所不同_

  • 如果 gethostbyname 没有这个域名的缓存记录,也没有在 hosts 里找到,它将会向 DNS 服务器发送一条 DNS 查询请求。DNS 服务器是由网络通信栈提供的,通常是本地路由器或者 ISP 的缓存 DNS 服务器。

  • 查询本地 DNS 服务器

  • 如果 DNS 服务器和我们的主机在同一个子网内,系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询

  • 如果 DNS 服务器和我们的主机在不同的子网,系统会按照下面的 ARP 过程对默认网关进行查询

ARP 过程

要想发送 ARP(地址解析协议)广播,我们需要有一个目标 IP 地址,同时还需要知道用于发送 ARP 广播的接口的 MAC 地址。

  • 首先查询 ARP 缓存,如果缓存命中,我们返回结果:目标 IP = MAC

如果缓存没有命中:

  • 查看路由表,看看目标 IP 地址是不是在本地路由表中的某个子网内。是的话,使用跟那个子网相连的接口,否则使用与默认网关相连的接口。
  • 查询选择的网络接口的 MAC 地址
  • 我们发送一个二层( OSI 模型_ 中的数据链路层)ARP 请求:

ARP Request::

Sender MAC: interface:mac:address:here
Sender IP: interface.ip.goes.here
Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
Target IP: target.ip.goes.here

根据连接主机和路由器的硬件类型不同,可以分为以下几种情况:

直连:

  • 如果我们和路由器是直接连接的,路由器会返回一个 ARP Reply (见下面)。

集线器:

  • 如果我们连接到一个集线器,集线器会把 ARP 请求向所有其它端口广播,如果路由器也“连接”在其中,它会返回一个 ARP Reply 。

交换机:

  • 如果我们连接到了一个交换机,交换机会检查本地 CAM/MAC 表,看看哪个端口有我们要找的那个 MAC 地址,如果没有找到,交换机会向所有其它端口广播这个 ARP 请求。
  • 如果交换机的 MAC/CAM 表中有对应的条目,交换机会向有我们想要查询的 MAC 地址的那个端口发送 ARP 请求
  • 如果路由器也“连接”在其中,它会返回一个 ARP Reply

ARP Reply::

Sender MAC: target:mac:address:here
Sender IP: target.ip.goes.here
Target MAC: interface:mac:address:here
Target IP: interface.ip.goes.here

现在我们有了 DNS 服务器或者默认网关的 IP 地址,我们可以继续 DNS 请求了:

  • 使用 53 端口向 DNS 服务器发送 UDP 请求包,如果响应包太大,会使用 TCP 协议
  • 如果本地/ISP DNS 服务器没有找到结果,它会发送一个递归查询请求,一层一层向高层 DNS 服务器做查询,直到查询到起始授权机构,如果找到会把结果返回

使用套接字

当浏览器得到了目标服务器的 IP 地址,以及 URL 中给出来端口号(http 协议默认端口号是 80, https 默认端口号是 443),它会调用系统库函数 socket ,请求一个
TCP流套接字,对应的参数是 AF_INET/AF_INET6 和 SOCK_STREAM 。

  • 这个请求首先被交给传输层,在传输层请求被封装成 TCP segment。目标端口会被加入头部,源端口会在系统内核的动态端口范围内选取(Linux下是ip_local_port_range)
  • TCP segment 被送往网络层,网络层会在其中再加入一个 IP 头部,里面包含了目标服务器的IP地址以及本机的IP地址,把它封装成一个IP packet。
  • 这个 TCP packet 接下来会进入链路层,链路层会在封包中加入 frame 头部,里面包含了本地内置网卡的MAC地址以及网关(本地路由器)的 MAC 地址。像前面说的一样,如果内核不知道网关的 MAC 地址,它必须进行 ARP 广播来查询其地址。

到了现在,TCP 封包已经准备好了,可以使用下面的方式进行传输:

  • 以太网_
  • WiFi_
  • 蜂窝数据网络_

对于大部分家庭网络和小型企业网络来说,封包会从本地计算机出发,经过本地网络,再通过调制解调器把数字信号转换成模拟信号,使其适于在电话线路,有线电视光缆和无线电话线路上传输。在传输线路的另一端,是另外一个调制解调器,它把模拟信号转换回数字信号,交由下一个 网络节点_ 处理。节点的目标地址和源地址将在后面讨论。

大型企业和比较新的住宅通常使用光纤或直接以太网连接,这种情况下信号一直是数字的,会被直接传到下一个 网络节点_ 进行处理。

最终封包会到达管理本地子网的路由器。在那里出发,它会继续经过自治区域(autonomous system, 缩写 AS)的边界路由器,其他自治区域,最终到达目标服务器。一路上经过的这些路由器会从IP数据报头部里提取出目标地址,并将封包正确地路由到下一个目的地。IP数据报头部 time to live (TTL) 域的值每经过一个路由器就减1,如果封包的TTL变为0,或者路由器由于网络拥堵等原因封包队列满了,那么这个包会被路由器丢弃。

上面的发送和接受过程在 TCP 连接期间会发生很多次:

  • 客户端选择一个初始序列号(ISN),将设置了 SYN 位的封包发送给服务器端,表明自己要建立连接并设置了初始序列号
  • 服务器端接收到 SYN 包,如果它可以建立连接:
    • 服务器端选择它自己的初始序列号
    • 服务器端设置 SYN 位,表明自己选择了一个初始序列号
    • 服务器端把 (客户端ISN + 1) 复制到 ACK 域,并且设置 ACK 位,表明自己接收到了客户端的第一个封包
  • 客户端通过发送下面一个封包来确认这次连接:
    • 自己的序列号+1
    • 接收端 ACK+1
    • 设置 ACK 位
  • 数据通过下面的方式传输:
    • 当一方发送了N个 Bytes 的数据之后,将自己的 SEQ 序列号也增加N
    • 另一方确认接收到这个数据包(或者一系列数据包)之后,它发送一个 ACK 包,ACK 的值设置为接收到的数据包的最后一个序列号
  • 关闭连接时:
    • 要关闭连接的一方发送一个 FIN 包
    • 另一方确认这个 FIN 包,并且发送自己的 FIN 包
    • 要关闭的一方使用 ACK 包来确认接收到了 FIN

TLS 握手

  • 客户端发送一个 ClientHello 消息到服务器端,消息中同时包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和压缩算法。
  • 服务器端向客户端返回一个 ServerHello 消息,消息中包含了服务器端的TLS版本,服务器所选择的加密和压缩算法,以及数字证书认证机构(Certificate Authority,缩写 CA)签发的服务器公开证书,证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程,直到协商生成一个新的对称密钥
  • 客户端根据自己的信任CA列表,验证服务器端的证书是否可信。如果认为可信,客户端会生成一串伪随机数,使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥
  • 服务器端使用自己的私钥解密上面提到的随机数,然后使用这串随机数生成自己的对称主密钥
  • 客户端发送一个 Finished 消息给服务器端,使用对称密钥加密这次通讯的一个散列值
  • 服务器端生成自己的 hash 值,然后解密客户端发送来的信息,检查这两个值是否对应。如果对应,就向客户端发送一个 Finished 消息,也使用协商好的对称密钥加密
  • 从现在开始,接下来整个 TLS 会话都使用对称秘钥进行加密,传输应用层(HTTP)内容

HTTP 协议

如果浏览器是 Google 出品的,它不会使用 HTTP 协议来获取页面信息,而是会与服务器端发送请求,商讨使用 SPDY 协议。

如果浏览器使用 HTTP 协议而不支持 SPDY 协议,它会向服务器发送这样的一个请求::

GET / HTTP/1.1
Host: google.com
Connection: close
[其他头部]

“其他头部”包含了一系列的由冒号分割开的键值对,它们的格式符合HTTP协议标准,它们之间由一个换行符分割开来。(这里我们假设浏览器没有违反HTTP协议标准的bug,同时假设浏览器使用 HTTP/1.1 协议,不然的话头部可能不包含 Host 字段,同时 GET 请求中的版本号会变成 HTTP/1.0 或者 HTTP/0.9 。)

HTTP/1.1 定义了“关闭连接”的选项 “close”,发送者使用这个选项指示这次连接在响应结束之后会断开。例如:

Connection:close

不支持持久连接的 HTTP/1.1 应用必须在每条消息中都包含 “close” 选项。

在发送完这些请求和头部之后,浏览器发送一个换行符,表示要发送的内容已经结束了。

服务器端返回一个响应码,指示这次请求的状态,响应的形式是这样的::

200 OK
[响应头部]

然后是一个换行,接下来有效载荷(payload),也就是 www.google.com 的HTML内容。服务器下面可能会关闭连接,如果客户端请求保持连接的话,服务器端会保持连接打开,以供之后的请求重用。

如果浏览器发送的HTTP头部包含了足够多的信息(例如包含了 Etag 头部),以至于服务器可以判断出,浏览器缓存的文件版本自从上次获取之后没有再更改过,服务器可能会返回这样的响应::

304 Not Modified
[响应头部]

这个响应没有有效载荷,浏览器会从自己的缓存中取出想要的内容。

在解析完 HTML 之后,浏览器和客户端会重复上面的过程,直到HTML页面引入的所有资源(图片,CSS,favicon.ico等等)全部都获取完毕,区别只是头部的 GET / HTTP/1.1 会变成 GET /$(相对www.google.com的URL) HTTP/1.1 。

如果HTML引入了 www.google.com 域名之外的资源,浏览器会回到上面解析域名那一步,按照下面的步骤往下一步一步执行,请求中的 Host 头部会变成另外的域名。

HTTP 服务器请求处理

HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx,以及 Windows 上的 IIS。

  • HTTPD 接收请求
  • 服务器把请求拆分为以下几个参数:
    • HTTP 请求方法(GET, POST, HEAD, PUT, DELETE, CONNECT, OPTIONS, 或者 TRACE)。直接在地址栏中输入 URL 这种情况下,使用的是 GET 方法
    • 域名:google.com
    • 请求路径/页面:/ (我们没有请求google.com下的指定的页面,因此 / 是默认的路径)
  • 服务器验证其上已经配置了 google.com 的虚拟主机
  • 服务器验证 google.com 接受 GET 方法
  • 服务器验证该用户可以使用 GET 方法(根据 IP 地址,身份信息等)
  • 如果服务器安装了 URL 重写模块(例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite),服务器会尝试匹配重写规则,如果匹配上的话,服务器会按照规则重写这个请求
  • 服务器根据请求信息获取相应的响应内容,这种情况下由于访问路径是 “/“ ,会访问首页文件(你可以重写这个规则,但是这个是最常用的)。
  • 服务器会使用指定的处理程序分析处理这个文件,假如 Google 使用 PHP,服务器会使用 PHP 解析 index 文件,并捕获输出,把 PHP 的输出结果返回给请求者

浏览器背后的故事

当服务器提供了资源之后(HTML,CSS,JS,图片等),浏览器会执行下面的操作:

  • 解析 —— HTML,CSS,JS
  • 渲染 —— 构建 DOM 树 -> 渲染 -> 布局 -> 绘制

浏览器

浏览器的功能是从服务器上取回你想要的资源,然后展示在浏览器窗口当中。资源通常是 HTML 文件,也可能是 PDF,图片,或者其他类型的内容。资源的位置通过用户提供的 URI(Uniform Resource Identifier) 来确定。

浏览器解释和展示 HTML 文件的方法,在 HTML 和 CSS 的标准中有详细介绍。这些标准由 Web 标准组织 W3C(World Wide Web Consortium) 维护。

不同浏览器的用户界面大都十分接近,有很多共同的 UI 元素:

  • 一个地址栏
  • 后退和前进按钮
  • 书签选项
  • 刷新和停止按钮
  • 主页按钮

浏览器高层架构

组成浏览器的组件有:

  • 用户界面 用户界面包含了地址栏,前进后退按钮,书签菜单等等,除了请求页面之外所有你看到的内容都是用户界面的一部分
  • 浏览器引擎 浏览器引擎负责让 UI 和渲染引擎协调工作
  • 渲染引擎 渲染引擎负责展示请求内容。如果请求的内容是 HTML,渲染引擎会解析 HTML 和 CSS,然后将内容展示在屏幕上
  • 网络组件 网络组件负责网络调用,例如 HTTP 请求等,使用一个平台无关接口,下层是针对不同平台的具体实现
  • UI后端 UI 后端用于绘制基本 UI 组件,例如下拉列表框和窗口。UI 后端暴露一个统一的平台无关的接口,下层使用操作系统的 UI 方法实现
  • Javascript 引擎 Javascript 引擎用于解析和执行 Javascript 代码
  • 数据存储 数据存储组件是一个持久层。浏览器可能需要在本地存储各种各样的数据,例如 Cookie 等。浏览器也需要支持诸如 localStorage,IndexedDB,WebSQL 和 FileSystem 之类的存储机制

HTML 解析

浏览器渲染引擎从网络层取得请求的文档,一般情况下文档会分成8kB大小的分块传输。

HTML 解析器的主要工作是对 HTML 文档进行解析,生成解析树。

解析树是以 DOM 元素以及属性为节点的树。DOM是文档对象模型(Document Object Model)的缩写,它是 HTML 文档的对象表示,同时也是 HTML 元素面向外部(如Javascript)的接口。树的根部是”Document”对象。整个 DOM 和 HTML 文档几乎是一对一的关系。

解析算法

HTML不能使用常见的自顶向下或自底向上方法来进行分析。主要原因有以下几点:

  • 语言本身的“宽容”特性
  • HTML 本身可能是残缺的,对于常见的残缺,浏览器需要有传统的容错机制来支持它们
  • 解析过程需要反复。对于其他语言来说,源码不会在解析过程中发生变化,但是对于 HTML 来说,动态代码,例如脚本元素中包含的 document.write() 方法会在源码中添加内容,也就是说,解析过程实际上会改变输入的内容

由于不能使用常用的解析技术,浏览器创造了专门用于解析 HTML 的解析器。解析算法在 HTML5 标准规范中有详细介绍,算法主要包含了两个阶段:标记化(tokenization)和树的构建。

解析结束之后

浏览器开始加载网页的外部资源(CSS,图像,Javascript 文件等)。

此时浏览器把文档标记为可交互的(interactive),浏览器开始解析处于“推迟(deferred)”模式的脚本,也就是那些需要在文档解析完毕之后再执行的脚本。之后文档的状态会变为“完成(complete)”,浏览器会触发“加载(load)”事件。

注意解析 HTML 网页时永远不会出现“无效语法(Invalid Syntax)”错误,浏览器会修复所有错误内容,然后继续解析。

CSS 解析

  • 根据 CSS词法和句法_ 分析CSS文件和 <style> 标签包含的内容以及 style 属性的值
  • 每个CSS文件都被解析成一个样式表对象(StyleSheet object),这个对象里包含了带有选择器的CSS规则,和对应CSS语法的对象
  • CSS解析器可能是自顶向下的,也可能是使用解析器生成器生成的自底向上的解析器

页面渲染

  • 通过遍历DOM节点树创建一个“Frame 树”或“渲染树”,并计算每个节点的各个CSS样式值
  • 通过累加子节点的宽度,该节点的水平内边距(padding)、边框(border)和外边距(margin),自底向上的计算”Frame 树”中每个节点的首选(preferred)宽度
  • 通过自顶向下的给每个节点的子节点分配可行宽度,计算每个节点的实际宽度
  • 通过应用文字折行、累加子节点的高度和此节点的内边距(padding)、边框(border)和外边距(margin),自底向上的计算每个节点的高度
  • 使用上面的计算结果构建每个节点的坐标
  • 当存在元素使用 floated,位置有 absolutely 或 relatively 属性的时候,会有更多复杂的计算,详见http://dev.w3.org/csswg/css2/ 和 http://www.w3.org/Style/CSS/current-work
  • 创建layer(层)来表示页面中的哪些部分可以成组的被绘制,而不用被重新栅格化处理。每个帧对象都被分配给一个层
  • 页面上的每个层都被分配了纹理(?)
  • 每个层的帧对象都会被遍历,计算机执行绘图命令绘制各个层,此过程可能由CPU执行栅格化处理,或者直接通过D2D/SkiaGL在GPU上绘制
  • 上面所有步骤都可能利用到最近一次页面渲染时计算出来的各个值,这样可以减少不少计算量
  • 计算出各个层的最终位置,一组命令由 Direct3D/OpenGL发出,GPU命令缓冲区清空,命令传至GPU并异步渲染,帧被送到Window Server。

GPU 渲染

  • 在渲染过程中,图形处理层可能使用通用用途的 CPU,也可能使用图形处理器 GPU
  • 当使用 GPU 用于图形渲染时,图形驱动软件会把任务分成多个部分,这样可以充分利用 GPU 强大的并行计算能力,用于在渲染过程中进行大量的浮点计算。

Window Server

后期渲染与用户引发的处理

渲染结束后,浏览器根据某些时间机制运行JavaScript代码(比如Google Doodle动画)或与用户交互(在搜索栏输入关键字获得搜索建议)。类似Flash和Java的插件也会运行,尽管Google主页里没有。这些脚本可以触发网络请求,也可能改变网页的内容和布局,产生又一轮渲染与绘制。

.. Creative Commons Zero: https://creativecommons.org/publicdomain/zero/1.0/
.. _CSS词法和句法: http://www.w3.org/TR/CSS2/grammar.html
.. _Punycode: https://en.wikipedia.org/wiki/Punycode
.. _以太网: http://en.wikipedia.org/wiki/IEEE_802.3
.. _WiFi: https://en.wikipedia.org/wiki/IEEE_802.11
.. _蜂窝数据网络: https://en.wikipedia.org/wiki/Cellular_data_communication_protocol
.. _analog-to-digital converter: https://en.wikipedia.org/wiki/Analog-to-digital_converter
.. _网络节点: https://en.wikipedia.org/wiki/Computer_network#Network_nodes
.. _不同的操作系统有所不同 : https://en.wikipedia.org/wiki/Hosts
%28file%29#Location_in_the_file_system
.. _downgrade attack: http://en.wikipedia.org/wiki/SSL_stripping
.. _OSI 模型: https://en.wikipedia.org/wiki/OSI_model

CS相关经典书籍

发表于 2019-03-16 更新于 2019-04-02 分类于 note
本文字数: 4.7k 阅读时长 ≈ 4 分钟

经典编程书籍大全

100+ 经典技术书籍,涵盖:计算机系统与网络、系统架构、算法与数据结构、前端开发、后端开发、移动开发、数据库、测试、项目与团队、程序员职业修炼、求职面试 和 编程相关的经典书籍。

这个列表综合了伯乐在线网站以往推荐经典书籍文章中的列表,以及在微信和微博中被广泛推荐的好书。虽然已经包括了100多本,覆盖的面也比较全。仍然有很多方面需要补充,而且相信还有很多没有被收录的好书。欢迎大家在 issues 中推荐或自荐。

计算机系统与网络

  • 《图灵的秘密:他的生平、思想及论文解读》
  • 《计算机系统概论》
  • 《深入理解Linux内核》
  • 《深入Linux内核架构》
  • 《TCP/IP详解 卷1:协议》
  • 《Linux系统编程(第2版)》
  • 《Linux内核设计与实现(第3版)》
  • 《深入理解计算机系统(原书第3版)》
  • 《计算机程序的构造和解释(原书第2版)》
  • 《编码:隐匿在计算机软硬件背后的语言》
  • 《性能之颠:洞悉系统、企业与云计算》
  • 《UNIX网络编程 卷1:套接字联网API(第3版)》
  • 《UNIX网络编程 卷2:进程间通信》
  • 《Windows核心编程(第5版)》
  • 《WireShark网络分析就这么简单》
  • 《WireShark网络分析的艺术》

编程通用

  • 《设计原本》
  • 《编程原本》
  • 《代码大全》
  • 《UNIX编程艺术》
  • 《代码整洁之道》
  • 《编程珠玑(第2版)》
  • 《编程珠玑(续)》
  • 《软件调试的艺术》
  • 《修改代码的艺术》
  • 《编程语言实现模式》
  • 《编写可读代码的艺术》
  • 《解析极限编程:拥抱变化》
  • 《精通正则表达式(第3版)》
  • 《编译原理(第2版)》龙书
  • 《重构:改善既有代码的设计》
  • 《七周七语言:理解多种编程范型》
  • 《调试九法:软硬件错误的排查之道》
  • 《程序设计语言:实践之路(第3版)》
  • 《计算的本质:深入剖析程序和计算机》
  • 《设计模式 : 可复用面向对象软件的基础》
  • 《Head First 设计模式(中文版) 》(感谢@Great-Li-Xin 推荐 )

算法与数据结构

  • 《算法(第4版)》
  • 《算法导论(原书第2版)》
  • 《Python算法教程》
  • 《算法设计与分析基础(第3版)》
  • 《学习 JavaScript 数据结构与算法》
  • 《数据结构与算法分析 : C++描述(第4版)》
  • 《数据结构与算法分析 : C语言描述(第2版)》
  • 《数据结构与算法分析 : Java语言描述(第2版)》

职业修炼与规划

  • 《大教堂与集市》
  • 《卓有成效的程序员》
  • 《程序员的职业素养》
  • 《程序员修炼之道:从小工到专家》
  • 《软件开发者路线图:从学徒到高手》
  • 《我编程,我快乐: 程序员职业规划之道》
  • 《程序员的思维修炼:开发认知潜能的九堂课》
  • 《高效程序员的45个习惯:敏捷开发修炼之道(修订版)》

大师访谈

  • 《编程大师智慧》
  • 《编程大师访谈录》
  • 《编程人生 : 15位软件先驱访谈录》
  • 《奇思妙想 : 15位计算机天才及其重大发现》
  • 《图灵和ACM图灵奖》

架构/性能

  • 《架构即未来》
  • 《微服务设计》
  • 《大数据日知录》
  • 《企业应用架构模式》
  • 《Web性能权威指南》
  • 《SRE:Google运维解密》
  • 《发布!软件的设计与部署》
  • 《高扩展性网站的 50 条原则》
  • 《大型网站技术架构:核心原理与案例分析》
  • 《恰如其分的软件架构:风险驱动的设计方法》
  • 《软件系统架构:使用视点和视角与利益相关者合作(第2版)》

Linux / Unix

  • 《Linux/Unix 系统编程手册》(感谢@geekgao 的推荐)
  • 《Unix 环境高级编程(第3版)》
  • 《Unix/Linux 编程实践教程》
  • 《鸟哥的 Linux 私房菜(基础学习篇)》和《鸟哥的 Linux 私房菜(服务器架设篇)》
  • 《Linux 命令行与 shell 脚本编程大全(第3版)》
  • 《只是为了好玩 : Linux 之父林纳斯自传》

Web前端

  • 《高性能 JavaScript》
  • 《锋利的 jQuery(第2版)》
  • 《JavaScript 忍者秘籍》(感谢@joker-danta 补充推荐)
  • 《编写可维护的 JavaScript》
  • 《你不知道的 JavaScript(上)》
  • 《你不知道的 JavaScript(中)》
  • 《JavaScript 权威指南(第6版)》
  • 《JavaScript 语言精粹(修订版)》
  • 《JavaScript DOM编程艺术 (第2版)》
  • 《JavaScript 高级程序设计(第3版)》
  • 《JavaScript 异步编程:设计快速响应的网络应用》
  • 《JavaScript设计模式与开发实践》
  • 《JavaScript框架设计(第2版)》
  • 《Effective JavaScript:编写高质量JavaScript代码的68个有效方法》
  • 《HTML5 权威指南》
  • 《HTML5 秘籍(第2版)》
  • 《HTML5 与 CSS3 基础教程(第八版)》
  • 《CSS 揭秘》
  • 《CSS 设计指南(第3版)》
  • 《CSS 权威指南(第3版)》
  • 《深入浅出 HTML 与 CSS》
  • 《ES6 标准入门(第三版)》
  • 《深入理解 ES6》

Java开发

  • 《Java8 实战》
  • 《Java并发编程实战》
  • 《Java性能权威指南》
  • 《Java程序员修炼之道》
  • 《实战Java高并发程序设计》
  • 《Java编程思想 (第4版)》
  • 《深入理解Java虚拟机(第2版)》
  • 《Effective java 中文版(第2版)》
  • 《Java核心技术·卷1:基础知识(原书第9版)》
  • 《Java核心技术·卷2:高级特性(原书第9版)》

.NET/.NET Core

  • 《C# 6.0 本质论》
  • 《果壳中的C#:C#5.0权威指南》
  • 《你必须知道的.NET(第2版)》
  • 《深入理解C#(第3版)》
  • 《Effective C#: 50 Specific Ways to Improve Your C#, Third Edition》
  • 《More Effective C# (Includes Content Update Program): 50 Specific Ways to Improve Your C#, 2nd edition》
  • 《Async in C# 5.0: Unleash the Power of Async》
  • 《C#并发编程经典实例》
  • 《C#多线程编程实战(原书第2版)》
  • 《CLR via C#(第4版)》
  • 《.NET本质论 第1卷:公共语言运行库》
  • 《.NET探秘 : MSIL权威指南》
  • 《Pro .NET Performance》
  • 《Shared Source CLI Essentials》
  • 《.NET 高级调试》
  • 《Microsoft.NET 和 Windows 应用程序调试》
  • 《微软.NET 程序的加密与解密》
  • 《.NET Development Using the Compiler API》
  • 《.NET设计规范 : 约定、惯用法与模式》
  • 《编写高性能的.NET代码》
  • 《Building Microservices with .NET Core》
  • 《Microservices in .NET Core, with Examples in NancyFX》

Python

  • 《集体智慧编程》
  • 《笨办法学Python》
  • 《Python基础教程》
  • 《Python源码剖析》
  • 《Head First Python》
  • 《与孩子一起学编程》
  • 《Python学习手册(第4版)》
  • 《Python Cookbook(第3版)》
  • 《Python参考手册(第4版)》
  • 《Python核心编程(第3版)》
  • 《Python科学计算(第2版)》
  • 《利用 Python 进行数据分析》
  • 《Think Python:像计算机科学家一样思考Python(第2版)》
  • 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》
  • 《Python绝技:运用Python成为顶级黑客》
  • 《Flask Web开发:基于Python的Web应用开发实战》

Android

  • 《Android编程权威指南(第2版)》
  • 《移动应用UI设计模式(第2版)》
  • 《Android开发艺术探索》

iOS

  • 《iOS编程实战》
  • 《iOS编程(第4版)》
  • 《Objective-C高级编程》
  • 《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》

PHP

  • 《Head First PHP & MySQL(中文版)》
  • 《深入PHP:面向对象、模式与实践(第3版)》

C语言

  • 《C标准库》
  • 《C和指针》
  • 《C专家编程》
  • 《C陷阱与缺陷》
  • 《C语言接口与实现》
  • 《C程序设计语言(第2版)》
  • 《C语言参考手册(第5版)》

C++

  • 《C++标准库》
  • 《C++编程思想》
  • 《C++语言的设计与演化》
  • 《C++程序设计原理与实践》
  • 《C++ Primer (中文第5版)》
  • 《C++ Primer习题集(第5版) 》
  • 《C++程序设计语言(第1-3部分)(原书第4版) 》
  • 《Effective C++:改善程序与设计的55个具体做法(第3版)(中文版) 》
  • 《More Effective C++:35个改善编程与设计的有效方法(中文版) 》
     

机器学习和数据挖掘

  • 《数据之巅》
  • 《矩阵分析》
  • 《机器学习》
  • 《统计学习方法》
  • 《机器学习导论》
  • 《推荐系统实践》
  • 《机器学习实战》
  • 《Web数据挖掘》
  • 《深入浅出统计学》
  • 《模式分类(第2版)》
  • 《概率论与数理统计》
  • 《统计学习基础(第2版)(英文) 》
  • 《数据挖掘:概念与技术(第3版)》
  • 《数据挖掘:实用机器学习工具与技术(原书第3版)》
  • 《大数据:互联网大规模数据挖掘与分布式处理(第2版)》

数据库

  • 《数据库系统概念》 感谢@noisnemid 推荐
  • 《数据库系统实现》
  • 《SQL应用重构》
  • 《SQL Cookbook》
  • 《高性能MySQL (第3版)》
  • 《深入浅出SQL(中文版)》
  • 《MySQL技术内幕 : InnoDB存储引擎(第2版)》
  • 《深入浅出MySQL : 数据库开发、优化与管理维护》
  • 《收获,不止SQL优化:抓住SQL的本质》
  • 《SQL Server 性能优化与管理的艺术》
  • 《SQL Server性能调优实战》
  • 《T-SQL性能调优秘笈:基于SQL Server 2012窗口函数》

测试

  • 《探索式软件测试》
  • 《有效的单元测试》
  • 《Google软件测试之道》

项目与团队

  • 《人月神话》
  • 《快速软件开发》
  • 《人件(原书第3版)》
  • 《门后的秘密:卓越管理的故事》
  • 《极客与团队:软件工程师的团队生存秘笈》
  • 《硝烟中的 Scrum 和 XP》 (感谢@geekgao 的推荐。

求职面试

  • 《程序员面试金典(第5版)》
  • 《编程之美 : 微软技术面试心得》
  • 《金领简历:敲开苹果、微软、谷歌的大门》
  • 《剑指Offer:名企面试官精讲典型编程题(纪念版)》

编程之外

  • 《暗时间》
  • 《数学之美》
  • 《赢得朋友》
  • 《精益创业》
  • 《批判性思维》
  • 《世界是数字的》
  • 《程序员的数学》
  • 《程序员健康指南》
  • 《禅与摩托车维修艺术》
  • 《关键对话:如何高效能沟通》
  • 《写作法宝:非虚构写作指南》
  • 《黑客与画家 : 来自计算机时代的高见》
  • 《软件随想录(卷1)》《软件随想录(卷2)》
  • 《如何把事情做到最好:改变全球9800万人的人生指导书》
1…192021…26
AIRobot

AIRobot

AIRobot quick note
130 日志
15 分类
23 标签
GitHub E-Mail
Creative Commons
0%
© 2023 AIRobot | 716k | 10:51