1 字符指针
1.1 概述
字符指针变量(简称字符指针)是 C 语言中的一种指针类型,用于指向字符或字符串(字符数组、字符串字面量)。字符指针通常用于处理字符串(字符数组),可以方便地进行字符串的读取、修改和传递。
1.2 定义格式
字符指针的定义方式与其它类型的指针类似,只是类型指定为 char。
char *charPtr; // charPtr 是一个指向 char 类型的指针。
1.3 指向字符变量
字符指针 char * 可以指向一个 char 类型的变量,并通过解引用操作符 * 来访问或修改该变量的值。
#include <stdio.h>
int main()
{
// 定义一个字符变量
char ch = 'A';
// 定义一个字符指针,并让它指向字符变量
char *ptr = &ch;
// 第一次输出:初始值和地址
printf("Character: %c, Address: %p\n", *ptr, (void *)ptr);
// 通过解引用指针修改字符变量的值
*ptr = 'B'; // 只是修改内容,没有改变指针的指向
printf("Modified character: %c, Address: %p\n", *ptr, (void *)ptr); // 地址不变
// 定义一个新的字符变量
char newChar = 'C';
// 修改指针的指向
ptr = &newChar;
// 输出新指向的字符及其地址
printf("New character: %c, Address: %p\n", *ptr, (void *)ptr); // 地址改变
return 0;
}
程序在 VS Code 中的运行结果如下所示:

提示:
指针是一个变量,它保存的是地址;可以通过指针修改它指向的数据内容,但并不会改变指针本身的地址;只有当让指针指向另一个变量时,它的值(地址)才会改变。
1.4 指向字符数组
字符指针不仅可以指向单个字符,也可以指向整个字符串(字符数组),并且可以通过标准库函数如 strlen() 对其进行操作。只要指针指向的是以 '\0' 结尾的有效字符串,它就可以当作字符串来使用。
#include <stdio.h>
#include <string.h>
int main()
{
// 定义字符数组
char str[] = "Hello, World!";
// 定义字符指针并保存原始位置
char *ptr = str;
char *originalPtr = ptr; // 保存原始位置
// 使用指针遍历字符串并逐个打印字符
printf("字符串内容: ");
while (*ptr != '\0')
{
printf("%c", *ptr);
ptr++; // 最后指针会指向 '\0'
}
printf("\n");
// 输出长度信息
printf("字符数组 str 的长度(不包括 '\\0'): %zu\n", strlen(str));
printf("字符指针 originalPtr 所指向字符串的长度: %zu\n", strlen(originalPtr));
return 0;
}
程序在 VS Code 中的运行结果如下所示:

1.5 指向字符串字面量
字符串字面量(如 "Hello, World!")在 C 语言中是存储在只读内存段的常量字符串。
直接使用字符指针指向这些字符串是合法的,但尝试修改其内容会导致未定义行为。
因此,建议在声明字符指针时使用 const 关键字修饰,以明确表示所指向的内容不可修改。
#include <stdio.h>
#include <string.h>
int main()
{
// 使用字符数组定义字符串
char arrayStr[] = "Hello, Array!";
// 使用字符指针定义字符串(指向字符串字面量)
// 注意:字符串字面量是只读的,不能修改
const char *pointerStr = "Hello, Pointer!";
// 输出原始字符串
printf("字符数组定义的字符串: %s\n", arrayStr); // Hello, Array!
printf("字符指针定义的字符串: %s\n\n", pointerStr); // Hello, Pointer!
// 修改字符数组中的内容
printf("1. 单个字符修改很麻烦,需要注意字符串的结束符\n");
arrayStr[7] = '1';
arrayStr[8] = '2';
arrayStr[9] = '3';
printf("没添加结束符的情况:%s\n", arrayStr); // Hello, 123ay!
arrayStr[10] = '\0';
printf("添加字符串结束符后:%s\n\n", arrayStr); // Hello, 123
// 使用 strcpy 函数修改字符串
printf("2. 使用 strcpy 函数修改字符串,注意字符数组的长度\n");
strcpy(arrayStr, "new Array!");
printf("使用 strcpy 函数修改后的字符数组: %s\n", arrayStr); // new Array!
// 尝试修改字符指针指向的字符串(不推荐,会导致未定义行为)
// 下面这行代码会被注释掉,以避免编译器警告或运行时错误
// pointerStr[0] = 'M'; // 这行代码会导致未定义行为
printf("\n尝试修改字符指针指向的字符串(不推荐,会导致未定义行为)\n");
// 重新赋值字符指针,使其指向新的字符串常量
pointerStr = "new Pointer!";
printf("重新赋值(指向其他字符串常量)后的字符指针: %s\n", pointerStr); // new Pointer!
// 分别计算字符数组和字符指针所指向字符串的长度
printf("\n--- 长度计算 ---\n");
printf("字符数组 arrayStr 的字符串长度(不包括 '\\0'): %zu\n", strlen(arrayStr));
printf("字符指针 pointerStr 所指向字符串的长度(不包括 '\\0'): %zu\n", strlen(pointerStr));
return 0;
}
程序在 VS Code 中的运行结果如下所示:

