简介
- 常用的编程技巧,使用示例与方法
C++ 后端开发 处理多个请求路径的方法 查表法
在 C++ 后端开发中,处理多个请求路径的一种有效方法是使用查表法,也就是使用数据结构来存储请求路径与相应处理函数之间的映射关系。您可以使用std::map
、std::unordered_map
或自定义数据结构来实现路由表。
以下是一个使用std::unordered_map
的示例:
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
#include <iostream>
#include <unordered_map>
#include <functional>
#include <boost/asio.hpp>
#include <boost/beast.hpp>
namespace asio = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
using RequestHandler = std::function<void(http::request<http::string_body>&, http::response<http::string_body>&)>;
int main() {
asio::io_context io_context;
asio::ip::tcp::acceptor acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 8080));
// 创建请求路径到处理函数的映射表
std::unordered_map<std::string, RequestHandler> route_map;
// 添加路由规则
route_map["/"] = [](http::request<http::string_body>& request, http::response<http::string_body>& response) {
response.result(http::status::ok);
response.set(http::field::content_type, "text/plain");
response.body() = "Hello, World!";
};
route_map["/about"] = [](http::request<http::string_body>& request, http::response<http::string_body>& response) {
response.result(http::status::ok);
response.set(http::field::content_type, "text/plain");
response.body() = "About Us";
};
while (true) {
asio::ip::tcp::socket socket(io_context);
acceptor.accept(socket);
beast::flat_buffer buffer;
http::request<http::string_body> request;
http::read(socket, buffer, request);
// 获取请求的路径
std::string path = request.target().to_string();
// 查找并调用对应的处理函数
auto it = route_map.find(path);
if (it != route_map.end()) {
http::response<http::string_body> response{request.version()};
it->second(request, response);
response.prepare_payload();
http::write(socket, response);
} else {
// 处理未知路径请求
http::response<http::string_body> response{http::status::not_found, request.version()};
response.set(http::field::content_type, "text/plain");
response.body() = "Not Found";
response.prepare_payload();
http::write(socket, response);
}
}
return 0;
}
在这个示例中,我们创建了一个std::unordered_map
来存储请求路径与相应的处理函数之间的映射关系。然后,我们根据请求的路径查找映射表中的处理函数,并调用它来处理请求。这种方法允许您轻松地添加、删除或修改路由规则,以适应不同的请求路径。
C++ /api/too/sucker/open 分割字符串 取出/之内的字符串
要在 C++ 中分割字符串并提取 /
之间的子字符串,你可以使用字符串处理函数和迭代来完成。以下是一个示例函数,演示了如何做到这一点:
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
#include <iostream>
#include <vector>
#include <string>
// 分割字符串并提取 '/' 之间的子字符串
std::vector<std::string> splitAndExtract(const std::string& input) {
std::vector<std::string> result;
size_t startPos = 0; // 记录子字符串的起始位置
// 从头到尾遍历输入字符串
for (size_t i = 0; i < input.length(); ++i) {
if (input[i] == '/') {
// 当找到 '/' 时,提取子字符串并加入结果向量
std::string subStr = input.substr(startPos, i - startPos);
result.push_back(subStr);
startPos = i + 1; // 更新子字符串的起始位置
}
}
// 处理最后一个子字符串
if (startPos < input.length()) {
std::string subStr = input.substr(startPos);
result.push_back(subStr);
}
return result;
}
int main() {
std::string input = "/api/too/sucker/open";
std::vector<std::string> segments = splitAndExtract(input);
// 输出提取的子字符串
for (const std::string& segment : segments) {
std::cout << segment << std::endl;
}
return 0;
}
在这个示例中,splitAndExtract
函数接受一个输入字符串,遍历字符串并查找 /
,每次找到 /
时,提取前一个 /
和当前 /
之间的子字符串,并将其添加到结果向量中。最后,处理最后一个子字符串,然后返回结果向量。
运行示例代码将输出:
1
2
3
4
api
too
sucker
open
这样,你就可以成功分割字符串并提取其中的子字符串。
C uint16_t 怎么存储到uint8_t数组中
将 uint16_t
数据存储到 uint8_t
数组中需要进行字节拆分,因为 uint16_t
是两个字节(16位),而 uint8_t
是一个字节(8位)。通常情况下,高位字节(Most Significant Byte,MSB)会存储在数组的前面,低位字节(Least Significant Byte,LSB)会存储在数组的后面。
以下是一个示例,展示如何将 uint16_t
数据存储到 uint8_t
数组中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdint.h>
int main() {
uint16_t value = 0xABCD; // 要存储的 uint16_t 数据
// 将 uint16_t 数据拆分成两个 uint8_t 字节
uint8_t byte1 = (value >> 8) & 0xFF; // 高位字节
uint8_t byte2 = value & 0xFF; // 低位字节
// 存储到 uint8_t 数组中
uint8_t byteArray[2];
byteArray[0] = byte1;
byteArray[1] = byte2;
printf("Byte 1: 0x%02X\n", byteArray[0]);
printf("Byte 2: 0x%02X\n", byteArray[1]);
return 0;
}
在上述示例中,我们首先将 uint16_t
数据 value
拆分成高位字节 byte1
和低位字节 byte2
。然后,将这两个字节存储到 uint8_t
数组 byteArray
中。通过打印数组中的字节,我们可以看到 uint16_t
数据被正确地存储在了 uint8_t
数组中。
需要注意的是,字节拆分和存储的顺序可能受到字节序的影响。在不同的机器架构上,字节序可能是大端序(Big Endian)或小端序(Little Endian)。在实际应用中,你需要根据你的机器架构来确定正确的字节顺序。
C 打开设备文件 详解 示例
在C语言中,打开设备文件通常是通过文件路径来实现的,类似于打开普通的文件。设备文件可以是串口、并口、磁盘、字符设备等。下面是一个简单的示例,演示如何在Linux系统中打开串口设备文件。
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
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
int main() {
const char *devicePath = "/dev/ttyS0"; // 串口设备文件路径
// 使用 open() 函数打开串口设备文件
int fd = open(devicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1) {
perror("Error opening device file");
return 1;
}
// 配置串口参数
struct termios serialConfig;
tcgetattr(fd, &serialConfig);
cfmakeraw(&serialConfig);
cfsetispeed(&serialConfig, B9600); // 输入波特率
cfsetospeed(&serialConfig, B9600); // 输出波特率
tcsetattr(fd, TCSANOW, &serialConfig);
// 使用设备文件进行读写操作
char buffer[256];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
if (bytesRead == -1) {
perror("Error reading device file");
close(fd);
return 1;
}
// 关闭设备文件
close(fd);
return 0;
}
在这个示例中,我们使用open()
函数打开了一个串口设备文件 “/dev/ttyS0”。然后,我们配置了串口参数,将串口设置为原始模式,设置输入和输出波特率。最后,我们使用read()
函数从设备文件中读取数据。
需要注意的是,示例中的代码是一个简化版,实际上,配置和使用设备文件需要考虑更多细节,如错误处理、参数配置、数据处理等。同时,不同的设备文件可能有不同的配置和操作方式,你需要根据具体设备的文档和要求来调整代码。
此外,使用open()
函数打开设备文件时,需要具有足够的权限。在Linux系统中,通常需要使用root权限或者将用户添加到相应的设备组中。
std::cout “\033[36m” 是什么意思
在C++中,std::cout
是标准输出流对象,用于将文本数据打印到控制台。\033[36m
是一种 ANSI 转义序列,用于在终端中设置文本颜色。具体来说,\033
表示转义字符,[36m
是用于设置文本颜色的 ANSI 转义码。[36m
表示设置文本颜色为青色(cyan)。
所以,如果您在使用 std::cout
时添加 \033[36m
,则之后打印在终端的文本将会以青色显示。
示例代码:
1
2
3
4
5
6
7
8
#include <iostream>
int main()
{
std::cout << "\033[36mThis text is in cyan color.\033[0m" << std::endl;
std::cout << "This text is in the default color." << std::endl;
return 0;
}
在这个示例中,第一行的文本将以青色显示,第二行的文本将恢复为默认颜色。请注意,为了恢复文本颜色到默认,我们在末尾使用了 ANSI 转义序列 \033[0m
。
libmagic 测试示例
在 C++ 中使用 libmagic
库进行文件类型识别的示例需要以下步骤:
-
安装
libmagic
库: 在使用libmagic
库之前,您需要先在系统中安装该库。在大多数 Linux 发行版中,您可以使用包管理器进行安装。在 Windows 上,可以使用预编译的库或从源代码构建。 -
创建 C++ 项目: 创建一个新的 C++ 项目,例如使用 Visual Studio、Code::Blocks 或其他 IDE。
-
引入
libmagic
头文件: 在您的 C++ 代码中引入libmagic
的头文件。
1
#include <magic.h>
- 初始化
libmagic
: 在使用libmagic
之前,需要初始化库的上下文。可以使用magic_open
函数来初始化。
1
2
3
4
magic_t magicCookie = magic_open(MAGIC_MIME_TYPE | MAGIC_NO_CHECK_COMPRESS);
if (magicCookie == NULL) {
// 处理初始化失败的情况
}
- 设置
libmagic
数据库路径(可选): 默认情况下,libmagic
使用系统提供的魔术数据库文件。如果需要,您可以使用magic_load
函数加载自定义的魔术数据库文件。
1
2
3
if (magic_load(magicCookie, NULL) != 0) {
// 处理加载数据库失败的情况
}
- 识别文件类型:
使用
magic_file
函数来识别文件类型。该函数将返回文件的 MIME 类型。
1
2
3
4
5
6
7
const char* filename = "path/to/your/file";
const char* mimeType = magic_file(magicCookie, filename);
if (mimeType == NULL) {
// 处理识别文件类型失败的情况
} else {
std::cout << "File MIME type: " << mimeType << std::endl;
}
- 关闭
libmagic
: 在使用完成后,记得关闭libmagic
上下文。
1
magic_close(magicCookie);
完整的示例代码如下:
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
#include <iostream>
#include <magic.h>
int main() {
magic_t magicCookie = magic_open(MAGIC_MIME_TYPE | MAGIC_NO_CHECK_COMPRESS);
if (magicCookie == NULL) {
std::cerr << "Failed to initialize libmagic" << std::endl;
return 1;
}
if (magic_load(magicCookie, NULL) != 0) {
std::cerr << "Failed to load magic database" << std::endl;
magic_close(magicCookie);
return 1;
}
const char* filename = "path/to/your/file";
const char* mimeType = magic_file(magicCookie, filename);
if (mimeType == NULL) {
std::cerr << "Failed to determine file type" << std::endl;
} else {
std::cout << "File MIME type: " << mimeType << std::endl;
}
magic_close(magicCookie);
return 0;
}
请确保根据您的系统和编译环境调整上述代码,以及正确链接 libmagic
库。此示例演示了如何使用 libmagic
库来识别文件类型并输出其 MIME 类型。
rmdir 删除目录失败
如果在使用C++的rmdir()
函数时删除目录失败,可能是由于以下原因之一:
-
权限问题:确保您的程序有足够的权限来删除目录。删除目录需要具有对目录及其上级目录的写权限。您可以使用
chmod
命令来修改目录的权限。 -
目录非空:
rmdir()
函数只能删除空目录。如果目录中包含文件或其他子目录,rmdir()
将失败。在删除目录之前,您需要确保目录为空。您可以使用递归方式删除目录中的所有文件和子目录,然后再尝试使用rmdir()
删除空目录。
下面是一个示例代码,演示了如何使用递归方式删除目录及其内容:
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
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
bool removeDirectory(const std::string& directoryPath) {
DIR* dir = opendir(directoryPath.c_str());
if (!dir) {
std::cerr << "Failed to open directory: " << directoryPath << std::endl;
return false;
}
struct dirent* entry;
while ((entry = readdir(dir))) {
std::string fileName = entry->d_name;
if (fileName != "." && fileName != "..") {
std::string filePath = directoryPath + "/" + fileName;
if (entry->d_type == DT_DIR) {
if (!removeDirectory(filePath)) {
closedir(dir);
return false;
}
} else {
if (unlink(filePath.c_str()) != 0) {
std::cerr << "Failed to delete file: " << filePath << std::endl;
closedir(dir);
return false;
}
}
}
}
closedir(dir);
if (rmdir(directoryPath.c_str()) != 0) {
std::cerr << "Failed to delete directory: " << directoryPath << std::endl;
return false;
}
return true;
}
int main() {
const std::string directoryPath = "/path/to/directory";
if (removeDirectory(directoryPath)) {
std::cout << "Directory deleted: " << directoryPath << std::endl;
} else {
std::cerr << "Failed to delete directory: " << directoryPath << std::endl;
}
return 0;
}
在上述示例中,我们定义了一个removeDirectory()
函数,使用递归方式删除目录及其内容。函数首先打开目录,然后遍历目录中的每个条目。对于每个条目,如果是子目录,则递归调用removeDirectory()
函数来删除子目录及其内容;如果是文件,则使用unlink()
函数删除文件。最后,使用rmdir()
函数删除空目录。
请注意,上述代码只是一个示例,您可能需要根据实际情况进行修改和调整。
希望以上信息能够帮助您解决删除目录失败的问题。如有其他问题,请随时提问。
删除文件
在Linux的C++程序中删除文件,您可以使用标准库函数std::remove
或者std::remove_all
。下面是示例代码:
使用std::remove
删除单个文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <cstdio>
int main() {
const char* filePath = "/path/to/file.txt"; // 指定文件路径
if (std::remove(filePath) != 0) {
std::cerr << "Failed to delete file: " << filePath << std::endl;
} else {
std::cout << "File deleted: " << filePath << std::endl;
}
return 0;
}
删除目录文件
在C++11标准中,没有提供直接删除目录的标准库函数。然而,您可以使用操作系统的相关函数来删除目录和文件。对于Linux系统,您可以使用rmdir
函数来删除空目录,使用unlink
函数来删除文件。以下是一个示例代码:
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
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
bool removeFile(const std::string& filePath) {
if (unlink(filePath.c_str()) != 0) {
std::cerr << "Failed to delete file: " << filePath << std::endl;
return false;
}
return true;
}
bool removeDirectory(const std::string& directoryPath) {
if (rmdir(directoryPath.c_str()) != 0) {
std::cerr << "Failed to delete directory: " << directoryPath << std::endl;
return false;
}
return true;
}
int main() {
const std::string filePath = "/path/to/file.txt";
const std::string directoryPath = "/path/to/directory";
if (removeFile(filePath)) {
std::cout << "File deleted: " << filePath << std::endl;
}
if (removeDirectory(directoryPath)) {
std::cout << "Directory deleted: " << directoryPath << std::endl;
}
return 0;
}
在上述示例中,我们定义了两个函数removeFile
和removeDirectory
,分别用于删除文件和目录。这些函数使用unlink
和rmdir
函数来执行删除操作。如果删除操作成功,函数返回true
,否则返回false
。
在main
函数中,我们指定要删除的文件路径和目录路径,并调用相应的函数进行删除。如果删除操作成功,我们输出相应的成功消息,否则输出错误消息。
请注意,上述代码中使用了unistd.h
和sys/stat.h
头文件,这些头文件提供了对操作系统的底层函数的访问。此外,删除操作需要适当的文件系统权限,确保您的程序具有足够的权限来执行删除操作。
希望这可以帮助您在C++11中删除目录和文件。如有其他问题,请随时提问。
linux 获取内存信息
在Linux上,可以使用 sys/sysinfo.h
头文件中的 sysinfo
结构和函数来获取系统内存信息。下面是一个C++示例代码,演示如何在Linux下获取可用内存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <sys/sysinfo.h>
#include <iostream>
int main() {
struct sysinfo sys_info;
if (sysinfo(&sys_info) != -1) {
long long total_memory = sys_info.totalram * sys_info.mem_unit;
long long free_memory = sys_info.freeram * sys_info.mem_unit;
long long used_memory = total_memory - free_memory;
std::cout << "Total Memory: " << total_memory << " bytes" << std::endl;
std::cout << "Free Memory: " << free_memory << " bytes" << std::endl;
std::cout << "Used Memory: " << used_memory << " bytes" << std::endl;
} else {
std::cerr << "Failed to retrieve system memory information." << std::endl;
}
return 0;
}
该代码使用 sysinfo()
函数从系统获取内存信息,并将总内存大小、可用内存大小和已使用内存大小打印出来。请注意,sysinfo()
函数返回的内存大小单位是字节(bytes)。如果需要以更友好的方式显示内存大小,你可能需要对其进行单位转换(例如以MB或GB为单位)。
编译并运行此程序,将输出类似以下内容:
1
2
3
Total Memory: 16729580544 bytes
Free Memory: 578932736 bytes
Used Memory: 16150647808 bytes
请注意,sysinfo()
函数返回的内存信息是系统整体的统计信息,可能与实际应用程序可用的内存有所不同。如果需要更精确的内存信息,可以考虑使用特定的第三方库或系统调用。
std::map erase() 详解
std::map
是 C++ 标准库中的关联容器之一,它提供了一种键值对的映射关系。std::map
中的元素按照键的顺序进行排序,并且每个键只能出现一次。
std::map
提供了 erase()
成员函数,用于删除指定的元素或一定范围内的元素。erase()
的使用方式有以下几种形式:
- 删除单个元素:
1
iterator erase(iterator pos);
这种形式的
erase()
接受一个迭代器pos
,它指向要删除的元素,并返回一个指向下一个元素的迭代器。 - 删除指定键的元素:
1
size_type erase(const key_type& key);
这种形式的
erase()
接受一个键key
,它指定要删除的元素的键,并返回删除的元素数量(0 或 1)。 - 删除指定范围的元素:
1
iterator erase(iterator first, iterator last);
这种形式的
erase()
接受两个迭代器first
和last
,它们指定要删除的元素范围(包括first
,不包括last
),并返回一个指向下一个元素的迭代器。
下面是一个使用 erase()
的示例:
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
#include <iostream>
#include <map>
int main() {
std::map<int, std::string> map;
map[1] = "One";
map[2] = "Two";
map[3] = "Three";
map[4] = "Four";
// 删除指定键的元素
map.erase(2);
// 删除范围内的元素
auto it = map.find(3);
if (it != map.end()) {
map.erase(it, map.end());
}
// 输出剩余的元素
for (const auto& pair : map) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
在上述示例中,我们创建了一个 std::map
对象,并插入了一些键值对。然后使用 erase()
删除了键为 2 的元素,以及从键为 3 的元素开始的所有元素。最后,我们遍历剩余的元素并进行输出。
需要注意的是,当使用 erase()
删除元素时,std::map
中的迭代器会失效。所以在删除后继续使用已失效的迭代器是不安全的。
希望这个解释对你有帮助!如有需要,请随时提问。
std:;map erase() std::remove_if()
std::remove_if()
是 C++ 标准库中的一个算法函数,用于从容器中移除满足指定条件的元素。然而,对于关联容器 std::map
来说,它的元素是按照键的顺序进行排序的,并且每个键只能出现一次,因此无法直接使用 std::remove_if()
函数来删除 std::map
中的元素。
要删除 std::map
中满足条件的元素,可以使用以下步骤:
- 使用迭代器遍历
std::map
。 - 根据条件判断,将要删除的元素的键记录下来。
- 使用
std::map
的成员函数erase()
删除记录的键对应的元素。
下面是一个示例代码,演示如何使用 std::remove_if()
删除 std::map
中的满足条件的元素:
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
#include <iostream>
#include <map>
#include <algorithm>
int main() {
std::map<int, std::string> myMap;
myMap[1] = "One";
myMap[2] = "Two";
myMap[3] = "Three";
myMap[4] = "Four";
myMap[5] = "Five";
// 定义要删除的元素的条件
auto isEvenKey = [](const std::pair<int, std::string>& pair) { return pair.first % 2 == 0; };
// 记录要删除的键
std::vector<int> keysToRemove;
for (auto it = myMap.begin(); it != myMap.end(); ++it) {
if (isEvenKey(*it)) {
keysToRemove.push_back(it->first);
}
}
// 删除记录的键对应的元素
for (const auto& key : keysToRemove) {
myMap.erase(key);
}
// 输出结果
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
在示例中,我们定义了一个一元谓词 isEvenKey
,用于判断键是否为偶数。然后,使用迭代器遍历 std::map
,记录满足条件的键到 keysToRemove
容器中。最后,使用 erase()
函数删除 keysToRemove
容器中记录的键对应的元素。
需要注意的是,std::remove_if()
并不适用于 std::map
,因为它无法处理关联容器的特殊性质。因此,我们需要手动遍历 std::map
,判断条件并删除元素。
希望这个解释对你有帮助!如有需要,请随时提问。
std::map erase() 删除元素后 迭代器失效
在使用 erase()
函数删除元素后,原来的迭代器会失效,因为元素已经被删除,迭代器指向的位置不再有效。如果你需要在删除元素后继续遍历 std::map
,有几种方法可以解决迭代器失效的问题:
-
使用返回的迭代器:
erase()
函数返回一个指向下一个元素的迭代器。你可以将这个返回的迭代器用作下一次迭代的起始位置。1 2 3 4
auto it = map.find(key); if (it != map.end()) { it = map.erase(it); // 删除元素并更新迭代器 }
这样,即使在删除元素后,你仍然拥有一个有效的迭代器来继续遍历。
-
使用后置递增运算符: 后置递增运算符
++
可以使迭代器指向下一个元素,因此你可以在删除元素之前复制迭代器,然后再删除元素。1 2 3 4 5 6
for (auto it = map.begin(); it != map.end(); ) { auto current = it++; if (/* 满足删除条件 */) { map.erase(current); } }
这种方式可以确保在删除元素之前获得下一个有效的迭代器。
-
使用标准算法: 你也可以使用标准库中的算法来删除满足条件的元素,而不使用
erase()
函数。例如,可以使用std::remove_if
结合std::map
的成员函数erase()
进行元素删除。1
map.erase(std::remove_if(map.begin(), map.end(), /* 删除条件 */), map.end());
这种方式会在不失效迭代器的情况下删除元素。
无论你选择哪种方法,都需要确保在删除元素后不再使用失效的迭代器。另外,如果你在遍历 std::map
的过程中需要频繁地删除元素,考虑使用 std::unordered_map
(无序映射)来避免迭代器失效的问题。
希望这些解决方案对你有帮助!如有需要,请随时提问。
unix C++ 捕获信号
在 UNIX 系统中,可以使用 C++ 来捕获和处理信号。下面是一个示例代码,演示如何在 C++ 中捕获信号:
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
#include <iostream>
#include <csignal>
// 信号处理函数
void signalHandler(int signal)
{
std::cout << "Received signal: " << signal << std::endl;
// 进行信号处理操作
// 可以选择在这里终止程序
// exit(0);
}
int main()
{
// 注册信号处理函数
std::signal(SIGINT, signalHandler); // 捕获 Ctrl+C 信号
// 主循环或其他业务逻辑
while (true)
{
// 执行任务
}
return 0;
}
在上面的示例中,我们定义了一个名为 signalHandler
的函数作为信号处理函数。该函数会在接收到指定的信号时被调用。在 main
函数中,我们使用 std::signal
函数注册了 signalHandler
函数来捕获 SIGINT
信号,即 Ctrl+C 信号。
当程序运行时,如果收到 SIGINT
信号(通过按下 Ctrl+C),将会触发信号处理函数 signalHandler
,并在控制台输出收到的信号编号。
你可以根据需要注册其他信号和处理函数,例如 SIGTERM
(终止信号)等。
需要注意的是,信号处理是特定于操作系统的,并且在不同的操作系统上可能有所不同。因此,确保在目标系统上查阅相关文档以了解特定信号的定义和处理方式。
希望这个示例对你有帮助!如有需要,请随时提问。
C++11 操作符重载
在C++11中,可以通过成员函数或非成员函数的方式重载运算符。下面是一些常见的运算符重载示例:
1. 算术运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
class Number {
private:
int value;
public:
Number(int val) : value(val) {}
// 重载加法运算符
Number operator+(const Number& other) const {
return Number(value + other.value);
}
};
int main() {
Number num1(5);
Number num2(10);
Number result = num1 + num2; // 使用重载的加法运算符
std::cout << result.value << std::endl; // 输出结果: 15
return 0;
}
2. 关系运算符
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
#include <iostream>
class Number {
private:
int value;
public:
Number(int val) : value(val) {}
// 重载相等运算符
bool operator==(const Number& other) const {
return value == other.value;
}
};
int main() {
Number num1(5);
Number num2(10);
if (num1 == num2) { // 使用重载的相等运算符
std::cout << "相等" << std::endl;
} else {
std::cout << "不相等" << std::endl; // 输出结果: 不相等
}
return 0;
}
3. 输入/输出流运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>
class Person {
private:
std::string name;
public:
Person(const std::string& n) : name(n) {}
// 重载输出流运算符
friend std::ostream& operator<<(std::ostream& os, const Person& person) {
os << "姓名: " << person.name;
return os;
}
};
int main() {
Person p("张三");
std::cout << p << std::endl; // 使用重载的输出流运算符输出对象信息
return 0;
}
4. 下标运算符
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
#include <iostream>
class Array {
private:
int data[5];
public:
Array() {
for (int i = 0; i < 5; ++i) {
data[i] = i + 1;
}
}
// 重载下标运算符
int& operator[](int index) {
return data[index];
}
};
int main() {
Array arr;
std::cout << arr[2] << std::endl; // 输出结果: 3
arr[3] = 8; // 修改元素的值
std::cout << arr[3] << std::endl; // 输出结果: 8
return 0;
}
这只是一小部分运算符重载的示例,你可以根据需要重载其他运算符,以满足特定的编程需求。请注意,运算符重载应遵循适当的语义和约定,以确保代码的可读性和一致性。
C++11 年月日换算成秒
要将给定的年、月、日转换为时间戳(Unix时间戳),可以使用C++11中的std::chrono
库进行操作。下面是一个示例代码,它将输入的年、月、日转换为时间戳:
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
#include <iostream>
#include <chrono>
#include <ctime>
std::time_t getTimestamp(int year, int month, int day)
{
std::tm timeinfo = {};
timeinfo.tm_year = year - 1900; // 年份需要减去1900
timeinfo.tm_mon = month - 1; // 月份需要减去1
timeinfo.tm_mday = day;
std::time_t timestamp = std::mktime(&timeinfo);
return timestamp;
}
int main()
{
int year, month, day;
std::cout << "输入年份:";
std::cin >> year;
std::cout << "输入月份:";
std::cin >> month;
std::cout << "输入日期:";
std::cin >> day;
std::time_t timestamp = getTimestamp(year, month, day);
std::cout << "时间戳:" << timestamp << std::endl;
return 0;
}
在上面的示例中,我们定义了一个getTimestamp
函数,它接受年、月、日作为参数,并返回对应的时间戳。在函数中,我们首先构造了一个std::tm
结构体对象timeinfo
,并将年、月、日赋值给相应的成员变量。然后,我们使用std::mktime
函数将timeinfo
转换为时间戳。
在main
函数中,我们通过用户输入获取年、月、日,并调用getTimestamp
函数来获取时间戳,然后将其打印出来。
请注意,这个示例假设用户输入的年、月、日是有效的,并且是按照日历中的约定进行输入的。在实际应用中,你可能需要添加输入验证和错误处理来确保输入的正确性。
希望以上信息能对你有所帮助。如有任何进一步的疑问,请随时提问。
C++11 读写锁
C++11引入了读写锁(Read-Write Lock),它是一种特殊的锁机制,允许多个线程同时读取共享数据,但在写入数据时需要互斥保护。读写锁可以提高并发性能,特别适用于读操作远远超过写操作的情况。C++11中的读写锁由std::shared_mutex
类实现。
以下是使用C++11中的读写锁的基本操作:
-
创建读写锁对象:在需要使用读写锁的地方,创建一个
std::shared_mutex
对象。1
std::shared_mutex rwMutex;
-
读操作使用共享锁(Shared Lock):当需要对共享数据进行读操作时,使用
std::shared_lock
对象上锁。1 2 3
std::shared_lock<std::shared_mutex> lock(rwMutex); // 读取共享数据的代码
共享锁允许多个线程同时持有锁,并发读取共享数据。多个读操作可以同时进行,互不干扰。
-
写操作使用独占锁(Exclusive Lock):当需要对共享数据进行写操作时,使用
std::unique_lock
对象上锁。1 2 3
std::unique_lock<std::shared_mutex> lock(rwMutex); // 写入共享数据的代码
独占锁只允许一个线程持有锁,其他线程需要等待锁释放才能进行写操作。
-
解锁:使用
unlock
函数解锁读写锁。1
lock.unlock();
或者,
std::shared_lock
和std::unique_lock
对象的析构函数会在作用域结束时自动释放锁。
读写锁的使用示例:
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 <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex rwMutex;
int sharedData = 0;
void reader()
{
std::shared_lock<std::shared_mutex> lock(rwMutex);
// 读取共享数据
std::cout << "Reader: Shared data = " << sharedData << std::endl;
}
void writer()
{
std::unique_lock<std::shared_mutex> lock(rwMutex);
// 写入共享数据
sharedData++;
std::cout << "Writer: Shared data incremented to " << sharedData << std::endl;
}
int main()
{
std::thread t1(reader);
std::thread t2(writer);
std::thread t3(reader);
t1.join();
t2.join();
t3.join();
return 0;
}
在上述示例中,reader
函数表示读操作,writer
函数表示写操作。读操作使用共享锁,写操作使用独占锁。在main
函数中,我们创建了一个读线程和两个写线程,它们并发访问共享数据。
通过使用读写锁,我们可以实现读操作的并发性,而保证写操作的互斥性。这样可以提高多线程环境下的性能。
希望以上信息对你有所帮助。如有进一步的疑问,请随时提问。
告警时间间隔 业务逻辑
当涉及业务告警时间间隔时,实际的实现方式将取决于具体的业务需求和应用场景。下面提供一个示例来详细解释如何在C++中实现业务告警时间间隔。
假设我们有一个简单的业务场景:在某个服务的请求处理过程中,如果出现异常情况,我们希望以一定的时间间隔触发告警。以下是一个基本的示例代码:
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
#include <iostream>
#include <chrono>
#include <ctime>
#include <thread>
const int ALERT_INTERVAL_SECONDS = 60; // 告警时间间隔为60秒
std::time_t lastAlertTime = 0; // 上次告警时间的初始值为0
void processRequest()
{
// 模拟请求处理过程
bool hasError = true; // 假设出现异常情况
if (hasError)
{
std::time_t currentTime = std::time(nullptr); // 获取当前时间
if (currentTime - lastAlertTime > ALERT_INTERVAL_SECONDS)
{
// 满足告警条件,进行告警操作
std::cout << "Alert: An error occurred!" << std::endl;
// 更新上次告警时间为当前时间
lastAlertTime = currentTime;
}
}
}
int main()
{
while (true)
{
processRequest();
// 暂停一段时间
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
在上述示例中,processRequest
函数表示请求处理过程,如果出现异常情况(这里通过hasError
来模拟),则判断距离上次告警时间是否大于告警时间间隔。如果大于间隔,则触发告警操作,并更新上次告警时间为当前时间。
在main
函数中,我们通过一个无限循环来模拟持续的请求处理过程。在每次循环中,我们调用processRequest
函数处理请求,并在每次处理之间暂停一段时间(这里使用std::this_thread::sleep_for
函数暂停1秒)。
该示例中的告警时间间隔为60秒,如果在60秒内连续出现异常情况,将只触发一次告警操作。
请注意,这只是一个基本的示例来说明如何实现业务告警时间间隔,实际中你可能需要根据具体的业务需求进行调整和优化。例如,你可以将告警操作封装为一个函数,结合日志记录和通知机制,以满足实际的告警需求。
希望以上示例能够帮助你理解在C++中实现业务告警时间间隔的过程。如有进一步的疑问,请随时提问。
std::mutex 同一个对象 锁定多个临界区 会有冲突吗
在C++11中,std::mutex
是可重入的,也就是说,同一个线程可以多次对同一个std::mutex
对象进行上锁操作,而不会发生冲突。这是因为std::mutex
会跟踪上锁的次数,只有对应数量的解锁操作执行后,其他线程才能获得该互斥量的锁。
下面是一个示例来说明同一个对象锁定多个临界区不会发生冲突的情况:
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
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
void criticalSection()
{
mtx.lock();
std::cout << "Enter critical section." << std::endl;
// 执行一些需要保护的操作...
mtx.unlock();
std::cout << "Exit critical section." << std::endl;
}
int main()
{
std::thread t1(criticalSection);
std::thread t2(criticalSection);
t1.join();
t2.join();
return 0;
}
在上面的示例中,两个线程t1
和t2
都调用了criticalSection
函数,该函数内部对同一个std::mutex
对象mtx
进行了上锁和解锁操作。由于std::mutex
是可重入的,所以同一个线程可以多次对其进行上锁操作,而不会产生冲突。
重要的是要注意,虽然同一个对象可以锁定多个临界区,但仍然需要确保在每个临界区的结束处进行相应数量的解锁操作,以保持互斥量的正确状态。否则,未解锁的临界区可能会导致死锁或其他并发错误。
希望这个解答能够帮助你理解在C++11中使用std::mutex
锁定多个临界区的情况。如有任何进一步的疑问,请随时提问。
gprof 性能分析 程序剖析
在Linux环境下,使用gprof进行C++程序的性能分析与使用方法类似于前面提到的通用gprof用法。下面是关于在Linux中使用gprof进行C++程序性能分析的详细解释:
-
编译时启用性能计数器:与使用gprof分析C程序一样,在编译C++程序时也需要使用特殊的编译选项启用性能计数器。使用g++编译器时,可以添加”-pg”选项。例如:
1
$ g++ -pg -o myprogram myprogram.cpp
-
运行程序:编译完成后,运行生成的可执行文件。程序执行过程中会记录性能数据。
1
$ ./myprogram
-
生成gmon.out文件:当程序运行完毕后,会生成一个名为”gmon.out”的文件,其中包含了程序的性能数据。
-
生成性能报告:使用gprof工具来分析性能数据并生成性能报告。使用以下命令生成报告:
1
$ gprof myprogram gmon.out > report.txt
这将生成一个名为”report.txt”的文件,其中包含了函数级别的性能报告。
-
分析性能报告:打开生成的性能报告文件,你将看到与前述相似的内容,包括每个函数的调用次数和占总运行时间的百分比,每个函数内部花费的时间,以及函数调用关系图。
与分析C程序相比,对于C++程序,性能分析结果可能会更加复杂,因为C++中涉及到了对象的构造和析构,以及虚函数的动态调用等特性。因此,在分析C++程序时,除了关注函数级别的性能数据外,还需要注意类和对象的性能开销。
需要注意的是,gprof的性能分析结果是基于采样数据得出的,可能存在一定的误差。为了获得更准确的性能分析结果,建议在优化前后进行多次运行,并对多次运行结果取平均值。
希望以上解释能够帮助你理解如何在Linux中使用gprof进行C++程序的性能分析。如果你还有其他问题,请随时提问。
C++11 自然时间
在C++中,你可以使用标准库提供的时间相关函数和类来处理自然时间。下面是一些常用的C++函数和类,用于处理自然时间:
-
time() 函数:
time()
函数返回当前时间的秒数,自1970年1月1日以来的时间戳。可以使用<ctime>
头文件来访问该函数。示例:
1 2 3 4 5 6 7 8
#include <iostream> #include <ctime> int main() { std::time_t currentTime = std::time(nullptr); std::cout << "Current timestamp: " << currentTime << std::endl; return 0; }
-
std::tm 结构体:
std::tm
是一个表示时间的结构体,包含了年、月、日、时、分、秒等字段。可以使用<ctime>
头文件来访问该结构体。示例:
1 2 3 4 5 6 7 8 9 10 11
#include <iostream> #include <ctime> int main() { std::time_t currentTime = std::time(nullptr); std::tm* localTime = std::localtime(¤tTime); std::cout << "Current local time: " << localTime->tm_year + 1900 << "-" << localTime->tm_mon + 1 << "-" << localTime->tm_mday << " " << localTime->tm_hour << ":" << localTime->tm_min << ":" << localTime->tm_sec << std::endl; return 0; }
-
std::chrono 命名空间:
<chrono>
头文件提供了一组类和函数,用于处理时间间隔和时钟。它提供了高精度的计时功能和各种时间单位的表示。示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <iostream> #include <chrono> int main() { auto currentTime = std::chrono::system_clock::now(); std::time_t currentTimeStamp = std::chrono::system_clock::to_time_t(currentTime); std::cout << "Current timestamp: " << currentTimeStamp << std::endl; std::chrono::system_clock::time_point oneHourLater = currentTime + std::chrono::hours(1); std::time_t oneHourLaterStamp = std::chrono::system_clock::to_time_t(oneHourLater); std::cout << "One hour later: " << std::ctime(&oneHourLaterStamp) << std::endl; return 0; }
上述示例演示了使用 <chrono>
头文件中的类和函数获取当前时间戳,以及进行时间间隔的计算。
这些是C++中处理自然时间的一些常用工具和类。使用这些函数和类,你可以获取当前时间、进行时间戳的转换,以及计算时间间隔和操作。
C++11 解析main函数从命令行接收的参数
在 C++ 中,你可以通过 main
函数的参数来解析命令行输入。main
函数接受两个参数,一个是 argc
,表示命令行参数的数量,另一个是 argv
,是一个指向指针数组的指针,其中每个指针指向一个命令行参数的字符串。
下面是一个示例代码,展示如何解析 main
输入参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
int main(int argc, char* argv[]) {
// 输出命令行参数数量
std::cout << "argc: " << argc << std::endl;
// 输出每个命令行参数
for (int i = 0; i < argc; ++i) {
std::cout << "argv[" << i << "]: " << argv[i] << std::endl;
}
return 0;
}
在上述示例中,我们通过遍历 argv
数组来访问每个命令行参数,并使用 argc
获取参数数量。你可以根据需要进一步处理命令行参数,例如解析选项、获取参数值等。
下面是一个示例,展示如何解析命令行选项和参数值:
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
#include <iostream>
int main(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
std::cout << "Help message" << std::endl;
} else if (arg == "-v" || arg == "--version") {
std::cout << "Version 1.0" << std::endl;
} else if (arg == "-f" || arg == "--file") {
// 检查下一个参数是否存在
if (i + 1 < argc) {
std::string filename = argv[i + 1];
std::cout << "File: " << filename << std::endl;
++i; // 跳过下一个参数
} else {
std::cerr << "Missing filename argument" << std::endl;
}
} else {
std::cerr << "Unknown option: " << arg << std::endl;
}
}
return 0;
}
在上述示例中,我们检查每个命令行参数,根据不同的选项执行相应的操作。例如,如果参数是 -h
或 --help
,我们输出帮助信息;如果参数是 -f
或 --file
,我们检查下一个参数是否存在,并输出文件名。
你可以根据实际需求扩展这个示例,并根据需要处理更多的命令行选项和参数值。
C++11 “2023-6-21 10:15:2”日期字符串 取出年月日
要从日期字符串 “2023-6-21 10:15:2” 中提取年、月、日,可以使用C++中的字符串处理函数和算法,以及字符串流。以下是一个示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>
#include <sstream>
int main() {
std::string str = "2023-6-21 10:15:2";
std::istringstream iss(str);
std::string year, month, day;
// 提取年月日
std::getline(iss, year, '-');
std::getline(iss, month, '-');
std::getline(iss, day, ' ');
std::cout << "提取的年月日为:" << year << "-" << month << "-" << day << std::endl;
return 0;
}
在上面的代码中,我们使用std::istringstream将日期字符串转化为字符串流,然后使用std::getline函数和’-‘和’ ‘作为分隔符,逐行读取字符串流中的内容,分别存储到year、month和day字符串中。最后,我们输出提取到的年月日。
运行代码后,输出结果将是:2023-6-21
请注意,以上代码只是简单示例,假设日期字符串的格式固定且正确。如果实际情况与示例不符,你可能需要根据具体需求进行适当的调整和处理。另外,对于日期和时间的处理,你也可以考虑使用C++标准库中的日期时间库,如<chrono>
和<ctime>
,以便进行更为灵活和全面的日期时间操作。
C++11 从路径中提取文件名和路径
在 C++11 中,可以使用一些字符串操作函数和库来从路径中提取文件名和路径。下面是一个示例代码,展示如何在 C++11 中从路径中提取文件名和路径:
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
#include <iostream>
#include <string>
// 从路径中提取文件名
std::string extractFilename(const std::string& path) {
size_t lastSlash = path.find_last_of('/');
if (lastSlash != std::string::npos) {
return path.substr(lastSlash + 1);
}
return path;
}
// 从路径中提取路径部分
std::string extractPath(const std::string& path) {
size_t lastSlash = path.find_last_of('/');
if (lastSlash != std::string::npos) {
return path.substr(0, lastSlash);
}
return "";
}
int main() {
std::string filePath = "/home/user/Documents/example.txt";
// 提取文件名
std::string filename = extractFilename(filePath);
std::cout << "文件名: " << filename << std::endl;
// 提取路径
std::string path = extractPath(filePath);
std::cout << "路径: " << path << std::endl;
return 0;
}
在上述代码中,我们定义了两个函数 extractFilename()
和 extractPath()
,分别用于提取文件名和路径。这些函数使用了 std::string
类型的操作函数,如 find_last_of()
和 substr()
,来进行字符串处理。
运行上述代码将输出以下结果:
1
2
文件名: example.txt
路径: /home/user/Documents
请注意,这只是一种在 C++11 中从路径中提取文件名和路径的方法之一。这种方法依赖于字符串操作,可能不如 C++17 中的 <filesystem>
头文件提供的功能丰富和方便。如果你的项目可以使用 C++17 或更高版本的标准,那么推荐使用 <filesystem>
头文件来处理文件路径。
C++17 从路径中提取文件名和路径
在 C++ 中,你可以使用标准库中的<filesystem>
头文件来提取文件名和路径。<filesystem>
是在 C++17 中引入的,并提供了一组函数和类来操作文件系统路径。
下面是一个示例代码,展示如何从路径中提取文件名和路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path filePath = "/home/user/Documents/example.txt";
// 提取文件名
std::string filename = filePath.filename().string();
std::cout << "文件名: " << filename << std::endl;
// 提取路径
std::string path = filePath.parent_path().string();
std::cout << "路径: " << path << std::endl;
return 0;
}
在上述代码中,我们首先定义了一个路径 filePath
,然后使用 filename()
函数来提取文件名,parent_path()
函数来提取路径。最后,我们将文件名和路径打印输出。
运行上述代码将输出以下结果:
1
2
文件名: example.txt
路径: /home/user/Documents
这样,你就可以从路径中提取文件名和路径,以便进一步在 C++ 程序中使用。请确保在编译时启用了对 C++17 标准的支持,以便使用 <filesystem>
头文件。
通过进程号 杀死指定进程
在Linux中,可以使用C++编写程序来杀死指定的进程。以下是一个简单的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <unistd.h>
#include <signal.h>
#include <iostream>
int main() {
pid_t pid = 12345; // 要杀死的进程的PID
// 发送SIGKILL信号来终止指定的进程
if (kill(pid, SIGKILL) == 0) {
std::cout << "进程已成功终止" << std::endl;
} else {
std::cerr << "无法终止进程" << std::endl;
}
return 0;
}
在示例中,pid
变量存储了要杀死的进程的PID,可以根据需要修改为实际的PID。然后,使用 kill()
函数向指定的进程发送 SIGKILL
信号来终止进程。如果 kill()
函数返回值为0,表示成功终止进程;如果返回值为-1,表示无法终止进程。
请注意,杀死进程需要足够的权限。如果您的程序没有足够的权限,可能会导致无法终止指定的进程。
通过进程名 查找进程id
- 使用popen函数和pgrep命令来执行进程名的查找,并读取输出获取进程ID
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
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
pid_t findProcessIdByName(const char* processName) {
char command[256];
sprintf(command, "pgrep -f %s", processName);
FILE* fp = popen(command, "r");
if (fp != nullptr) {
char buffer[16];
if (fgets(buffer, sizeof(buffer), fp)) {
pclose(fp);
return atoi(buffer);
}
pclose(fp);
}
return -1;
}
int main ()
{
std::string program_name {"vca.exe"};
pid_t p;
p = findProcessIdByName(program_name.c_str());
std::cerr << "Program : " << program_name << ", pid: " << p << std::endl;
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
#include <iostream>
#include <vector>
#include <random>
std::string generateRandomMixedString(int size) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(0, 2);
std::string randomString;
randomString.reserve(size);
for (int i = 0; i < size; ++i) {
int choice = dist(gen);
if (choice == 0) {
// 生成数字字符
std::uniform_int_distribution<int> numDist('0', '9');
randomString.push_back(static_cast<char>(numDist(gen)));
} else if (choice == 1) {
// 生成大写字母字符
std::uniform_int_distribution<int> upperDist('A', 'Z');
randomString.push_back(static_cast<char>(upperDist(gen)));
} else {
// 生成小写字母字符
std::uniform_int_distribution<int> lowerDist('a', 'z');
randomString.push_back(static_cast<char>(lowerDist(gen)));
}
}
return randomString;
}
int main() {
int size = 10;
std::string randomString = generateRandomMixedString(size);
// 打印生成的随机数字和字符混合序列
std::cout << randomString << std::endl;
return 0;
}
在上述代码中,generateRandomMixedString()
函数使用std::random_device
来获取真正的随机数种子,然后使用std::mt19937
作为随机数引擎,并使用std::uniform_int_distribution
定义一个均匀分布的整数分布,范围为0到2。通过这个分布生成一个随机数,根据不同的随机数选择生成数字字符、大写字母字符或小写字母字符,并将其追加到std::string
中。最后,返回生成的随机数字和字符混合序列。
在main()
函数中,指定了字符序列的大小,并调用generateRandomMixedString()
函数来生成随机数字和字符混合序列。然后,通过打印输出生成的随机数字和字符混合序列。
std::queue 清空队列
要清空C++中的std::queue
队列,可以使用以下方法:
- 使用
std::queue
的swap()
函数:1 2
std::queue<T> emptyQueue; std::swap(queue, emptyQueue);
这种方法创建一个空的临时队列
emptyQueue
,然后通过调用std::swap()
函数将原始队列queue
与空队列交换。由于交换后,临时队列会被销毁,因此原始队列也会变为空。 - 循环弹出元素直到队列为空:
1 2 3
while (!queue.empty()) { queue.pop(); }
这种方法使用一个循环来连续地从队列中弹出元素,直到队列为空为止。通过循环弹出操作,可以逐个清空队列中的元素。
这两种方法都可以有效地清空std::queue
队列中的所有元素。根据具体情况选择合适的方法来清空队列。
需要注意的是,无论使用哪种方法,清空队列后,队列的大小将变为0,但是队列的容量(底层数据结构的大小)可能不会改变。如果需要释放底层数据结构所占用的内存,可以考虑重新创建一个新的队列对象。
回调函数 编程技巧 示例
以下是使用C++回调函数的一些示例:
示例 1: 使用函数指针作为回调函数
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
#include <iostream>
// 定义回调函数类型
typedef void (*CallbackFunc)(int);
// 接受回调函数作为参数的函数
void performOperation(int data, CallbackFunc callback) {
// 执行某些操作
// ...
// 调用回调函数
callback(data);
}
// 回调函数
void callbackFunction(int data) {
std::cout << "Callback executed with data: " << data << std::endl;
}
int main() {
int data = 42;
// 调用函数并传递回调函数
performOperation(data, callbackFunction);
return 0;
}
示例 2: 使用Lambda表达式作为回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
// 接受回调函数作为参数的函数
void performOperation(int data, const std::function<void(int)>& callback) {
// 执行某些操作
// ...
// 调用回调函数
callback(data);
}
int main() {
int data = 42;
// 使用Lambda表达式定义回调函数
auto callback = [](int data) {
std::cout << "Callback executed with data: " << data << std::endl;
};
// 调用函数并传递Lambda表达式作为回调函数
performOperation(data, callback);
return 0;
}
示例 3: 使用函数对象作为回调函数
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
#include <iostream>
// 定义回调函数的函数对象
class CallbackFunctor {
public:
void operator()(int data) {
std::cout << "Callback executed with data: " << data << std::endl;
}
};
// 接受回调函数作为参数的函数
void performOperation(int data, const CallbackFunctor& callback) {
// 执行某些操作
// ...
// 调用回调函数
callback(data);
}
int main() {
int data = 42;
// 创建回调函数对象
CallbackFunctor callback;
// 调用函数并传递回调函数对象
performOperation(data, callback);
return 0;
}
这些示例展示了不同的回调函数用法,包括使用函数指针、Lambda表达式和函数对象作为回调函数。根据您的需求和编程风格,选择适当的方法来实现回调函数,并根据具体的应用场景来定义和使用回调函数。
回调函数 编程技巧 建议
当涉及到使用回调函数的编程技巧时,以下是一些实用的建议和技巧:
-
使用函数指针或函数对象:C++中回调函数可以通过函数指针或函数对象进行实现。函数指针是一种直接的方法,它允许您将函数的地址传递给其他函数作为回调。而函数对象(可调用对象)是一个类对象,它可以像函数一样被调用,并作为回调函数传递。选择函数指针或函数对象取决于您的需求和编程风格。
-
使用函数模板:使用函数模板可以使回调函数更加通用和灵活。通过模板参数,可以将不同类型的回调函数传递给函数模板,从而实现更广泛的适用性。这样可以减少代码的重复性,提高代码的可复用性。
-
使用Lambda表达式:Lambda表达式是C++11引入的一种匿名函数形式,它可以用作回调函数。Lambda表达式允许您在需要时定义并传递函数逻辑,而不需要单独定义函数或函数对象。它提供了一种更紧凑和方便的方式来编写回调函数,尤其是对于简单的逻辑或临时需求。
-
传递上下文信息:在回调函数中,有时候可能需要访问一些外部的上下文信息或数据。为了确保回调函数能够访问这些信息,可以通过函数参数或捕获列表将其传递给回调函数。这样,回调函数就可以访问和操作相关的上下文数据。
-
使用函数对象状态:函数对象作为回调函数时,可以存储状态信息,并在多次调用之间保留状态。这种状态可以在回调函数的不同调用之间共享,并且可以用于跟踪和处理状态相关的信息。
-
错误处理和异常安全性:在使用回调函数时,要注意错误处理和异常安全性。确保在回调函数中适当处理错误和异常,以避免潜在的问题。这可能包括对异常的捕获和处理,以及在出现错误时返回适当的错误码或执行相应的错误处理逻辑。
-
文档和清晰的接口设计:在使用回调函数的代码中,提供清晰的文档和接口设计是非常重要的。确保文档中清楚地描述回调函数的目的、参数和预期行为。同时,定义清晰的接口和回调函数签名,使得使用回调函数的代码更加易于理解和使用。
这些技巧可以帮助您更好地使用回调函数,在C++中实现灵活和可扩展的编程。根据您的具体需求和编程场景,选择适
当的技巧和方法来使用回调函数,并确保代码的可读性、可维护性和稳定性。
C++ write 将整数写入文件
- 可以使用
std::to_string()
函数将整数转换为字符串,然后依次获取字符串的每个字符,存入到std::vector<char>
容器中 -
使用
write()
函数,将容器中的数据写入即可 - 示例
```cpp
#include
#include
int main()
{
num = 4;
std::string num_string {};
std::vector
1
return 0; } ```
C write 将整数写入文件
在 C 语言中,使用 write()
函数可以将整数写入到文件描述符中。write()
函数的原型如下:
1
2
3
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
fd
是文件描述符,表示要写入的文件或套接字的标识符。buf
是指向要写入数据的缓冲区的指针。count
是要写入的字节数。
要将整数写入文件,需要将整数转换为字符串形式,然后将字符串写入文件。可以使用 sprintf()
函数将整数转换为字符串,然后将字符串传递给 write()
函数进行写入。下面是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <unistd.h>
int main() {
int number = 12345;
char buffer[20];
int bytes_written;
sprintf(buffer, "%d", number);
bytes_written = write(STDOUT_FILENO, buffer, strlen(buffer));
if (bytes_written == -1) {
perror("Error writing to file");
return 1;
}
return 0;
}
上述示例中,将整数 number
转换为字符串形式,并将字符串写入标准输出。STDOUT_FILENO
表示标准输出的文件描述符。请注意,在实际使用中,你需要将文件描述符替换为要写入的文件或套接字的有效文件描述符。
文件读取到内存,写入到新文件
在 C++11 中,可以使用 std::ifstream
类来读取文件内容,并使用 std::ofstream
类来写入文件内容。下面是一个示例代码,演示如何将文件读取到内存中,然后保存为新文件:
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
#include <iostream>
#include <fstream>
#include <vector>
// 读取文件内容到内存
std::vector<char> ReadFileToMemory(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open file: " << filename << std::endl;
return {};
}
// 获取文件大小
file.seekg(0, std::ios::end);
std::streampos fileSize = file.tellg();
file.seekg(0, std::ios::beg);
// 分配内存缓冲区
std::vector<char> buffer(fileSize);
// 读取文件内容到缓冲区
if (!file.read(buffer.data(), fileSize)) {
std::cerr << "Failed to read file: " << filename << std::endl;
return {};
}
return buffer;
}
// 将内存内容保存为新文件
bool SaveMemoryToFile(const std::vector<char>& data, const std::string& filename) {
std::ofstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open file for writing: " << filename << std::endl;
return false;
}
// 将内存内容写入文件
if (!file.write(data.data(), data.size())) {
std::cerr << "Failed to write file: " << filename << std::endl;
return false;
}
return true;
}
int main() {
std::string inputFile = "input.txt";
std::string outputFile = "output.txt";
// 读取文件到内存
std::vector<char> fileData = ReadFileToMemory(inputFile);
if (fileData.empty()) {
std::cerr << "Failed to read file: " << inputFile << std::endl;
return 1;
}
// 将内存内容保存为新文件
if (!SaveMemoryToFile(fileData, outputFile)) {
std::cerr << "Failed to save file: " << outputFile << std::endl;
return 1;
}
std::cout << "File saved successfully: " << outputFile << std::endl;
return 0;
}
在上述代码中,ReadFileToMemory
函数接收一个文件名作为参数,并返回一个 std::vector<char>
,其中包含了文件的内容。函数使用 std::ifstream
打开文件,并根据文件大小分配一个足够大的缓冲区。然后,它将文件内容读取到缓冲区中,并返回缓冲区。
SaveMemoryToFile
函数接收一个 std::vector<char>
和一个文件名作为参数,并将内存中的数据写入到指定文件中。函数使用 std::ofstream
打开文件,并将内存中的数据写入文件。
在 main
函数中,首先指定要读取的输入文件名和要保存的输出文件名。然后,调用 ReadFileToMemory
函数将
文件读取到内存中,并检查是否成功。最后,调用 SaveMemoryToFile
函数将内存中的数据保存为新文件,并检查是否成功。
请根据实际需求修改文件名和路径,并确保有读取和写入文件的权限。
递归创建目录
在C++中,可以使用递归函数来判断路径是否存在并创建目录。以下是一个示例代码,演示了如何递归判断路径并创建目录:
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
#include <iostream>
#include <sys/stat.h>
bool createDirectory(const std::string& path) {
// 检查路径是否存在
struct stat info;
if (stat(path.c_str(), &info) != 0) {
// 路径不存在,创建目录
if (mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0) {
std::cout << "Created directory: " << path << std::endl;
return true;
} else {
std::cerr << "Failed to create directory: " << path << std::endl;
return false;
}
} else if (info.st_mode & S_IFDIR) {
// 路径已存在且为目录
std::cout << "Directory already exists: " << path << std::endl;
return true;
} else {
// 路径已存在但不是目录
std::cerr << "Path already exists but not a directory: " << path << std::endl;
return false;
}
}
bool createDirectoriesRecursive(const std::string& path) {
size_t pos = 0;
std::string subPath;
while ((pos = path.find_first_of("/\\", pos + 1)) != std::string::npos) {
subPath = path.substr(0, pos);
if (!createDirectory(subPath)) {
return false;
}
}
return createDirectory(path);
}
int main() {
std::string path = "/path/to/my/directory";
if (createDirectoriesRecursive(path)) {
std::cout << "All directories created successfully!" << std::endl;
} else {
std::cerr << "Failed to create directories." << std::endl;
}
return 0;
}
上述代码中的createDirectory
函数用于判断单个路径是否存在并创建目录。createDirectoriesRecursive
函数使用递归方式,逐级判断并创建目录,如果某一级目录已存在,则跳过创建。在main
函数中,我们可以调用createDirectoriesRecursive
函数来创建目录。
请注意,代码中使用了sys/stat.h
头文件和相关的系统调用来判断路径是否存在和创建目录。此外,还使用了一些文件权限标志(S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH
),以确保创建的目录具有适当的权限。
异常捕获
在C++中,您可以使用多个catch
块来捕获不同类型的异常,并采取相应的处理措施。每个catch
块可以捕获一个特定类型的异常,并执行相应的代码块。
下面是一个示例,展示了如何在C++中使用多个catch
块:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try {
// 可能引发异常的代码
// ...
}
catch (ExceptionType1& ex) {
// 处理 ExceptionType1 类型的异常
// ...
}
catch (ExceptionType2& ex) {
// 处理 ExceptionType2 类型的异常
// ...
}
catch (...) {
// 处理其他类型的异常
// ...
}
在上述示例中,try
块中的代码可能会引发异常。如果发生异常,程序会尝试与每个catch
块中的异常类型进行匹配。如果匹配成功,则会执行对应的catch
块中的代码。如果没有找到匹配的catch
块,则异常会继续传播到上一级的try
块或最终导致程序终止。
请注意以下几点:
catch
块中的异常类型应该与实际可能引发的异常类型匹配。可以使用具体的异常类类型(如ExceptionType1
、ExceptionType2
)或其基类来捕获相关异常。catch (...)
是一个特殊的catch
块,用于捕获所有类型的异常。它可以用作最后一个catch
块,以处理未被前面的catch
块捕获的异常。但是,请谨慎使用它,因为它会捕获所有异常,包括标准库和其他代码中可能引发的异常,这可能会导致不可预测的行为。- 捕获异常时,可以访问异常对象并执行适当的处理操作,如打印错误消息、记录日志、回滚操作等。
- 可以根据需要嵌套使用
try-catch
块,以处理更复杂的异常情况。 需要注意的是,异常处理应该根据实际需求进行适当的设计,并遵循良好的异常处理原则,以确保程序的可靠性和稳定性。
std::function 与 函数指针 互换
在某些情况下,我们可能需要将 std::function
对象转换为函数指针,或者将函数指针转换为 std::function
对象。虽然直接的互相转换是不可行的,但可以通过一些技巧来实现类似的效果。
将 std::function
转换为函数指针可以使用 std::function
的 target
成员函数和函数指针类型的静态转换来实现。下面是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <functional>
void foo(int x) {
std::cout << "foo: " << x << std::endl;
}
int main() {
std::function<void(int)> func = foo; // 将函数指针赋值给 std::function 对象
void (*funcPtr)(int) = static_cast<void (*)(int)>(func.target<void(*)(int)>());
if (funcPtr != nullptr) {
funcPtr(42); // 调用函数指针,等同于调用 foo 函数
}
return 0;
}
在上面的示例中,我们首先将函数 foo
赋值给 std::function<void(int)>
对象 func
。然后,我们使用 func.target<void(*)(int)>()
来获取函数指针类型的目标对象,并使用静态转换将其赋值给函数指针变量 funcPtr
。通过调用 funcPtr(42)
,我们实际上调用了函数 foo
。
将函数指针转换为 std::function
对象可以使用 std::function
的构造函数来实现。下面是一个示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>
void foo(int x) {
std::cout << "foo: " << x << std::endl;
}
int main() {
void (*funcPtr)(int) = foo; // 函数指针
std::function<void(int)> func(funcPtr); // 将函数指针转换为 std::function 对象
func(42); // 调用 std::function 对象,等同于调用 foo 函数
return 0;
}
在上面的示例中,我们首先定义了一个函数指针 funcPtr
并将其初始化为函数 foo
。然后,我们使用 std::function<void(int)>
的构造函数将函数指针转换为 std::function
对象 func
。通过调用 func(42)
,我们实际上调用了函数 foo
。
需要注意的是,函数指针和 std::function
对象之间的转换可能会丢失某些功能或引入性能开销,因为它们在实现和语义上是不同的。因此,在进行这种转换时,需要仔细考虑相关的设计和性能问题。
链接静态库顺序问题
- 默认情况下要求被依赖的库放在依赖它的库后面,当一个程序或共享库依赖的静态库较多时,可能会陷入解决链接问题的坑中
- 如果对静态库不熟悉,需要结构nm等工具来解决顺序问题。
-
但也可以偷懒,不关心静态库的顺序问题,ld为此提供了start-group和end-group两个选项,让包含在这两者间的静态库顺序可以随意。
- 以CMake为例,假设程序x依赖三个静态库:libX1.a、libX2.a和libX3.a,而libX2.a又依赖libX1.a,libX3.a依赖libX2.a和libX1.a,正常情况下的CMakeLists.txt格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
add_executable( x x.cpp ) target_link_libraries( x libX1.a libX2.a libX3.a )
- 上面的写法libX1.a、libX2.a和libX3.a的顺序不能变,只能按上面的先后顺序。如果去掉顺序的烦恼和痛苦,可以采用如下的写法:
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
target_link_libraries( x -Wl,--start-group libX1.a libX3.a libX2.a -Wl,--end-group ) 或 target_link_libraries( x -Wl,--start-group libX3.a libX2.a libX1.a -Wl,--end-group )
- 前面说了start-group和end-group是ld的选项,是链接选项,不是gcc/g++的编译选项,直接命令行或其它编译方式也可以使用,比如命令行方式:
g++ -g -o x x.cpp -Wl,--start-group libX2.a libX1.a libX3.a -Wl,--end-group
-Wl
的使用
- -Wl表示后面的参数传递给链接器,其中l是linker的意思。
- 链接时指定共享库的搜索路径(类似于设置LD_LIBRARY_PATH):
-Wl,-rpath=/usr/local/abc:/data/abc
-
以上也可以分开写:
-Wl,-rpath=/usr/local/abc -Wl,-rpath=/data/abc
- 部分库链接它的静态库,部分库链接它的共享库:
-Wl,-static -lb -Wl,-call_shared -la -lz
- 指定链接器:
-Wl,-dynamic-linker /lib/ld-linux.so.2 -e _so_start
- 指定导出的符号:
-Wl,--export-dynamic,--version-script,exports.lds
-
指定共享库的soname:
-Wl,--export-dynamic,--version-script,exports.lds,-soname=libqhttpd.so
- -rpath 增加共享库搜索路径
- –retain-symbols-file表示不丢弃未定义的符号和需要重定位的符号
-
–export-dynamic 创建一个动态连接的可执行程序时, 把所有的符号加到动态符号表中
- 用“-Wl,-Bstatic”指定链接静态库,使用“-Wl,-Bdynamic”指定链接共享库,使用示例:
-Wl,-Bstatic -lmysqlclient_r -lssl -lcrypto -Wl,-Bdynamic -lrt -Wl,-Bdynamic -pthread -Wl,-Bstatic -lgtest
C/C++ 浮点数比较
- 首先,声明一个C/C++中非常重要的原则:
- 除了可以表示为2的幂次以及整数数乘的浮点数可以准确表示外,其余的数的值都是近似值。
- 一般解决方法:
- 保留指定精度,强制转成整型,在进行判断
- 代码
1 2 3 4 5
double r {0.016392123}; double m {0.0163922131}; int r2 = static_cast<int>(r * 1e6); int m2 = static_cast<int>(m * 1e6);
std::vector
- 初始化
std::vector<int> ids(10,-1);
- 参数:
- 10 , 初始化10个元素
- -1 , 将默认值设置为-1
类型转换
- 移除const属性
const_cast()
- 语言支持的显示强制转换,将int转换为double,将int转换为bool
static_cast()
- 用户定义的构造函数或转换函数支持的显示强制转换
static_cast()
- 一个类的对象转换为另一个(无关)的类的对象
bit_cast()
- 在同一继承层次结构中,一个类的指针转换为另一个类的指针
- 建议
dynamic_cast()
,或static_cast()
- 建议
- 在同一继承层次结构中,一个类的引用转换为另一个类的引用
- 建议
dynamic_cast()
,或static_cast()
- 建议
- 指向一种类型的指针转换为指向其他不相关类型的指针
reinterpret_cast()
- 一种类型的引用转换为其他不相关的类型的引用
reinterpret_cast()
- 指向函数的指针转换为指向函数的指针
reinterpret_cast()
typeid
函数参数设置默认值
- 示例:
auto connect(const std::string &remote_ip = std::string(), const std::string &port = std::string())->void;
- 作用:
- 为函数
connect()
的两个参数remote_ip
和port
设置默认值,默认值是std::string()
对象的引用,为空
- 为函数
- 原理
- 在C++中,可以为函数的参数提供默认值,以便在调用函数时可以省略该参数。
- 要为函数的参数提供默认值,需要在声明函数时在该参数的类型后面放置一个等于号和默认值
将float类型转为string类型
- 方法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
//c++ 将float 类型转换成string 类型: #include<sstream> std::string Convert(float Num) { std::ostringstream oss; oss<<Num; std::string str(oss.str()); return str; } int main(){ float num = 10.11; std::string data = Convert(num); printf("data: %s\n", data.c_str()); return 0; }
- 方法二:(C++11)
1 2 3 4 5 6 7 8 9 10 11
//c++ 将float 类型转换成string 类型: #include<sstream> int main(){ float num = 10.11; std::string data = std::to_string(num); printf("data: %s\n", data.c_str()); return 0; }
类中声明并初始化静态变量
- 在类中,如果想要直接声明并且初始化一个静态成员,则这个静态变量必须是静态常量
1 2 3 4 5 6
class DataBase { private: static const int num = 123; DataBase() = default; ~DataBase() = default; };
使用using定义函数指针
using Func_ptr = int (*)(int, string);
: 定义了一个指向返回值为int, 形参有两个,分别是int,string的函数指针
日志 – 获取函数的名字,行号,文件名
- ANSI C标准中有几个标准预定义宏(也是常用的):
__LINE__
:在源代码中插入当前源代码行号;__FILE__
:在源文件中插入当前源文件名;__DATE__
:在源文件中插入当前的编译日期__TIME__
:在源文件中插入当前编译时间;__STDC__
:当要求程序严格遵循ANSI C标准时该标识被赋值为1;__cplusplus
:当编写C++程序时该标识符被定义。
- 代码:
1 2 3 4 5 6 7 8 9 10 11
#define LOG(level, format, ...) / do { / fprintf(stderr, "[%s|%s@%s,%d] " format "/n", / level, __func__, __FILE__, __LINE__, ##__VA_ARGS__ ); / } while (0) int main() { LOG(LOG_DEBUG, "a=%d", 10); return 0; }
- 限制是format不能是变量,必须是常量字符串,如果要记录一个变量字符串,不能像printf那样printf(s)了,要LOG(“DEBUG”, “%s”, s)。
函数返回类型写在后面
- 返回类型写在前面:
return-type identifier ( argument-declarations... )
int func (int a);
- 返回类型写在后面:
auto identifier ( argument-declarations... ) -> return_type
auto func(int a)->int;
- 当使用decltype进行返回类型推导的时候,是无法获取到模板参数的,需要使用declval进行辅助推导
1 2 3 4 5 6 7
//这种方法无法做到 template <typename T1, typename T2> decltype(a + b) compose(T1 a, T2 b); //需要使用declval template <typename T1, typename T2> decltype(std::declval<T1>() + std::declval<T2>()) compose(T1 a, T2 b);
- 由于使用declval()这种方法太过于繁琐,所以搞出了这东西
1 2
template <typename T1, typename T2> auto compose(T1 a, T2 b) -> decltype(a + b);
函数后面加const
- c++ 函数前面和后面 使用 const 的作用:
- 前面使用 const 表示返回值为 const
- 后面加 const 表示函数不可以修改 class 的成员
- c++ 在函数后加const的意义:
- 我们定义的类的成员函数中,常常有一些成员函数不改变类的数据成员,也就是说,这些函数是”只读”函数,而有一些函数要修改类数据成员的值。
- 如果把不改变数据成员的函数都加上const关键字进行标识,显然,可提高程序的可读性。其实,它还能提高程序的可靠性,已定义成const的成员函数,一旦企图修改数据成员的值,则编译器按错误处理。
- const成员函数和const对象 实际上,const成员函数还有另外一项作用,即常量对象相关。对于内置的数据类型,我们可以定义它们的常量,用户自定义的类也一样,可以定义它们的常量对象。
- 非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误)
- 表示成员函数隐含传入的this指针为const指针,决定了在该成员函数中, 任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用);
- 唯一的例外是对于mutable修饰的成员。 加了const的成员函数可以被非const对象和const对象调用,但不加const的成员函数只能被非const对象调用
防止头文件重复编译
- C/C++防止头文件被重复引入的3种方法:
- 使用宏定义避免重复引入
- 使用
#pragma once
避免重复引入 - 使用
_Pragma
操作符
- 使用宏定义避免重复引入:
1 2 3 4
#ifndef _NAME_H #define _NAME_H //头文件内容 #endif
- 其中,_NAME_H 是宏的名称。需要注意的是,这里设置的宏名必须是独一无二的,不要和项目中其他宏的名称相同。
- 当程序中第一次 #include 该文件时,由于 _NAME_H 尚未定义,所以会定义 _NAME_H 并执行“头文件内容”部分的代码;当发生多次 #include 时,因为前面已经定义了 _NAME_H,所以不会再重复执行“头文件内容”部分的代码。
- 使用
#pragma once
避免重复引入- 使用 #pragma one 指令,将其附加到指定文件的最开头位置,则该文件就只会被 #include 一次。
- 我们知道,#ifndef 是通过定义独一无二的宏来避免重复引入的,这意味着每次引入头文件都要进行识别,所以效率不高。但考虑到 C 和 C++ 都支持宏定义,所以项目中使用 #ifndef 规避可能出现的“头文件重复引入”问题,不会影响项目的可移植性。
- 和 ifndef 相比,#pragma once 不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,所以效率很高
- 但值得一提的是,并不是每个版本的编译器都能识别 #pragma once 指令,一些较老版本的编译器就不支持该指令(执行时会发出警告,但编译会继续进行),即 #pragma once 指令的兼容性不是很好。
- 目前,几乎所有常见的编译器都支持 #pragma once 指令,甚至于 Visual Studio 2017 新建头文件时就会自带该指令。可以这么说,在 C/C++ 中,#pragma once 是一个非标准但却逐渐被很多编译器支持的指令。
- 除此之外,#pragma once 只能作用于某个具体的文件,而无法向 #ifndef 那样仅作用于指定的一段代码。
- 使用
_Pragma
操作符- C99 标准中新增加了一个和 #pragma 指令类似的 _Pragma 操作符,其可以看做是 #pragma 的增强版,不仅可以实现 #pragma 所有的功能,更重要的是,_Pragma 还能和宏搭配使用。
- 当处理头文件重复引入问题时,可以将如下语句添加到相应文件的开头:_Pragma(“once”)
- 无论是 C 语言还是 C++,为防止用户重复引入系统库文件,几乎所有库文件中都采用了以上 3 种结构中的一种,这也是为什么重复引入系统库文件编译器也不会报错的原因。
- 3 种避免头文件被重复引入的方法,其中
- #pragma once 和 _Pragma(“once”) 可算作一类,其特点是编译效率高,但可移植性差(编译器不支持,会发出警告,但不会中断程序的执行);
- 而 #ifndef 的特点是可移植性高,编译效率差。读者可根据实际情况,挑选最符合实际需要的解决方案。
- 在某些场景中,考虑到编译效率和可移植性,#pragma once 和 #ifndef 经常被结合使用来避免头文件被重复引入。比如说:
1 2 3 4 5 6 7
#pragma once #ifndef _STUDENT_H #define _STUDENT_H class Student { //...... }; #endif
C/C++中冒号的用法
struct和class中的成员变量使用冒号
- 表示机构内位域的定义,即该变量占几个bit空间
1 2 3 4 5 6 7
typedef struct _XXX{ unsigned char a:4; unsigned char c; }; XXX
C++类的构造函数后加冒号
-
例如:
Mat::Mat(const Mat &src):Mat();
- 单冒号的作用是:表示后面是初始化列表
- 这样的写法一般有三种使用场景
- 对父类进行初始化。
- 目的:在创建子类对象时,能够把参数传给父类,从而先完成对父类的初始化.子类构造函数必须要调用父类的构造函数(无论显式还是隐式)
- 调用格式:
子类构造函数 : 父类构造函数
- 对类成员进行初始化
- 调用格式:
构造函数 : A(初始值),B(初始值),C(初始值)……
- 调用格式:
- 对类的const成员变量进行初始化
- 由于const成员变量的值无法在构造函数内部初始化,因此只能在变量定义时赋值或使用初始化列表赋值
- 对父类进行初始化。
- 对于2、3中的应用场景,有以下两点说明
- 构造函数列表初始化执行顺序与成员变量在类中声明顺序相同,与初始化列表中语句书写先后无关
- 相对于在构造函数中赋值,初始化列表执行效率更高。
C++函数后面加一个冒号的含义
- 冒号后面跟的是赋值,这种写法是C++的特性
- 例如
1
A(int aa, int bb):a(aa), b(bb){}
- 相当于
1 2 3 4 5
A(int aa, int bb) { a = aa; b = bb; }
- 例如
- C++构造函数后面加冒号,是初始化表达式。有四种情况下应该使用初始化表达式来初始化成员:
- 初始化 const 成员
- 初始化引用成员
- 当调用基类的构造函数,而它拥有一组参数时
- 当调用成员类的构造函数,而它拥有一组参数时
- 在程序中定义变量并初始化的机制中,有两种形式:
- 一个是我们传统的初始化的形式,即赋值运算符,例如:
int a = 10;
,char b = 'r';
- 还有一种是括号赋值,例如:
int a(10);
,char b('r');
- 一个是我们传统的初始化的形式,即赋值运算符,例如:
- 以上定义并初始化的形式是正确的,可以通过编译的。但是括号赋值,只能在变量定义并初始化中,不能用在变量定义后再赋值;冒号初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员时,只要有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员时,函数体还未执行。对于在函数中初始化,是在所有的数据成员被分配内存空间后才进行的。
- 这样是有好处的,有的数据成员需要在构造函数调入之后,函数体执行之前就进行初始化,例如引用数据成员,常量数据成员和对象数据成员
C++11 =default
和=delete
=default
- C++的类有四类特殊的成员函数,分别是:默认构造函数,析构函数,拷贝函数以及拷贝赋值函数。如果程序没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员函数时,编译器就会隐式地为这个类生成一个默认的特殊成员函数
- 例如:
1 2 3 4 5
class X { private: int a; }; X x; // 可以编译通过,编译器隐式生成默认构造函数
- 但是如果X 显式的自定义了非默认构造函数,却没有定义默认构造函数,下面的代码会出现编译错误
1 2 3 4 5 6 7 8 9 10
class X { public: X(int i) { a = i; } private: int a; }; X x; // 错误,默认构造函数X::X()不存在
- 为了解决上面的问题,我们需要自定义默认构造函数,例如:
1 2 3 4 5 6 7 8 9 10 11
class X { public: X(){}; // 手动定义默认构造函数 X(int i) { a = i; } private: int a; }; X x; // 正确,默认构造函数X::x()存在
- 但是手动编写存在两个问题:
- 程序员工作量变大
- 没有编译器自动生成的默认特殊构造函数效率高
-
为了解决上述的两个问题,C++11标准引入了一个新特性:
defaulted
函数 - defaulted 函数特性仅用于类的特殊成员函数,且该特殊成员函数没有默认参数。
- 例如:
1 2 3 4 5 6
class X { public: int f() = default; // 错误, f()非特殊成员函数 X(int) = default; //错误, 非特殊成员函数 X(int i = 1) = default; // 错误, 含有默认参数 };//能够支持default的,都是编译器能够在该情形下明确要做什么(构造函数一般是逐bit拷贝)
- 既可以在类体内定义(inline),也可以在类体外(out-of-line)定义
- 例如:
1 2 3 4 5 6 7 8 9 10
class X { public: X() = default; // inline X(const X&); // X& operator = (const X&); ~X() = default; // inline } X::X(const X&) = default; // out of line X& X::operator = (const X&) = default; // out of line
=delete
-
为了显示的禁用某个函数,C++11标准引入了一个新特性:deleted函数
- 使用:
1 2 3 4 5 6 7 8 9 10 11 12 13
class X{ public: X(); X(const X&) = delete; // 声明拷贝构造函数为 deleted 函数 X& operator = (const X &) = delete; // 声明拷贝赋值操作符为 deleted 函数 }; class Y{ public: // 禁止使用者在堆上申请对象 void *operator new(size_t) = delete; void *operator new[](size_t) = delete; };
- 规则:
- 必须在函数第一次声明的时候将其声明为deleted函数
- 不同于default,delete没有限制为特殊成员函数才能使用delete
boost中的使用
- 在支持C++11标准下,noncopyable类定义如下:
1 2 3 4 5 6 7 8
class noncopyable { protected: noncopyable() = default; ~noncopyable() = defalult; noncopyable(const noncopyable& ); noncopyable& operator= (const noncopyable&); };