Skip to content

Commit e76cf27

Browse files
author
Yanbin Zhu
committed
feat(*): c语言字符串的理解
1 parent b65e698 commit e76cf27

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

data/blog/c语言/pointer.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ void increment(int* p) {
3434
3535
变量地址而不是变量值传入函数,还有一个好处。对于需要大量存储空间的大型变量,复制变量值传入函数,非常浪费时间和空间,不如传入指针来得高效。
3636
37-
# \& 运算符
37+
# \& 取地址符
3838
3939
`&`运算符用来取出一个变量所在的内存地址。
4040

data/blog/c语言/内存管理.mdx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
title: 内存管理
3+
date: '2024-10-01'
4+
tags: ['C语言']
5+
draft: false
6+
summary: 内存管理
7+
---
8+
9+
10+
# C 语言的内存管理,分成两部分。
11+
12+
一部分是`系统管理`的,另一部分是`用户手动管理`的。
13+
14+
`系统管理的内存`,主要是`函数内部的变量(局部变量)`
15+
这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。
16+
这些变量存放的区域称为`”栈“(stack)``”栈“所在的内存是系统自动管理的`
17+
18+
`用户手动管理的内存`,主要是`程序运行的整个过程中都存在的变量(全局变量)`,这些变量需要用户手动从内存释放。
19+
如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为`”内存泄漏“(memory leak)`
20+
这些变量所在的内存称为`”堆“(heap)``”堆“所在的内存是用户手动管理的`
21+
22+
23+
# void指针
24+
前面章节已经说过了,每一块内存都有地址,通过指针变量可以获取指定地址的内存块。
25+
`指针变量必须有类型,否则编译器无法知道,如何解读内存块保存的二进制数据。`
26+
但是,向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。
27+
28+
为了满足这种需求,C 语言提供了一种`不定类型的指针`,叫做` void 指针`
29+
它只有内存块的地址信息,没有类型信息,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。
30+
31+
另一方面,void 指针等同于无类型指针,可以指向任意类型的数据,但是`不能解读数据`
32+
33+
`void 指针与其他所有类型指针之间是互相转换关系`,任一类型的指针都可以转为 void 指针,而 void 指针也可以转为任一类型的指针。
34+
```C
35+
int x = 10;
36+
37+
void* p = &x; // 整数指针转为 void 指针
38+
int* q = p; // void 指针转为整数指针
39+
```
40+
上面示例演示了,整数指针和 void 指针如何互相转换。`&x`是一个整数指针,p是 void 指针,赋值时`&x`的地址会自动解释为 void 类型。
41+
同样的,`p`再赋值给整数指针`q`时,`p`的地址会自动解释为整数指针。
42+
43+
注意,由于不知道 void 指针指向什么类型的值,所以不能用*运算符取出它指向的值。
44+
```C
45+
char a = 'X';
46+
void* p = &a;
47+
48+
printf("%c\n", *p); // 报错
49+
```
50+
上面示例中,p是一个 void 指针,所以这时无法用*p取出指针指向的值。

data/blog/c语言/字符串.mdx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
title: 字符串
3+
date: '2024-10-01'
4+
tags: ['C语言']
5+
draft: false
6+
summary: 字符串
7+
---
8+
9+
10+
# 定义
11+
12+
C 语言没有单独的字符串类型,字符串被当作字符数组,即`char类型的数组`
13+
14+
编译器会给数组分配一段连续内存,所有字符储存在相邻的内存单元之中。
15+
`字符串结尾`,C 语言`会自动添加一个全是二进制0的字节,写作\0字符,表示字符串结束`
16+
字符\0不同于字符0,前者的 ASCII 码是0(二进制形式00000000),
17+
后者的 ASCII 码是48(二进制形式00110000)。
18+
所以,字符串“Hello”实际储存的数组是`{'H', 'e', 'l', 'l', 'o', '\0'}`
19+
20+
```C
21+
char localString[10];
22+
```
23+
上面示例声明了一个10个成员的字符数组,可以当作字符串。由于必须留一个位置给`\0`,所以最多只能容纳9个字符的字符串。
24+
25+
26+
字符串写成数组的形式,是非常麻烦的。C 语言提供了一种简写法,双引号之中的字符,会被自动视为字符数组。
27+
```C
28+
{'H', 'e', 'l', 'l', 'o', '\0'}
29+
30+
// 等价于
31+
"Hello"
32+
```
33+
上面两种字符串的写法是等价的,内部存储方式都是一样的。双引号里面的字符串,不用自己添加结尾字符\0,C 语言会自动添加。
34+
35+
注意,双引号里面是字符串,单引号里面是字符,两者不能互换。如果把Hello放在单引号里面,编译器会报错。
36+
37+
38+
# 字符串变量的声明
39+
40+
```C
41+
// 写法一
42+
char s[14] = "Hello, world!";
43+
char s[] = "Hello, world!";
44+
45+
// 写法二
46+
char* s = "Hello, world!";
47+
```
48+
上面两种写法都声明了一个字符串变量s。
49+
如果采用第一种写法,由于字符数组的长度可以让编译器自动计算,所以声明时可以省略字符数组的长度。
50+
51+
52+
`字符指针``字符数组`,这两种声明字符串变量的写法`基本是等价`的,但是`有两个差异`
53+
54+
## 第一个差异是,指针指向的字符串,在 C 语言内部被当作常量,不能修改字符串本身
55+
```C
56+
char* s = "Hello, world!";
57+
s[0] = 'z'; // 错误
58+
```
59+
上面代码使用指针,声明了一个字符串变量,然后修改了字符串的第一个字符。
60+
这种写法是错的,会导致难以预测的后果,执行时很可能会报错。
61+
62+
为什么字符串声明为指针时不能修改,声明为数组时就可以修改?
63+
原因是系统会将字符串的字面量保存在内存的常量区,这个区是不允许用户修改的。
64+
`声明为指针时``指针变量存储的值是一个指向常量区的内存地址`,因此用户`不能通过这个地址去修改常量区`
65+
但是,`声明为数组时`,编译器`会给数组单独分配一段内存`,字符串字面量会`被编译器解释成字符数组`
66+
逐个`字符写入这段新分配的内存`之中,而`这段新内存是允许修改`的。
67+
68+
69+
## 第二个差异是,指针变量可以指向其它字符串
70+
71+
```C
72+
char* s = "hello";
73+
s = "world";
74+
```
75+
上面示例中,字符指针可以指向另一个字符串。
76+
77+
但是,字符数组变量不能指向另一个字符串。
78+
```C
79+
char s[] = "hello";
80+
s = "world"; // 报错
81+
```
82+
上面示例中,字符数组的数组名,总是指向初始化时的字符串地址,不能修改。
83+
84+
为什么数组变量不能赋值为另一个数组?
85+
原因是`数组变量所在的地址无法改变`,或者说,编译器一旦为数组变量分配地址后,
86+
这个地址就绑定这个数组变量了,这种绑定关系是不变的。
87+
C 语言也因此规定,`数组变量是一个不可修改的左值`,即不能用赋值运算符为它重新赋值。
88+
89+
90+
## Refernce
91+
https://wangdoc.com/clang/string

0 commit comments

Comments
 (0)