目录
目录
一、指针初步
指针的学习是一个让很多初学者望而生畏的部分,那么我们要想学好指针首先就要理解什么是指针。
1、内存地址
在计算机中为了方便管理和存放数据,内存通常被划分为一个一个的单元,这些单元就称为存储单元(或内存单元)。如果把数学楼比作内存的话,那么教学楼里的每间教室就相当于内存单元了。
每间教室都有一定的容量,内存中每个内存单元也有其大小,1个内存单元如果能存放1个字节的数据,则称为字节存储单元。每一个储存单元都有相应的编号,这个编号就是存储单元的“地址”,简称”存储地址 “,就像每间教室都有一个唯的教室号码一样,每一个存储单元都有唯一的地址。数据和程序就放在这些内存单元中。
2、变量地址- 系统分配给变量的内存单元的起始地址
C语言程序中所定义的变量,在程序运行时都要为这些变量分配相应的内存单元,每个变量所占用的内存单元的大小是随着其定义的数据类型的不同而不同的。
3、指针与指针变量
- 指针的实质就是内存地址
- 一个指针变量的值就是某个内存单元的地址
- “指针”是指地址,是常量
- “指针变量”是指取值为地址的常量
- C语言要实现变量的间接引用则要依靠指针变量
- 指针变量的大小
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
• 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是 相同的。
通过变量名来引用变量的内存单元值的方法被称为直接引用、而通过内存地址引用内存单元值的方法称为间接引用
指针变量的定义格式
[存储类型] 数据类型符 *变量名;
4、取地址与解引用
我们要想得到某个变量的地址就需要学习一个操作符———取地址操作符(&):取变量的地址
我们要想拆解某个变量的地址就需要学习一个操作符———解引用操作符(*) :取指针所指变量的内容(存谁指谁)
这两个操作符互为逆运算
#include<stdio.h>
int main()
{
int a=20;
int *p=&a;//取a的地址并存储到指针变量p中
int *p=0;//把a改为了0
return 0;
{
*p的意思就是通过p中存放的地址,找到指向的空间,*p其实就是a变量了;所以*p= 0,这个操作符是把0赋值给a
5、指针运算
5.1指针+-整数
指针和整数可以进行加减运算,但结果仅仅支持两种情况:
1. 当指针与整数相加时,结果是一个新的指针,指向原指针所指向的内存地址加上整数的偏移量。
2. 当指针与整数相减时,结果是一个整数,表示两个指针之间的偏移量。
需要注意的是,指针和整数之间的加减运算可能会导致指针越界或者访问非法内存,因此在进行这种运算时需要谨慎处理。
指针p是这样定义的:ptype*p;并且p当前的值是ADDR,那么
p+-n的值=ADDR土n*sizeof(ptyte)
也就是说,p将以sizeof(ptype)为单位进行相加、减,而不是简单地将p的值加、减n。
#include<stdio.h>
int main()
{
int a[10]={1,2,3};
int *p=&a;
int i=0;
int sz=sizeof(a)/sziof(a[0]);//计算数组长度
for(i=0;i<sz;i++)
{
printf("%d",*(p+i));//指针+整数,且p+1会跳过四个字节=i*sizeof(int)
}
return 0;
}
在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算
5.2指针-指针
前提:两个指针指向同一块空间
会得到两个指针之间的元素个数
5.3指针的关系运算
两个指针变量进行关系运算可表示它们所指向的内存单元之间的关系。
假设 p1和 p2 是两个指针变量,p1==p2表示p1和p2指向同一内存单元。
pl>p2 表示p1处于高地址位置。
p1<p2 表示p1处于低地址位置。
指针变量还可以与0比较,如零指针(空指针)
空指针是指指针变量没有指向任何有效的内存地址,即指针变量的值为null或者未初始化。在程序运行过程中,如果对一个空指针进行操作,就会引发空指针异常(NullPointerException),导致程序崩溃或出现其他不可预料的错误。空指针常常是由于未正确初始化指针变量、指针变量赋值为null或者指针变量指向的对象被释放或销毁等情况造成的。为了避免空指针异常,需要在使用指针变量之前进行判空操作,或者在创建指针变量时确保其指向一个有效的对象。
6野指针
野指针是指程序中的一个指针变量,它没有被正确初始化或者它指向的内存已经被释放。当程序访问一个野指针所指向的内存时,会导致不可预料的错误,如程序崩溃、数据损坏等。
野指针产生的原因主要有以下几种情况:
- 指针变量没有被初始化:如果一个指针变量没有被正确初始化,它将具有一个随机的地址值,指向了不确定的内存位置。
- 指针指向的内存已经被释放:如果一个指针变量指向的内存已经被释放,再次访问这个指针就会导致野指针的问题。
#include <stdio.h> #include <stdlib.h> int main() { int *ptr = (int*)malloc(sizeof(int)); *ptr = 10; free(ptr); printf("%d", *ptr); return 0; }
在这个例子中,首先我们使用
malloc
分配了一个整型内存空间,并将其地址赋给指针ptr
。 随后,我们给该内存空间赋值为10。 然后,我们使用free
释放了该内存空间。 最后,我们打印了指针ptr
指向的值。由于
ptr
指向的内存已被释放,继续使用该指针就会导致未定义的行为。 运行这个代码片段会导致程序崩溃或输出错误的结果。 - 指针越界访问(eg:当指针指向的地址超出了所指向的数组的范围时,该指针就是野指针)
#include <stdio.h> int main() { int arr[3] = {1, 2, 3}; int *ptr = arr; // 指针指向数组的第一个元素 printf("ptr[0]: %d\n", ptr[0]); // 输出:ptr[0]: 1 printf("ptr[1]: %d\n", ptr[1]); // 输出:ptr[1]: 2 printf("ptr[2]: %d\n", ptr[2]); // 输出:ptr[2]: 3 // 指针越界访问,访问了数组 arr 之后的内存 printf("ptr[3]: %d\n", ptr[3]); // 输出:ptr[3]: ??? return 0; }
解决野指针问题的方法包括:
- 初始化指针变量:在定义指针变量时,尽量将它初始化为NULL或者一个有效的地址。
- 注意指针的生命周期:在使用指针变量时,要确保它指向的内存是有效的,并且在不需要使用它时及时释放内存。
- 谨慎使用动态内存分配:使用动态内存分配函数(如malloc、new等)时,要确保分配的内存能够正确释放,避免产生野指针。
- 使用智能指针:智能指针是一种能够自动管理内存的指针类型,能够避免野指针的问题。
- 避免返回局部变量的地址
7、指针变量的类型和的意义
指针变量的类型指的是指针所指向的变量的类型,即指针所保存的地址对应的数据类型。这个类型的意义在于确定了指针变量可以指向的变量的类型和在使用指针时需要对指针进行的操作。
不同类型的指针变量有不同的意义和用途。以下是常见的指针变量类型和其意义:
- 整型指针(int*):指向整型数据的指针变量。可以用来访问和操作整型变量,或者用于访问和修改整型数组的元素。
- 字符型指针(char*):指向字符型数据的指针变量。可以用来访问和操作字符变量,或者用于访问和修改字符型数组的元素,也可以用于字符串的处理。
- 浮点型指针(float* 或 double*):指向浮点型数据的指针变量。可以用来访问和操作浮点型变量,或者用于访问和修改浮点型数组的元素。
- 结构体指针(struct*):指向结构体类型数据的指针变量。可以通过指针访问和操作结构体的成员变量,或者用于动态分配和释放结构体类型的内存空间。
- 数组指针(type*[] 或 type(*)[]):指向数组类型的指针变量。可以通过指针访问和操作数组的元素,或者用于动态分配和释放数组类型的内存空间。
- 多级指针(type**):指向指针类型的指针变量。可以通过多级指针实现对指针的间接访问和操作,或者用于实现动态多级指针的分配和释放。
指针变量的类型决定了指针变量的大小和对应的内存空间布局,以及对指针进行的操作的合法性和正确性。在使用指针时,需要注意指针变量的类型与指针操作的一致性,以避免出现类型不匹配、内存越界等错误。
8、const修饰指针
const修饰指针可以有两种方式:
-
const修饰指针变量的值
-
const修饰指针变量本身
也可以将const修饰符同时应用于指针的值和指针本身
#include <stdio.h>
int main() {
const int num = 5; // 声明一个常量num并初始化为5
// num = 10; // 错误,常量不可修改
printf("num = %d\n", num);
return 0;
}
被const修饰后,在语法上加了限制,只要我们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。
在上面的例子中,我们声明了一个常量num
并将其初始化为5。如果我们试图修改这个常量的值(取消注释第6行),则会导致编译错误。
输出结果:
num = 5
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。
9、assert断言
在C语言中,断言(assert)是一个宏,用于在程序中检查条件是否为真。如果条件为假,断言会终止程序并输出错误消息。断言的使用可以帮助开发人员在调试和测试过程中尽早地发现问题。
断言的语法如下:
#include <assert.h>
void assert(int expression);
expression是一个要测试的条件表达式,如果expression为假,则断言失败,程序终止,并输出错误消息。
以下是一个简单的示例,演示了如何使用断言:
#include <stdio.h>
#include <assert.h>
int main() {
int x = 5;
assert(x > 0); // 断言x大于0
printf("x is positive.\n");
return 0;
}
上面的示例中,断言语句assert(x > 0);
用于检查x是否大于0。如果x不大于0,程序将终止,并输出错误消息。否则,程序将继续执行,打印"x is positive."。
需要注意的是,断言通常在调试阶段使用,并不应该在最终发布的产品代码中使用。在发布代码之前,应该确保所有的断言都已经通过了。
10、传值和传址调用
传值调用(pass-by-value)是将函数实参的值复制一份传递给函数的形参,函数内部对形参的修改不会影响实参。
#include <stdio.h>
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10;
int y = 20;
printf("Before swap: x = %d, y = %d\n", x, y);
swap(x, y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
Before swap: x = 10, y = 20
After swap: x = 10, y = 20
可以看到,虽然在swap
函数内部交换了a
和b
的值,但在main
函数中的x
和y
的值并没有改变,这是因为在函数调用时,实际参数x
和y
的值被复制给了形式参数a
和b
,函数内部对形式参数的修改并不会影响实际参数的值。
传址调用(pass-by-reference)是将函数实参的地址传递给函数的形参,函数内部对形参的修改会影响实参。
#include <stdio.h>
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10;
int y = 20;
printf("Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("After swap: x = %d, y = %d\n", x, y);
return 0;
}
这个例子中,我们定义了一个swap
函数,它接受两个整数的地址作为参数。在函数内部,我们使用指针解引用操作符*
来访问传入的地址,并交换两个整数的值。
简单来说,传址调用可以实现在函数内部直接访问和修改实际参数的值。
但是传值调用不能改变参数的值
二、指针与数组
1、使用指针访问数组
- 数组就是数组,是一块连续的空间
- 指针就是指针,是一个连续的变量
- 数组名是指针,是元素的首地址
- 可以使用指针访问数组
1.1对数组名的理解
数组名是一个指向数组的指针,数组名可以看作是数组首元素的地址。通过数组名,可以访问数组中的元素,例如通过索引来访问特定位置的元素。数组名的类型是一个指针类型,指针类型表示了该指针所指向对象的类型。不同于一般的指针,数组名不能进行递增或递减运算,因为数组名指向的是整个数组而不是数组中的某个元素。
int a[4]={1,2,3,4};
int *p=&arr[0]; / /这里的&arr[0] 就是表示数组的第一通过元素的地址,所以数组名就是首元素的地址
但是有两个例外:
- sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
- &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)除此之外,任何地方使用数组名,数组名都表元素的地址
1.2指针的使用
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5};//定义一个整型数组
int *ptr = arr;//将指针指向数组的首地址
for(int i = 0; i < 5; i++)
{
printf("arr[%d] = %d\n", i,*(ptr+i));//使用指针访问数组元素
}
return 0;
}
其输出结果为:
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
在这个例子中,我们定义了一个整型数组arr
,然后定义了一个指针变量ptr
并将其指向数组的首地址。然后使用*(ptr+i)
的方式来访问数组中的元素,其中i
是数组的索引。在循环中,我们使用指针来访问数组中的每个元素,并打印出来。
2、数组指针
数组指针是指向数组的指针变量。它可以指向数组的第一个元素的地址,通过指针可以访问或操作数组中的元素。
例如,int* p 表示一个指向int类型的指针变量p,可以用来指向一个int类型的数组。
使用数组指针可以方便地对数组进行操作,比如遍历数组、修改数组元素的值等。同时,数组指针也可以作为函数的参数来传递数组,以便在函数内部对数组进行操作。当然,也可以在函数内部返回指向数组的指针。
以下是对数组指针的一些常见操作:
1. 定义数组指针:可以使用指针运算符*来定义一个指向数组的指针,例如int* p;
2. 初始化数组指针:可以将数组名直接赋值给指针变量,例如p = arr;
3. 访问数组元素:可以使用指针运算符*来访问数组元素的值,例如*p 表示数组的第一个元素,*(p+1) 表示数组的第二个元素;
4. 遍历数组:可以使用循环结构和指针变量来遍历数组,例如for(int i = 0; i < n; i++),可以通过*(p+i)或者p[i]来访问数组元素;
5. 修改数组元素:可以使用指针运算符*来修改数组元素的值,例如*p = 10,将数组的第一个元素的值修改为10。
3、指针数组
就是存放指针的数组
指针数组的每个元素都是⽤来存放地址(指针)的
指针数组的每个元素是地址,又可以指向⼀块区域。
4、二维数组
二维数组是C语言中一种特殊的数组类型。它可以看作是一个元素为一维数组的数组。
在C语言中,声明一个二维数组的语法如下:
数据类型 数组名[行数][列数];
其中,数据类型表示数组中元素的类型,数组名是数组的名称,行数和列数分别表示数组的行数和列数。
例如,声明一个名为matrix
的二维数组,其中有3行4列的整数数组,可以这样写:
int a[2][3]
要访问二维数组中的元素,可以使用两个索引,一个表示行号,一个表示列号。例如,要访问matrix
中的第2行第3列的元素,可以写成:
matrix[1][2];
可以使用循环遍历二维数组的所有元素。例如,可以使用嵌套循环来遍历matrix
中的所有元素:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
这段代码会逐行打印出matrix
中的所有元素。
需要注意的是,二维数组中的每一行的元素个数必须相同,但不同行的元素个数可以不同。
5、二维指针
在C语言中,二维指针是指指向指针的指针。它可以用来表示二维数组,或者是指向指针数组的指针。
以下是一个用二维指针表示二维数组的示例:
#include <stdio.h>
int main() {
int row = 3;
int col = 4;
// 动态分配内存来存储二维数组
int **arr = (int **)malloc(row * sizeof(int *)); // 第一维
for (int i = 0; i < row; i++) {
arr[i] = (int *)malloc(col * sizeof(int)); // 第二维
}
// 初始化二维数组
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
arr[i][j] = i * col + j;
}
}
// 打印二维数组
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
// 释放内存
for (int i = 0; i < row; i++) {
free(arr[i]);
}
free(arr);
return 0;
}
以上代码中,我们首先动态分配了一个二维指针arr
来表示二维数组,然后再根据实际的行和列数分别为第一维和第二维分配相应的内存空间。
然后,我们通过两个嵌套的循环来初始化二维数组,并使用两个嵌套的循环来打印二维数组的内容。
最后,我们需要释放动态分配的内存空间,以防止内存泄漏。
需要注意的是,二维指针在内存中是以连续的一维指针数组的形式存储的。因此,在访问二维指针表示的二维数组时,可以使用arr[i][j]
的方式进行访问。
三、不同类型的指针变量
1、字符指针变量
字符指针变量是指存储字符地址的变量,它的类型是char*。字符指针变量可以指向单个字符或者字符串的首字符地址。通过字符指针变量可以访问和操作字符数据。由于字符串实际上是以字符数组的形式存储的,所以字符指针变量可以用于处理字符串。例如,使用字符指针变量可以遍历字符串的每个字符,查找特定字符,拷贝字符串等操作。
下面是一个简单的C语言代码示例,演示了如何声明和使用字符指针变量:
#include <stdio.h>
int main() {
char str[] = "Hello, world!"; // 声明并初始化一个字符数组
char *ptr; // 声明一个字符指针变量
ptr = str; // 将字符数组的首地址赋值给指针变量
printf("str = %s\n", str); // 输出字符数组的内容
printf("ptr = %s\n", ptr); // 输出指针变量所指向的字符串
return 0;
}
在这个示例中,首先声明了一个字符数组 str
,并用 "Hello, world!"
初始化了它。然后,声明了一个字符指针变量 ptr
。接着,将字符数组的首地址赋值给指针变量 ptr
。最后,使用 printf()
函数分别输出了字符数组的内容和指针变量所指向的字符串。
运行结果将是:
str = Hello, world!
ptr = Hello, world!
这说明字符数组和字符指针变量在这种情况下是等价的,它们都指向存储在内存中的同一个字符串。
2、数组指针变量
数组指针变量是指一个指针变量,其指向的地址是一个数组的首地址。可以通过数组指针变量来操作数组的元素。例如,定义一个整型数组指针变量`int *p`,可以通过`p[i]`来访问数组的第i个元素。还可以用指针运算来遍历整个数组。
数组指针变量在C语言中的代码示例:
```c
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5}; // 定义一个整型数组
int *ptr; // 声明一个整型指针变量
ptr = arr; // 将数组的首地址赋给指针变量
// 通过指针遍历数组并打印元素
for (int i = 0; i < 5; i++) {
printf("%d ", *ptr); // 打印指针指向的值
ptr++; // 指针加一,指向下一个元素
}
return 0;
}
上述代码首先定义了一个整型数组`arr`,并初始化了数组的值。然后声明了一个整型指针变量`ptr`。
将数组的首地址赋给指针变量`ptr`,即`ptr = arr`。
然后通过指针变量`ptr`遍历数组,并打印数组中的元素。
通过指针变量`ptr`访问数组的元素需要使用`*ptr`的方式,其中`*`是指针解引用符号。
在循环中,每次迭代结束后将指针变量`ptr`加一,指向下一个元素。最后输出结果为:`1 2 3 4 5`
3、函数指针变量
函数指针变量是指一个指针变量,它可以指向一个函数的地址。函数指针变量可以用来调用函数,相当于间接调用函数。
#include <stdio.h>
void add(int a, int b) {
printf("Sum: %d\n", a + b);
}
void subtract(int a, int b) {
printf("Difference: %d\n", a - b);
}
void multiply(int a, int b) {
printf("Product: %d\n", a * b);
}
void divide(int a, int b) {
printf("Quotient: %d\n", a / b);
}
int main() {
void (*func_ptr)(int, int); // 声明函数指针
func_ptr = add; // 将函数指针指向 add 函数
func_ptr(5, 3); // 通过函数指针调用 add 函数
func_ptr = subtract; // 将函数指针指向 subtract 函数
func_ptr(5, 3); // 通过函数指针调用 subtract 函数
func_ptr = multiply; // 将函数指针指向 multiply 函数
func_ptr(5, 3); // 通过函数指针调用 multiply 函数
func_ptr = divide; // 将函数指针指向 divide 函数
func_ptr(5, 3); // 通过函数指针调用 divide 函数
return 0;
}
此程序声明了一个函数指针 func_ptr
,然后将其分别指向 add
、subtract
、multiply
和 divide
函数。最后通过函数指针调用了这些函数,实现函数的动态调用。
四、回调函数
1、回调函数
回调函数是一种常见的编程模式,用于异步编程。在回调函数实现中,一个函数作为参数传递给另一个函数,并在特定事件发生时被调用。
简单来说回调函数就是一个通过函数指针调用的函数
以下是一个使用C语言实现回调函数的示例:
```c
#include <stdio.h>
// 定义回调函数类型
typedef void (*callback)(void);
// 主函数
void main_function(callback cb) {
printf("Main function starts\n");
// 模拟一些操作
for (int i = 0; i < 3; i++) {
printf("Performing operation %d\n", i);
}
// 调用回调函数
cb();
printf("Main function ends\n");
}
// 回调函数
void callback_function() {
printf("Callback function called\n");
}
int main() {
// 调用主函数,并将回调函数作为参数传递
main_function(callback_function);
return 0;
}
在上述例子中,我们首先定义了一个回调函数类型 `callback`,它是一个指向无返回值、无参数的函数指针。然后,我们定义了主函数 `main_function`,它接受一个回调函数作为参数,并在适当的时候调用该回调函数。
在 `main` 函数中,我们调用了 `main_function(callback_function)`,将 `callback_function` 作为回调函数传递给 `main_function`。
执行上述C代码,输出将是:
Main function starts
Performing operation 0
Performing operation 1
Performing operation 2
Callback function called
Main function ends
可以看到,`main_function` 在执行一些操作后,调用了传递的回调函数 `callback_function`。这个例子展示了使用C语言实现回调函数的基本方法。你可以根据实际需求来编写和使用回调函数。
2、qsort函数
qsort函数是C语言中的一个标准库函数,用于对数组进行快速排序。它的原型为
void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));
其中,base是需要排序的数组的起始地址,num是数组中元素的个数,size是每个元素的大小,compar是一个函数指针,用于确定排序的顺序。
compar函数的原型为:int compar(const void *a, const void *b);
该函数比较a和b的值,并返回一个整数值以表示排序的顺序。具体来说,如果a小于b,则返回负数;如果a等于b,则返回0;如果a大于b,则返回正数。
使用qsort函数进行排序的示例代码如下:
#include <stdio.h>
#include <stdlib.h>
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int main() {
int arr[] = {5, 2, 8, 9, 1, 3};
int size = sizeof(arr) / sizeof(arr[0]);
qsort(arr, size, sizeof(int), compare);
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
return 0;
}
以上代码将数组arr进行升序排序,并输出排序后的结果。输出为:1 2 3 5 8 9。
需要注意的是,使用qsort函数进行排序时,要确保传入的数组元素类型和比较函数的参数类型匹配,以避免类型错误。
五、sizeof与strlen
sizeof是一个运算符,用于获取一个类型或变量的字节大小,而strlen是一个函数,用于获取一个字符串的长度(不包括字符串末尾的null字符)。
sizeof返回的是指定类型或变量在内存中所占的字节数,以字节为单位计算。它可以用于任何数据类型,包括基本数据类型(如int、float)和复合数据类型(如结构体、数组等),并且在编译时就可以确定。
strlen返回的是指定字符串的长度,即字符串中字符的个数,用整数表示。它只能用于以null字符结尾的字符串,因为它通过不断地向后遍历字符串中的字符,直到遇到null字符为止。
在使用上,sizeof通常在编译时计算,而strlen是在运行时计算。sizeof通常用于需要知道变量或类型的大小的情况,而strlen通常用于需要知道字符串长度的情况。
需要注意的是,sizeof返回的是字节大小,而strlen返回的是字符个数。对于字符串来说,一个字符可能占用多个字节(如UTF-8编码),所以sizeof和strlen的结果并不一定相等。