字符指针变量是C/C++中最基础但最容易出错的指针类型之一。它的类型是char*,专门用于存储字符变量的地址。让我们从一个简单例子开始:
c复制#include <stdio.h>
int main()
{
char a = 'w';
char* ch = &a;
*ch = 'z';
printf("%c\n", a);
return 0;
}
这段代码展示了字符指针的基本用法:通过char*类型的指针ch修改了变量a的值。但真正容易让人困惑的是下面这种情况:
c复制#include <stdio.h>
int main()
{
char* pstr = "hello world";
printf("%s\n", pstr);
return 0;
}
关键理解点:这里的
pstr并不是存储了整个字符串,而是存储了字符串常量"hello world"的首字符'h'的内存地址。字符串常量在内存中以连续字符序列存储,并以'\0'结尾。
常见误区与注意事项:
c复制char* p = "constant";
*p = 'C'; // 错误!尝试修改只读内存
c复制char arr[] = "modifiable";
arr[0] = 'M'; // 合法操作
让我们深入分析这个经典的面试题:
c复制#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
对于str1和str2:
str1和str2是完全独立的两个数组str1 == str2比较的是数组首地址,必然为false对于str3和str4:
str3和str4实际上指向同一个内存地址str3 == str4比较的是指针值,结果为true重要提示:这种优化行为是编译器相关的,并非C/C++标准强制要求。某些编译器可能不会进行这种优化。
数组指针是指向整个数组的指针,与指向数组首元素的普通指针有本质区别。
c复制int(*p2)[10]; // 正确的数组指针声明
int* p1[10]; // 这是指针数组,不是数组指针
关键区别:
int(*p2)[10]:p2是一个指针,指向包含10个int的数组int* p1[10]:p1是一个数组,包含10个int*指针c复制int arr[10] = {0};
int(*p)[10] = &arr; // p指向整个arr数组
// 通过数组指针访问元素
(*p)[0] = 1; // 等价于arr[0] = 1
内存布局解析:
code复制+------+ +-------------------------+
| p |---->| arr[0] | arr[1] | ... | arr[9] |
+------+ +-------------------------+
理解二维数组传参的关键在于认识二维数组的内存布局:
c复制int arr[3][5] = {{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};
二维数组实际上是"数组的数组":
正确的传参方式:
c复制void func(int (*p)[5], int row); // 数组指针形式
// 或
void func(int arr[][5], int row); // 省略第一维
// 调用
func(arr, 3);
常见错误:试图使用
int**作为二维数组参数类型,这是完全错误的,因为内存布局完全不同。
函数指针是C/C++中强大的特性,允许我们将函数作为参数传递或存储在数据结构中。
c复制#include <stdio.h>
void test() {
printf("hehe\n");
}
int main() {
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
输出结果证明:函数名和&函数名获取的都是相同的函数地址。
c复制int Add(int x, int y) {
return x + y;
}
int main() {
int(*pf)(int, int) = Add;
// 三种等效的调用方式
printf("%d\n", Add(2, 3));
printf("%d\n", (*pf)(2, 3));
printf("%d\n", pf(2, 3));
return 0;
}
函数指针类型解析:
c复制int (*pf)(int, int)
↑ ↑ ↑
| | |—— 参数类型列表
| |—— 指针变量名
|—— 返回类型
typedef可以简化复杂指针类型的声明,提高代码可读性。
c复制typedef unsigned int uint;
typedef int* int_ptr;
c复制typedef int (*CalcFunc)(int, int);
// 使用简化后的类型名
CalcFunc addFunc = Add;
CalcFunc subFunc = Sub;
这种用法在大型项目中特别有用,可以保持类型声明的一致性。
函数指针数组将多个相关函数组织在一起,实现类似"跳转表"的功能。
c复制int (*funcArray[5])(int, int);
解读:funcArray是一个包含5个元素的数组,每个元素是一个指向函数的指针,该函数接受两个int参数并返回int。
原始switch-case实现的缺点:
使用函数指针数组的改进方案:
c复制int (*operations[5])(int, int) = {NULL, Add, Sub, Mul, Div};
int main() {
// ...初始化代码...
if (input >= 1 && input <= 4) {
result = operations[input](x, y);
}
// ...其他代码...
}
优势对比:
| 特性 | switch-case实现 | 函数指针数组实现 |
|---|---|---|
| 代码量 | 冗长 | 简洁 |
| 扩展性 | 差 | 好 |
| 维护性 | 困难 | 容易 |
| 执行效率 | 可能较慢 | 直接跳转,高效 |
函数指针最常见的应用是实现回调机制:
c复制typedef void (*Callback)(int status);
void fetchData(Callback cb) {
// 模拟异步操作
int result = /*...*/;
cb(result);
}
void handleResult(int status) {
printf("Operation result: %d\n", status);
}
int main() {
fetchData(handleResult);
return 0;
}
空指针调用:
c复制int (*func)(int) = NULL;
func(10); // 崩溃!
解决方案:总是检查指针是否为空
类型不匹配:
c复制int func(char c) {...}
int (*pf)(int) = func; // 错误!
解决方案:确保类型严格匹配
返回栈内存指针:
c复制char* getString() {
char str[] = "local";
return str; // 危险!
}
解决方案:返回静态或动态分配的内存
inline函数替代简单的函数指针调用利用函数指针可以实现简单的插件架构:
c复制// 插件接口定义
typedef struct {
const char* name;
int (*init)();
void (*process)(void* data);
int (*cleanup)();
} Plugin;
// 示例插件实现
int myInit() { /*...*/ }
void myProcess(void* data) { /*...*/ }
int myCleanup() { /*...*/ }
Plugin myPlugin = {
"MyPlugin",
myInit,
myProcess,
myCleanup
};
// 系统加载插件
void loadPlugin(Plugin* p) {
if (p->init()) {
// 注册插件
// ...
}
}
这种设计模式在大型软件系统中非常常见,如Apache模块系统、游戏引擎的脚本扩展等。
虽然本文聚焦C风格的指针,但在C++中,我们通常更推荐使用更安全的替代方案:
std::unique_ptr, std::shared_ptrstd::functionenum class例如,计算器案例可以用C++11重写:
cpp复制#include <functional>
#include <vector>
std::vector<std::function<int(int,int)>> operations = {
nullptr,
[](int a, int b) { return a + b; },
[](int a, int b) { return a - b; },
// ...其他操作...
};
// 调用方式相同
int result = operations[choice](x, y);
这种实现既保持了函数指针数组的简洁性,又增加了类型安全和现代C++的特性支持。