工 具 函 数

本文介绍了C++中常用的工具函数,包括内存操作、字符串处理等,并详细讲解了日志记录方法、函数调用栈输出及宏定义的正确使用方式。此外,还探讨了运算符、拷贝控制、内存泄漏检测及函数重载、隐藏与覆盖等高级主题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

欢迎访问我的博客首页


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);

   可变参数的日志。

#include <ctime>
#include <iostream>
#include <sstream>
using namespace std;

template <typename T>
void join(ostream &os, const T &t) {
    os << t;
}

template <typename T, typename... Args>
void join(ostream &os, const T &t, const Args &...rest) {
    os << t;
    join(os, rest...);
}

template <typename... Args>
void print(const char *file,
           const char *func,
           const int line,
           const Args &...args) {
    // 拼接可变参数。
    stringstream ss;
    join(ss, args...);
    // 获取时间戳。
    time_t rawtime;
    struct tm info;
    char timestamp[20];
    time(&rawtime);
    localtime_s(&info, &rawtime);
    strftime(timestamp, 20, "%Y/%m/%d %H:%M:%S", &info);
    // 输出。
    cout << "-- file " << file << ", function " << func << ", line " << line
         << ", " << timestamp << endl
         << "   " << ss.str() << endl;
}

#define log(...) (print(__FILE__, __FUNCTION__, __LINE__, __VA_ARGS__))

int main() {
    log(1, '2', "abc");
    return 0;
}

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 输出函数调用栈。下面的代码来自 优快云

#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 与访问控制的关系

  访问控制只限定访问不改变成员性质。访问控制不影响重载、隐藏、覆盖,即不同访问控制属性的函数依然可以重载、隐藏、覆盖。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值