1.6 字符数组 VS 字符指针
字符数组的操作限制
在 C 语言中,字符数组名是一个指向数组首元素的常量指针(constant pointer),这意味着:
- 初始化限制:
- 只能在定义时初始化为一个字符串字面量或另一个字符数组的内容。
- 例如:char str[] = "Hello"; 或 char str[6] = {'H','e','l','l','o','\0'}; 或使用 strcpy 等函数初始化。
- 不可重新赋值:
- 不能被重新赋值以指向新的内存地址。
- 错误示例:char str[10]; str = "New String";(会编译报错)。
- 正确做法:只能通过逐个元素重新赋值或使用 strcpy 等函数来修改内容。
#include <stdio.h>
#include <string.h>
int main()
{
// 定义并初始化字符数组
char arrayStr[] = "Hello, Array!";
printf("初始字符数组内容: %s\n", arrayStr);
// 尝试将字符数组名指向新的字符串(会报错)
// arrayStr = "New String!"; // 编译错误:数组名是常量指针,不能重新赋值
// 使用 strcpy 修改字符数组内容
//注意:目标数组必须足够大,以容纳源字符串
strcpy(arrayStr, "new Array!");
printf("使用 strcpy 修改后的内容: %s\n", arrayStr);
// 逐个字符修改字符数组内容
arrayStr[0] = 'N';
arrayStr[1] = 'e';
arrayStr[2] = 'w';
arrayStr[3] = '_';
arrayStr[4] = 'A';
arrayStr[5] = 'r';
arrayStr[6] = 'r';
arrayStr[7] = 'a';
arrayStr[8] = 'y';
arrayStr[9] = '\0'; // 手动添加字符串结束符
printf("逐个字符修改后的内容: %s\n", arrayStr);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

字符指针的灵活性
字符指针与字符数组名的一个关键区别在于其灵活性:字符指针是一个可变的指针,可以重新赋值以指向不同的字符串或内存位置。
- 可重新赋值:
- 字符指针可以在程序运行时指向不同的内存地址。
- 示例:char *ptr = "Hello"; ptr = "World";(合法)
- 动态性:
- 可以指向字符串字面量、字符数组或动态分配的内存。
- 允许在运行时改变指向的内容。
- 不能通过指针修改字符串字面量的内容。
#include <stdio.h>
int main()
{
// 1. 指向字符串字面量
const char *ptr = "Hello, World!";
printf("初始指向: %s\n", ptr);
// 2. 重新赋值,指向另一个字符串字面量
ptr = "This is a new string.";
printf("重新赋值后指向: %s\n", ptr);
// 3. 指向字符数组
char arrayStr[] = "Character Array";
ptr = arrayStr;
printf("现在指向字符数组: %s\n", ptr);
// 4. 修改字符数组内容(注意:不是修改指针本身)
arrayStr[0] = 'L';
printf("修改字符数组后的内容: %s\n", ptr); // 指针内容同步更新
return 0;
}
程序在 VS Code 中的运行结果如下所示:

修改方式的区别
在C语言中,字符数组和字符指针虽然都可以用于处理字符串,但它们在修改方式上有本质区别:
| 特性 | 字符数组 | 字符指针 |
|---|---|---|
| 地址变化 | 固定不变 | 可以改变 |
| 修改内容 | 修改数组内容,不改变数组地址 | 改变指针值使其指向不同地址 |
| 内存分配 | 通常为栈内存或静态存储区 | 可以指向栈、静态存储区或堆内存 |
| 灵活性 | 较低 | 较高 |
- 字符数组:
- 数组名是常量指针,初始化后地址固定。
- 只能修改数组内容,不能改变数组本身的地址。
- 示例:char str[10] = "Hello"; 后,str 始终指向同一内存位置。
- 字符指针:
- 是一个变量,可以重新赋值指向不同地址。
- 可以指向字符串字面量、其他数组或动态分配的内存。
- 示例:char *ptr = "Hello"; ptr = "World"; 合法
#include <stdio.h>
#include <string.h>
int main()
{
// 定义字符数组和字符指针
char arrayStr[] = "Hello, I am Init Array!";
char *ptrStr = "Hello, Pointer!";
// 输出初始地址和内容
printf("【初始状态】\n");
printf("字符数组 arrayStr 的地址: %p, 内容: %s\n", (void *)arrayStr, arrayStr);
printf("字符指针 ptrStr 的地址: %p, 内容: %s\n\n", (void *)&ptrStr, ptrStr);
// 修改字符数组内容(不改变地址)
strcpy(arrayStr, "Modified Array");
printf("【修改字符数组内容后】\n");
printf("字符数组 arrayStr 的地址: %p, 内容: %s\n", (void *)arrayStr, arrayStr);
printf("字符指针 ptrStr 的地址: %p, 内容: %s\n\n", (void *)&ptrStr, ptrStr);
// 修改字符指针的指向(改变地址)
ptrStr = "New Pointer String";
printf("【修改字符指针的指向后】\n");
printf("字符数组 arrayStr 的地址: %p, 内容: %s\n", (void *)arrayStr, arrayStr);
printf("字符指针 ptrStr 的地址: %p, 内容: %s\n", (void *)&ptrStr, ptrStr);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

对比总结表
| 特性/行为 | 字符数组 (Character Array) | 字符指针 (Character Pointer) |
|---|---|---|
| 定义方式 | char array[] = "Hello"; 或 char array[10]; | char *ptr = "Hello"; 或 const char *ptr = "Hello";(推荐) |
| 内存分配 | 在栈上或静态存储区分配空间(可写) | 指向字符串字面量(只读)、字符数组(可写)或堆内存(需手动管理) |
| 地址特性 | 数组名是常量指针,指向固定不变 | 指针变量本身是变量,可以改变其指向的地址 |
| 初始化 | 只能在定义时用字符串字面量或字符列表或 strcpy 等函数初始化 | 可在定义时赋值,也可后续重新赋值 |
| 内容修改 | ✅ 可以修改数组中的字符(如 array[i] = 'a') | ✅ 如果指向的是字符数组或动态内存,可以修改内容;❌ 不可修改字符串字面量的内容 |
| 重新赋值 | ❌ 不允许(如 array = "new"; 是非法操作) | ✅ 允许(如 ptr = "new"; 是合法操作) |
| 字符串字面量指向 | ✅ 可以初始化为字符串字面量 | ✅ 可以指向字符串字面量 |
| 是否可变内容 | ✅ 可变(因为数据在栈上或静态区,且是副本) |
❌ 如果指向字符串字面量不可变; ✅ 如果指向字符数组或堆内存则可变 |
| 动态内存支持 | ❌ 不支持(大小固定) | ✅ 支持,可通过 malloc / calloc 分配,并配合 realloc 动态扩展 |
| 灵活性 | ❌ 固定大小和地址 | ✅ 更灵活,可随时改变指向和大小 |
| 安全性 | ⚠️ 易发生缓冲区溢出(如使用 strcpy 时未检查长度) | ⚠️ 需注意悬空指针、野指针、内存泄漏等 |
| 典型用途 | 存储需要频繁修改的局部字符串 | 字符串访问、函数传参、动态字符串处理等 |
| 内存释放 | ❌ 不需要(栈内存自动释放) | ✅ 需要(当指向堆内存时,应调用 free()) |
字符串结束符 \0 的处理 | 初始化时自动添加,手动修改时需注意维护 | 同样需要保证以 \0 结尾才能作为字符串使用 |
2 编程练习
2.1 指针遍历数组
编写程序,定义一个整型数组,并通过以下四种方式访问并打印数组中的每个元素:
- 通过数组名和下标访问:arr[i]
- 通过指针和下标访问:ptr[i]
- 通过数组名加偏移量访问:*(arr + i)
- 通过指针加偏移量访问:*(ptr + i)
#include <stdio.h>
int main()
{
// 定义一个整型数组并初始化
int arr[] = {10, 20, 30};
// 计算数组元素个数
int size = sizeof(arr) / sizeof(arr[0]);
// 定义指针指向数组首元素
int *ptr = arr;
// 使用四种方式访问数组元素并输出
printf("数组元素为:\n");
for (int i = 0; i < size; i++)
{
printf("通过数组名下标访问: arr[%d] = %d\n", i, arr[i]);
printf("通过指针下标访问: ptr[%d] = %d\n", i, ptr[i]);
printf("通过数组名偏移访问: *(arr + %d) = %d\n", i, *(arr + i));
printf("通过指针偏移访问: *(ptr + %d) = %d\n", i, *(ptr + i));
printf("\n");
}
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.2 指针与数组求和
编写一个程序,定义一个整型数组,并使用指针以四种不同方式计算数组中所有元素的总和,并输出每种方式的结果。
- 使用数组下标访问:arr[i]
- 使用指针下标访问:ptr[i]
- 使用数组名加偏移访问:*(arr + i)
- 使用指针加偏移访问:*(ptr + i)
#include <stdio.h>
int main()
{
// 定义整型数组并初始化
int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]); // 计算数组长度
// 用于存储四种方式计算出的总和
int sum[] = {0, 0, 0, 0};
int *ptr = arr; // 指针指向数组首元素
// 使用指针遍历数组,并用四种方法分别求和
for (int i = 0; i < size; i++)
{
// 方法一:使用数组名 + 下标访问
sum[0] += arr[i];
// 方法二:使用指针 + 下标访问
sum[1] += ptr[i];
// 方法三:使用数组名 + 偏移访问
sum[2] += *(arr + i);
// 方法四:使用指针 + 偏移访问
sum[3] += *(ptr + i);
}
// 输出每种方法计算出的数组元素总和
printf("数组元素的和为:\n");
printf(" 方法一(arr[i]): %d\n", sum[0]);
printf(" 方法二(ptr[i]): %d\n", sum[1]);
printf(" 方法三(*(arr + i)): %d\n", sum[2]);
printf(" 方法四(*(ptr + i)): %d\n", sum[3]);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.3 指针与数组的最大值
编写程序,定义一个整型数组,并通过指针访问数组元素的方式,查找该数组中的最大值。
#include <stdio.h>
int main()
{
int arr[] = {3, 7, 2, 9, 1};
int size = sizeof(arr) / sizeof(arr[0]);
int max = arr[0]; // 假设第一个元素是最大值
int *ptr = arr; // 指针指向数组的第一个元素
// 使用指针查找数组中的最大值
for (int i = 1; i < size; i++)
{
// 1. 使用数组下标访问数组元素并比较
// if (arr[i] > max)
// {
// max = arr[i];
// }
// 2. 使用指针访问数组元素并比较
if (*(ptr + i) > max)
{
max = *(ptr + i);
}
}
printf("数组中的最大值: %d\n", max);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.4 指针与字符串反转
编写程序,定义一个字符串,并使用两个字符指针分别指向字符串的开头和结尾,逐个交换字符,最终实现字符串的反转。
#include <stdio.h>
#include <string.h>
int main()
{
// 定义一个字符数组并初始化为待反转的字符串
char str[] = "Hello, World!";
// 获取字符串长度
int length = strlen(str);
// 定义两个指针:分别指向字符串的开头和末尾
char *start = str; // 指向字符串首字符
char *end = str + length - 1; // 指向字符串最后一个有效字符
// 使用指针对字符串进行原地反转
while (start < end)
{
// 交换当前 start 和 end 所指向的字符
char temp = *start;
*start = *end;
*end = temp;
// 移动指针:start 向后,end 向前
start++;
end--;
}
// 输出反转后的字符串
printf("反转后的字符串: %s\n", str);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.5 统计多个字符串长度
定义一个指针数组,其元素指向多个字符串字面量。使用循环遍历该指针数组,统计并打印每个字符串的长度。要求不使用 strlen(),而是用指针手动遍历直到 '\0'。
#include <stdio.h>
int main()
{
char *words[] = {"apple", "banana", "cherry", "date"};
int size = sizeof(words) / sizeof(words[0]);
for (int i = 0; i < size; i++)
{
int length = 0; // 每一个单词的长度
char *ptr = words[i]; // 指向每一个单词的指针
// 计算单词的长度
// *ptr 指向当前字符,当 *ptr 不为 '\0' 时,继续循环
while (*ptr != '\0')
{
length++;
ptr++;
}
printf("%s -> %d\n", words[i], length);
}
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.6 查找最长的字符串
给定一个指针数组,保存多个字符串地址。编写程序找出其中最长的字符串,并打印它的内容和长度。
#include <stdio.h>
#include <string.h>
int main()
{
char *names[] = {"Tom", "Jerry", "Alice", "Bob"};
int size = sizeof(names) / sizeof(names[0]);
char *longest = names[0]; // 假设第一个字符串是最长的
for (int i = 1; i < size; i++)
{
// 比较当前字符串和最长字符串的长度
if (strlen(names[i]) > strlen(longest))
{
longest = names[i];
}
}
printf("最长的字符串是:%s(长度为%d)\n", longest, (int)strlen(longest));
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.7 交换二维数组的两行
定义一个 3x4 的二维数组,并使用数组指针交换其中任意两行的数据。例如交换第 0 行和第 2 行。要求通过指针操作完成,不要逐个交换元素。
#include <stdio.h>
int main()
{
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
// 定义一个指向一行(4 个整数)的指针
int (*rowPtr)[4] = matrix; // rowPtr 指向 matrix 的第 0 行
// 打印原始矩阵
printf("原始矩阵:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", rowPtr[i][j]);
}
printf("\n");
}
// 交换第 0 行和第 2 行
int tempRow[4]; // 临时数组,用于保存第 0 行
for (int j = 0; j < 4; j++)
{
tempRow[j] = rowPtr[0][j]; // 保存第 0 行
rowPtr[0][j] = rowPtr[2][j]; // 第 2 行赋给第 0 行
rowPtr[2][j] = tempRow[j]; // 原第 0 行赋给第 2 行
}
// 打印交换后的矩阵
printf("\n交换第0行和第2行后的矩阵:\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", rowPtr[i][j]);
}
printf("\n");
}
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2.8 字符串字符类型统计(指针版)
编写一个 C 程序,定义一个字符数组并初始化为一个字符串(如 "Hello World! 123"),然后使用字符指针遍历该字符串,并分别统计其中的:
- 字母个数(A-Z, a-z)
- 数字个数(0-9)
- 其他字符个数(空格、标点等)
- 最后输出各类字符的数量。
要求:只能通过字符指针访问字符串内容,不能直接使用数组下标 [] 来访问字符。
#include <stdio.h>
#include <ctype.h> // 提供 isalpha() 和 isdigit()
int main()
{
// 定义并初始化字符数组
char str[] = "Hello World! 123";
// 定义字符指针,指向字符串首地址
char *ptr = str;
// 初始化计数器
int letterCount = 0; // 字母计数
int digitCount = 0; // 数字计数
int otherCount = 0; // 其他字符计数
// 使用字符指针遍历字符串
while (*ptr != '\0')
{
if (isalpha(*ptr))
{
letterCount++; // 如果是字母,字母计数加一
}
else if (isdigit(*ptr))
{
digitCount++; // 如果是数字,数字计数加一
}
else
{
otherCount++; // 其他字符(如空格、标点)
}
ptr++; // 移动指针到下一个字符
}
// 输出结果
printf("原始字符串: %s\n\n", str);
printf("字母个数: %d\n", letterCount);
printf("数字个数: %d\n", digitCount);
printf("其他字符个数: %d\n", otherCount);
return 0;
}
程序在 VS Code 中的运行结果如下所示:

2318






