欢迎访问我的博客首页。
工具函数
1. 内存操作函数
内存操作函数 memset、memcpy、memcmp、memmove、memchr 可以在静态、动态内存操作。
int a[10] = { 9,8,7,6,5,4,3,2,1 }, b[10];
memset(b, 0, sizeof(b)); // 按字节操作,int不是1字节所以只能置0。对char型数组或指针可以置任意字符。
int* c = (int*)malloc(sizeof(int) * 5);
memcpy(c + 3, a + 1, sizeof(int) * 2); // 按字节操作。从a[1]开始复制2*sizeof(int)字节的内容放在c[3]开始的内存。
int d[2] = { 0,1 }, e[2] = { 0,2 };
cout << memcmp(d, e, sizeof(int) * 1) << endl; // 按字节操作,以d、e开始,比较它们前n个字节。
2. 字符串操作函数
strcpy、strncpy、strcmp、strcat。
3. 比较
memcpy 与 strcpy:前者按指定字节长度复制内存。后者只能复制字符直到复制 ‘\0’ 后才结束。
4. 包含位置信息的日志
void print(const char* msg, const char* file, const char* func, const int line) {
char tmpbuf[21];
time_t tmpTime = time(0);
strftime(tmpbuf, sizeof(tmpbuf) / sizeof(char) - 1, "%Y/%m/%d %H:%M:%S", localtime(&tmpTime));
cout << "-- file " << file << ", function " << func << ", line " << line << ", " << tmpbuf << endl << " " << msg << endl;
}
#define log(msg) (print(msg, __FILE__, __FUNCTION__, __LINE__))
C 语言实现的日志:
#define selfLog(fmt, ...) \
printf("-- %s, function %s, line %d, time %s: "fmt, __FILE__, __func__, __LINE__, __TIME__, ##__VA_ARGS__)
把日志输入文件中:
FILE *handle;
#define selfLog(fmt, ...) \
handle = fopen("log.txt", "a+"); \
fprintf(handle, fmt, __VA_ARGS__); \
fclose(handle);
5. 在堆上申请二维数组
const int w = 3, h = 4;
int(*arr)[w] = (int(*)[w])malloc(sizeof(int) * w * h);
int(*arr)[w] = new int[h][w];
vector<vector<int>> arr(h, vector<int>(w));
上面三种方法申请 4 行 3 列的数组。前两种方法可以很方便地初始化为 0,但它们的第 2 个纬度的长度 w 必须用常量指定。
6. linux 系统输出函数调用栈
在 linux 系统上使用 C/C++ 时,可以借助 execinfo.h 输出函数调用栈。下面的代码来自 CSDN。
#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>
void print_stacktrace() {
int size = 16;
void * array[16];
int stack_num = backtrace(array, size);
char ** stacktrace = backtrace_symbols(array, stack_num);
for (int i = 0; i < stack_num; ++i) {
printf("%s\n", stacktrace[i]);
}
free(stacktrace);
}
void fun1() {
printf("stackstrace begin:\n");
print_stacktrace();
}
void fun2() {
fun1();
}
void fun3() {
fun2();
}
int main() {
fun3();
}
编译这段代码,需要指定链接选项。
cmake_minimum_required(VERSION 3.5.1)
project(demo)
set(LINK_FLAGS "-rdynamic -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now")
set(CMAKE_SHARED_LINKER_FLAGS "${LINK_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${LINK_FLAGS}")
add_executable(main
main.cc
)
0. 宏定义
1.3.1 宏函数
// 1.参与运算的参数要加括号。
#define f1(x) 2 * x // f1(1+2) = 2 * 1 + 2 = 4。
#define f2(x, y) x * y // f2(1+2, 3+4) = 1 + 2 * 3 + 4 = 11。
// 2.参与运算的宏定义要加括号。
#define f3(x, y) (x) < (y) ? (x) : (y) // f3(1, 2) * 10 = 1 < 2 ? 1 : 2 * 10 = 1。
// 3.宏定义与自增:表达式中不同位置的同一个自增变量的值总是相等的。
// 3.1 i从1开始。
#define f4(x) (x) * (x)
// f4(++i) // 用前加2,用时是3,用后不加。
// f4(i++) // 用前不加,用时是3,用后加2。
// 3.2 i从1开始。
#define f5(x, y) (x) * (y)
// f5(++i, ++i) // 用前加2,用时是3,用后不加。
// f5(++i, i++) // 用前加1,用时是4,用后加1。
// f5(i++, ++i) // 用前加1,用时是6,用后加1。
// f5(i++, i++) // 用前不加,用时是7,用后加2。
// 3.3 i从1开始。
#define f6_1(x) (++x) * (++x) // 用前加2,用时是3,用后不加。
#define f6_2(x) (++x) * (x++) // 用前加1,用时是4,用后加1。
#define f6_3(x) (x++) * (++x) // 用前加1,用时是6,用后加1。
#define f6_4(x) (x++) * (x++) // 用前不加,用时是7,用后加2。
3. 运算符
3.1 算术运算符
2.1.1 递增运算符
下面每行语句开始前x的值都是1。
x = ++x; // 2。
x = x++; // 2。
x = (++x) * (++x); // (x+1+1) * (x+1+1) = 9。
x = (++x) * (x++); // (x+1) * (x+1) + 1 = 5。
x = (x++) * (++x); // (x+1) * (x+1) + 1 = 5。
x = (x++) * (x++); // x * x + 1 + 1 = 3。
y = ++x; // 2。
y = x++; // 1。
y = (++x) * (++x); // (x+1+1) * (x+1+1) = 9。
y = (++x) * (x++); // (x+1) * (x+1) = 4。
y = (x++) * (++x); // (x+1) * (x+1) = 4。
y = (x++) * (x++); // x * x = 1。
3.2 关系运算符
用C++判断两个以上变量的关系,可能不是你想要的结果:
int a = 1, b = 2, c = 1;
if (a <= b <= c) cout << "yes" << endl; else cout << "no" << endl;
上面的程序运行结果是yes。因为a <= b <= c等价于(a <= b) <= c。a <= b为true,true的值是1,所以等价于1 <= c。于是表达为真。
3.3 逻辑运算符
逻辑运算表达式化简。逻辑运算和算术运算一样有交换律、分配律、结合律。此外逻辑运算还有吸收律。
!a && (a || b) 等价于 (!a && a) || (!a && b) 等价于 !a && b
高效判断奇偶的方法:奇数&1 = 1/true,偶数&1 = 0/false。任何数&0 = 0。
4. 拷贝控制
4.1 右值引用
3.5.1 右值引用与std::move
右值引用int&&只能绑定/匹配右值。这是移动函数的参数匹配原理:右值引用的形参只能匹配右值。
std::move实现从左值到右值的转换。
// 1.右值引用只能绑定右值。对于左值,需要使用std::move转换成右值才能绑定。
int a = 1;
const int b = 2;
int&& r1 = 1;
int&& r2 = std::move(a);
int&& r3 = a + 1;
int&& r4 = b + 1;
int&& r5 = std::move(r4);
// 2.移动函数的参数匹配。
void fun(int&) { cout << "实参是左值" << endl; }
void fun(int&&) { cout << "实参是右值" << endl; }
int main() {
int a = 1;
fun(a); // 左值。
fun(a + 1); // 右值。
fun(std::move(a)); // 右值。
}
3.5.2 完美转发与std::forward
void Call(int& e) { cout << "--1." << endl; }
void Call(int&& e) { cout << "--2." << endl; }
void Call(const int& e) { cout << "--3." << endl; }
void Call(const int&& e) { cout << "--4." << endl; }
template<typename T>
void notPerfectForward(T&& t) { Call(t); }
template<typename T>
void PerfectForward(T&& t) { Call(forward<T>(t)); }
int main() {
int a = 1;
const int b = 2;
// 1.直接根据实参匹配重载函数。
Call(a); // 匹配1,3。
Call(move(a)); // 匹配2,4,3。
Call(b); // 匹配3。
Call(move(b)); // 匹配4,3。
// 2.不使用std::forward。
notPerfectForward(a); // 重载函数1。
notPerfectForward(move(a)); // 重载函数1。
notPerfectForward(b); // 重载函数3。
notPerfectForward(move(b)); // 重载函数3。
// 3.完美转发。
PerfectForward(a); // 重载函数1。
PerfectForward(move(a)); // 重载函数2。
PerfectForward(b); // 重载函数3。
PerfectForward(move(b)); // 重载函数4。
}
第10行定义的函数,可以完美转发4种类型的实参匹配4个重载函数。
【参考】
5. 内存泄漏
5.3.1 内存泄露检测工具
在windows上可以使用VLD检测内存泄漏。
5.3.2 基类虚析构函数防止内存泄露
6. 函数
6.1 重载、隐藏、覆盖
6.4.1 重载
重载函数:同一作用域内的几个函数名字相同但形参列表不同。
重载原理:使用命名倾轧技术,即编译器使用函数名和参数列表把重载函数编译成不同名字的函数。
1. 使用底层const指针或引用可以定义重载函数。
// 下面2个函数都匹配int*和int* const的实参。它们不能重载。
void fun(int* x) { cout << "--fun(int*)." << endl; }
void fun(int* const x) { cout << "--fun(int* const)." << endl; } // 顶层const。
// 下面1个函数匹配const int*的实参。它可以和上面两个函数之一重载。
void fun(const int* x) { cout << "--fun(const int*)." << endl; } // 底层const。
2. 重载函数参数数量相同且参数类型可以相互转换时的最佳匹配。
void f(int, int) {}
void f(double, double) {}
f(42, 2.56); // 与上面两个函数各匹配一个参数。因具有二义性,无法匹配。
实参类型转换。
6.4.2 隐藏
派生类的函数会隐藏所有从基类继承来的同名函数。
6.4.3 覆盖/重写
覆盖是隐藏的一种,除函数名相同外,还要:1.基类函数是虚函数;2.派生类函数与该虚函数形参相同;3.两个函数的返回值相同或协变。
【协变和逆变】
6.4.4 与访问控制的关系
访问控制只限定访问不改变成员性质。访问控制不影响重载、隐藏、覆盖,即不同访问控制属性的函数依然可以重载、隐藏、覆盖。