当前位置: 首页 > article >正文

C语言指针专题二 -- 字符指针与字符串

目录

1. 字符指针与字符串的核心原理

字符串的本质

字符串的存储

字符指针的特性

字符指针的操作 

2. 编程实例

3. 常见陷阱与注意事项

4. 总结


1. 字符指针与字符串的核心原理

字符串的本质

  • C语言中没有独立的字符串类型,字符串本质是 \0(空字符)结尾的字符数组

  • 字符串的存储方式:

    • 字符数组:显式声明数组并初始化,如 char str[] = "Hello";

    • 字符指针:直接指向字符串字面量(存储在程序的只读内存区),如 char *p = "Hello";

字符串的存储

在C语言中,字符串是以字符数组的形式存储的,并以空字符 \0 作为字符串的结束标志。下面通过文字描述和图示来解释C语言中的字符串存储规则。

假设有一个简单的字符串 "Hello",在内存中的存储形式如下:

地址    内存内容
-----------------
1000    'H' (72)
1001    'e' (101)
1002    'l' (108)
1003    'l' (108)
1004    'o' (111)
1005    '\0' (0)  // 字符串结束标志
  • 每个字符都占用一个字节的存储空间。
  • '\0' 是字符串的结束标记,它告诉程序这个位置之前的所有字符属于当前字符串。
  • 注意这里的地址(如1000)只是示意性的,实际的内存地址会根据系统分配情况有所不同。

图例表示
如果我们用图形的方式表示上述 "Hello" 字符串在内存中的存储方式,可以想象成如下布局:

+--------+--------+--------+--------+--------+--------+
| 1000   | 1001   | 1002   | 1003   | 1004   | 1005   |
+--------+--------+--------+--------+--------+--------+
| 'H'    | 'e'    | 'l'    | 'l'    | 'o'    | '\0'   |
+--------+--------+--------+--------+--------+--------+

每个方格代表一个字节的内存单元,里面存放的是相应字符的ASCII码值。例如,'H' 的ASCII码是72,但在内存表示中我们通常直接写字符本身而不是其ASCII码值,以便于理解和记忆。

字符指针的特性

  • 指向字符串字面量:字符指针可以指向字符串常量(只读内存区),但不可修改其内容。

    char *p = "Hello"; // p指向只读内存区的字符串
    // p[0] = 'h';     // 错误!尝试修改只读内存会导致崩溃
  • 指向字符数组:字符指针可以指向动态分配的或栈上的字符数组,此时可修改内容。
    char arr[] = "Hello";
    char *p = arr;     // p指向栈上的字符数组
    p[0] = 'h';        // 合法!修改栈内存

字符指针的操作 

  • 遍历字符串:通过指针移动逐个访问字符,直到遇到\0。
  • 动态内存分配:使用malloc为字符串动态分配内存,需手动释放。
  • 字符串函数:C标准库提供<string.h>中的函数(如strcpy、strlen),底层均依赖指针操作。

2. 编程实例

实例1:字符指针与字符串初始化

#include <stdio.h>

int main() {
    // 方式1:字符指针指向字符串字面量(只读)
    char *str1 = "Hello";
    printf("str1: %s\n", str1); // 输出: Hello

    // 方式2:字符数组(可修改)
    char str2[] = "World";
    char *p = str2;           // 指针指向字符数组
    p[0] = 'w';               // 修改第一个字符
    printf("str2: %s\n", p);  // 输出: world

    return 0;
}

实例2:动态分配字符串内存

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

int main() {
    // 动态分配内存存储字符串
    char *str = (char*)malloc(20 * sizeof(char));
    if (str == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }

    strcpy(str, "Dynamic");  // 复制字符串到堆内存
    printf("初始内容: %s\n", str); // 输出: Dynamic

    strcat(str, "123");      // 追加字符串
    printf("追加后内容: %s\n", str); // 输出: Dynamic123

    free(str);  // 释放内存
    str = NULL; // 避免野指针

    return 0;
}

