重学C语言(一) 字符串数组输出

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int i;
    char *names[] = { "John", "Mona", "Lisa", "Frank" };
    for (i = 0; i < 4; ++i) {
        char *pos = names[i];
        printf("%s\n", pos);
    }
} 
<think>嗯,用户这次明确要求深入了解C语言字符串数组的用法和示例。结合用户提供的多个引用内容来看,ta应该是在学习C语言字符串数组的不同实现方式和常见操作。用户可能是个C语言初学者,正在处理字符串数组相关的编程作业或项目。从引用内容看,ta至少已经掌握了基础语法,但可能对字符串数组的两种表示方法(二维字符数组vs指针数组)以及它们的区别和适用场景感到困惑。特别是引用[4]中明确区分了这两种方式,用户可能需要更具体的对比说明。我注意到用户特别引用了关于字符串数组遍历时NULL终止符的重要性(引用[5]),这说明ta可能遇到过数组越界或指针错误的问题。另外引用[3]展示了固定长度字符串数组的截断风险,这也是实际编程中容易出错的地方。在组织回答时,需要重点突出:1)二维数组和指针数组的对比表格会很直观,引用[4]的内容可以整合进去2)遍历时指针操作的安全问题,比如NULL哨兵的使用(引用[5]的关键点)3)输入输出场景中的缓冲区溢出防范,引用[2]的gets()实际已不被推荐4)多维数组内存布局的可视化说明很重要(用户引用中没有但很有必要)5)错误示例部分尤其要突出空字符处理,引用[3]的截断问题就是典型值得警惕的是,用户提供的引用[3]代码里存在手动填充分隔符的做法(Hello\0\0...),这实际是错误的示范,需要在回答中特别纠正。对于专业细节要特别注意:-strlen和sizeof的区别需要单独强调-fgets替代gets的安全输入方法必须说明-JNI处理中文的部分在用户引用[3]提到过但被省略了,可以放在扩展部分-字符串数组排序的qsort用法虽然不在引用中,但属于常用操作应当补充用户可能接下来会问关于动态分配字符串数组或排序的问题,所以最后的相关问题里预留了扩展方向。</think> 以下是对C语言字符串数组用法及关键细节的详细解析(结合站内引用整合): --- ### 字符串数组的两种核心表示方法 #### 1. 二维字符数组(固定长度) **特点**:连续内存块,每行存储固定长度的字符串 ```c char strArray[3][10] = {"Apple", "Banana", "Cherry"}; ``` - **初始化细节**: - 未使用的空间自动填充`\0`(如"Apple"后补5个`\0`) - 超出列宽的字符串会被截断(危险!)[^3] ```c char arr[2][5] = {"Hello", "World"}; // 实际存储:"Hell\0"、"Worl\0" ``` #### 2. 指针数组(动态指向) **特点**:数组元素指向字符串常量区地址 ```c char* fruits[] = {"Mango", "Peach", "Grape"}; ``` - 内存布局: ``` fruits[0] → "Mango\0" // 常量区 fruits[1] → "Peach\0" fruits[2] → "Grape\0" ``` - **访问方式对比**: | 操作 | 二维数组 | 指针数组 | |-------------------|-------------------|-------------------| | 访问字符 | `arr[1][2]` | `fruits[1][2]` | | 指针偏移访问 | `*(*(arr+1)+2)` | `*(*(fruits+1)+2)`[^1]| --- ### 二、关键操作及注意事项 #### 1. 安全遍历方法(必加`NULL`哨兵) ```c char* languages[] = {"C", "Java", "Python", NULL}; // 结尾NULL必须 for(char** p = languages; *p != NULL; p++) { printf("%s\n", *p); } ``` **未加`NULL`的后果**: 指针会继续向后访问非法内存 → 程序崩溃[^5] #### 2. 输入输出安全规范 ✅ **安全输入**: ```c char names[3][20]; for(int i=0; i<3; i++) { fgets(names[i], 20, stdin); // 替代危险的gets() } ``` ❌ **危险操作**: ```c char buf[10]; gets(buf); // 可能溢出![^2] ``` #### 3. 修改字符串的限制 - **指针数组的元素不可修改**(常量区只读): ```c char* colors[] = {"Red", "Blue"}; colors[0][0] = 'r'; // 运行时错误!(Segmentation fault) ``` - **二维数组可修改**(栈/堆内存): ```c char colorArr[][10] = {"Red", "Blue"}; strcpy(colorArr[0], "Green"); // 合法 ``` --- ### 三、内存布局解析(二维数组 vs 指针数组) #### 1. 二维字符数组内存模型 ``` 地址: 0x1000 [A][p][p][l][e][\0][\0][\0][\0][\0] // "Apple" 地址: 0x100A [B][a][n][a][n][a][\0][\0][\0][\0] // "Banana" ... ``` **特点**:连续内存块,空间预分配,可能浪费内存 #### 2. 指针数组内存模型 ``` 地址: 0x2000 [0x3000][0x3006][0x300C] // 指针数组元素 ... 地址: 0x3000 [C][\0] // 常量区"C" 地址: 0x3006 [J][a][v][a][\0] // "Java" ``` **特点**:指针分散存储,字符串在只读区[^4] --- ### 四、经典错误案例 ```c // 错误1:未初始化直接操作 char arr[3][10]; printf("%s", arr[0]); // 输出乱码(未赋初值) // 错误2:越界访问 char* words[] = {"Cat","Dog"}; words[2] = "Fox"; // 越界!仅分配了2个元素指针的空间 // 错误3:混淆sizeof和strlen char str[10] = "Hello"; int size = sizeof(str); // 10(数组总字节数) int len = strlen(str); // 5(实际字符数) ``` --- ### 五、实用代码示例 #### 1. 动态输入字符串数组 ```c #include <stdio.h> #include <stdlib.h> int main() { char* cities[4]; char buffer[100]; for(int i=0; i<4; i++) { fgets(buffer, 100, stdin); cities[i] = strdup(buffer); // 堆分配拷贝 } // 使用后务必释放! for(int i=0; i<4; i++) free(cities[i]); } ``` #### 2. 字符串数组排序(qsort) ```c #include <string.h> #include <stdlib.h> int compare(const void* a, const void* b) { return strcmp(*(const char**)a, *(const char**)b); } int main() { char* animals[] = {"Zebra", "Lion", "Tiger"}; qsort(animals, 3, sizeof(char*), compare); // 输出:Lion, Tiger, Zebra } ``` --- **注意事项总结**: 1️⃣ 指针数组遍历结尾必须加`NULL` 2️⃣ 二维数组初始化警惕字符串截断 3️⃣ 常量区的字符串不可修改 4️⃣ 用`fgets`替代`gets`防范溢出 5️⃣ 动态分配后必须手动释放内存 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值