实例3:字符指针遍历字符串

#include <stdio.h>

void print_string(const char *str) {
    while (*str != '\0') {  // 遍历直到空字符
        printf("%c ", *str);
        str++;              // 指针后移
    }
    printf("\n");
}

int main() {
    char *s = "Pointer";
    print_string(s);  // 输出: P o i n t e r 
    return 0;
}

 实例4:修改字符指针指向的内容

#include <stdio.h>

int main() {
    char arr[] = "Hello";  // 栈上的字符数组
    char *p1 = arr;        // 指向可修改的数组
    p1[0] = 'h';           // 合法修改
    printf("p1: %s\n", p1); // 输出: hello

    char *p2 = "Hello";    // 指向只读内存的字符串字面量
    // p2[0] = 'h';        // 运行时错误(段错误)!
    printf("p2: %s\n", p2);

    return 0;
}

 实例5:字符串作为函数参数(指针传递)

#include <stdio.h>

// 函数:统计字符串长度(手动实现strlen)
int string_length(const char *str) {
    int len = 0;
    while (*str != '\0') {
        len++;
        str++;
    }
    return len;
}

int main() {
    char *s = "C Programming";
    printf("字符串长度: %d\n", string_length(s)); // 输出: 13
    return 0;
}

3. 常见陷阱与注意事项

1. 修改只读字符串

char *p = "Hello";
p[0] = 'h';  // 段错误!字符串字面量不可修改。

 2. 未分配内存直接使用指针

char *p;
strcpy(p, "Hello");  // 野指针!p未初始化指向合法内存。

3. 忘记字符串终止符\0

char arr[5] = {'H', 'e', 'l', 'l', 'o'}; // 缺少\0,遍历时可能越界
printf("%s", arr);  // 输出可能包含乱码

4. 内存泄漏

char *str = malloc(100);
// 忘记调用 free(str);

4. 总结

  • 字符指针是操作字符串的核心工具,灵活但需谨慎使用。

  • 字符串字面量存储在只读内存区,字符指针指向时不可修改。

  • 字符数组(栈或堆内存)可通过指针修改内容。

  • 动态分配字符串内存时,必须手动管理生命周期(malloc/free)。

  • 始终确保字符串以\0结尾,避免越界访问。


http://www.kler.cn/a/527630.html

相关文章:

  • jvisualvm工具使用
  • 16进制(十六进制)和二进制之间的转换
  • 游戏开发领域 - 游戏引擎 UE 与 Unity
  • deepseek R1 14b显存占用
  • C语言练习(31)
  • 【NLP251】意图识别 与 Seq2Seq
  • 翻译: Anthropic CEO:DeepSeek-R1是人工智能领域的革命吗?二
  • 一文读懂fgc之cms
  • web安全测试之 xss攻击_request
  • [openwrt] odhcpd ra_management Vs ra_flags 和 ra_slaac
  • 守护进程
  • 代码随想录34 动态规划
  • C动态库的生成与在Python和QT中的调用方法
  • 猿人学web 19题(js逆向)
  • 为AI聊天工具添加一个知识系统 之70 详细设计 之11 维度运动控制的应用:上下文受控的自然语言
  • Git进阶之旅:Git 分支管理
  • gcc和g++的区别以及明明函数有定义为何链接找不到
  • 计算机网络——流量控制
  • CSS 溢出内容处理:从基础到实战
  • 解锁豆瓣高清海报(一) 深度爬虫与requests进阶之路
  • [EAI-029] RoboVLMs,基于VLM构建VLA模型的消融研究
  • Ubuntu 系统,如何使用双Titan V跑AI
  • Learning Vue 读书笔记 Chapter 3
  • 每日一博 - 三高系统架构设计:高性能、高并发、高可用性解析
  • 扩展无限可能:Obsidian Web Viewer插件解析
  • buuuctf_秘密文件