目录
[C 中的 NULL 指针](#C 中的 NULL 指针)
在学习指针之前,我们先弄清楚一个概念:
地址
地址在计算机内存中是一个唯一的标识符,用于指示变量或数据在内存中的位置。简单地说:地址就是可以唯一标识某一点的一个编号,即一个数字!我们都见过尺子,我们统一以毫米为单位,一把长1000毫米的尺子,其范围区间为0~999,而我们可以准确的找到35毫米、256毫米处的位置。
在C语言中,每个变量都在内存中占据一定的空间,这些空间是连续的,并且每个空间都有一个唯一的地址。通过使用取地址运算符&,我们可以获取变量的地址。例如,对于一个整型变量x,可以使用&x来获取它的地址。
地址通常以十六进制表示法来表示,因为它更紧凑且易于阅读。在C语言中,我们可以使用指针来存储和操作地址。指针是一种特殊的变量类型,它可以存储一个地址值。
通过指针,我们可以实现对变量的间接访问。通过解引用运算符*,我们可以访问指针所指向的地址处存储的值。例如,如果有一个指向整型变量的指针ptr,可以使用*ptr来获取该变量的值。
总而言之,地址在C语言中是非常重要的概念,它允许我们直接访问和修改变量的值。通过使用指针,我们可以存储和操作这些地址。地址的表示通常以十六进制形式呈现,方便理解和使用。
指针的定义和使用
指针是C语言中的一个重要概念,它允许程序直接访问和修改变量的值。在C语言中,指针是一种特殊的变量类型,它可以存储一个内存地址。
指针的定义方式如下:
cpp
type *pointer_name;
其中,type表示指针所指向的变量类型,pointer_name是指针变量的名称。例如,如果有一个整型变量x,可以定义一个指向该变量的指针如下:
cpp
int *ptr;
这样就定义了一个名为ptr的指向整型变量的指针。注意,在定义指针时,需要使用*运算符来指示该变量是一个指针变量。
指针的使用可以分为两个方面:指针的赋值和指针的解引用。
指针的赋值可以通过将一个变量的地址赋值给指针变量来实现。例如,如果有一个整型变量x,可以将它的地址赋值给指针变量ptr如下:
cpp
int x = 10;
int *ptr = &x;
这样就将ptr指向了x的地址。注意,在赋值时,需要使用取地址运算符&来获取变量x的地址。
指针的解引用可以通过解引用运算符*来实现。例如,如果有一个指向整型变量的指针ptr,可以使用*ptr来获取该变量的值。例如:
cpp
int x = 10;
int *ptr = &x;
printf("%d", *ptr); // 输出10
这样就输出了x的值。
除了指针的赋值和解引用之外,还有一些其他的指针操作,例如指针的算术运算、指针与数组的关系、指针的类型转换等等。这些操作都是指针的进阶知识,需要更深入的学习和理解。
总之,指针是C语言中非常重要的概念,它允许程序直接访问和修改变量的值。指针的定义方式为type *pointer_name,指针的赋值和解引用可以通过&和*运算符实现。
数组与指针的区别与联系
C语言中的数组和指针都是非常重要的概念,它们在很多情况下可以互相转换和使用。但是,数组和指针之间也存在一些区别。
首先,数组是一种特殊的数据结构,它可以存储一组相同类型的数据。在C语言中,数组的定义方式如下:
cpp
type array_name[array_size];
其中,type表示数组元素的类型,array_name是数组的名称,array_size是数组的大小。例如,如果有一个包含5个整型元素的数组,可以定义如下:
cpp
int arr[5];
指针是一种特殊的变量类型,它可以存储一个内存地址。在C语言中,指针的定义方式如下:
cpp
type *pointer_name;
其中,type表示指针所指向的变量类型,pointer_name是指针变量的名称。例如,如果有一个整型变量x,可以定义一个指向该变量的指针如下:
cpp
int *ptr = &x;
指针的值可以通过解引用运算符*来获取。例如,可以使用*ptr来获取指针所指向的变量的值。
虽然数组和指针看起来有些不同,但它们之间也存在一些联系。首先,数组名可以被解释为指向数组第一个元素的指针。例如,如果有一个整型数组arr,则可以使用arr或&arr[0]来获取数组第一个元素的地址。
其次,指针和数组之间可以进行互相转换。例如,可以使用指针加法运算来遍历数组元素,也可以使用数组下标来访问指针所指向的内存。例如:
cpp
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // 将数组名赋值给指针
for (int i = 0; i < 5; i++) {
printf("%d ", *(ptr + i)); // 使用指针遍历数组元素
}
printf("\n");
printf("%d\n", *(arr + 2)); // 使用数组名访问指针所指向的内存
//输出:1 2 3 4 5
//输出:3
这样就可以使用指针和数组来访问和修改同一个数据。
总之,数组和指针是C语言中非常重要的概念,它们在很多情况下可以互相转换和使用。数组是一种特殊的数据结构,用于存储一组相同类型的数据,而指针是一种特殊的变量类型,用于存储一个内存地址。数组名可以被解释为指向数组第一个元素的指针,指针和数组之间可以进行互相转换。
字符串与指针的用法
前面我们已经讨论过字符数组与字符串,字符指针和字符数组都可以用来存储字符串,但它们有着本质的区别:字符指针str是个变量,可以改变str使它指向不同的字符串,但不能改变str所指向的字符串常量的值。而string是一个数组,可以改变数组中保存的内容。应注意字符串指针和字符串数组的区别。
字符串指针是一种特殊的指针类型,它用于指向以空字符('\0')结尾的字符串。
字符串指针定义的语法格式为:
cpp
char *str;
其中,char:表示字符类型。*:表示指针类型的声明符号,用于声明一个指针变量。str:是指针变量的名称。
可以通过以下几种方式来初始化字符串指针:
1、直接将字符串常量赋值给字符串指针
cpp
char *str = "Hello, world!";
这种方式会将字符串常量的首地址赋值给字符串指针,使其指向该字符串。
需要注意的是,使用字符串常量初始化字符串指针时,不能通过指针来修改字符串中的内容,因为字符串常量是只读的。如果需要修改字符串中的内容,应该使用字符数组。
2、将字符数组的首地址赋值给字符串指针
cpp
char string[] = "Hello, world!";
char *str = string;
这种方式会将字符数组的首地址赋值给字符串指针,使其指向该字符数组。
与字符串常量不同,使用字符数组初始化字符串指针时,可以通过指针来修改字符数组中的内容。
3、动态分配内存并将其地址赋值给字符串指针
cpp
char *str = (char*)malloc(sizeof(char) * 20);
strcpy(str, "Hello, world!");
这种方式会动态分配一段内存空间,并将其地址赋值给字符串指针。可以使用strcpy()函数将字符串常量或字符数组的内容复制到动态分配的内存空间中。
需要注意的是,在使用完动态分配的内存空间后,应该使用free()函数将其释放,以避免内存泄漏。
总之,字符串指针是一种非常常用的数据类型,在C语言中被广泛地应用于字符串处理、文件操作等方面。需要注意的是,在使用字符串指针时,应该注意指针的有效性和字符串的长度,避免出现指针越界或内存泄漏等问题。
C 中的 NULL 指针
在C语言中,NULL指针是一个特殊的指针值,表示指针不指向任何有效的内存地址。它是一个预定义的宏,通常被定义为整数0。
当一个指针被赋值为NULL时,它就指示不再指向任何有效的对象或函数。这在编程中很有用,可以用来表示指针的初始状态或者指针未被初始化的情况。
下面是一些关于NULL指针的重要事实和使用方式:
1、初始化指针:在声明指针变量时,可以将其初始化为NULL,例如:int *ptr = NULL;。这样做可以确保指针不会包含任意的垃圾值。
2、检查指针是否为NULL:在使用指针之前,通常需要检查它是否为NULL,以避免访问无效的内存地址。可以使用条件语句(如if语句)来检查指针是否为NULL,例如:
cpp
if (ptr == NULL) {
// 指针为空的处理逻辑
}
3、作为函数返回值:当函数无法成功执行某个操作时,可以将NULL作为函数的返回值,用于表示失败或错误的情况。调用函数后,可以检查返回的指针是否为NULL,以确定函数是否成功执行。
4、动态内存分配失败:在使用动态内存分配函数(如malloc、calloc等)分配内存时,如果分配失败,函数将返回NULL指针。因此,在使用动态内存之前,应该检查返回的指针是否为NULL,以确保成功分配了所需的内存。
需要注意的是,对NULL指针进行解引用操作是非法的,会导致程序崩溃或未定义行为。因此,在使用指针之前,始终要确保它不为NULL。
使用NULL指针可以提高代码的可读性和健壮性,有助于避免悬空指针和空指针引发的错误。在编写C程序时,合理地使用NULL指针是一个良好的编程习惯。
指针的算术运算
在C语言中,指针的算术运算允许对指针进行加法、减法和比较操作。这些算术运算使得可以在指针上进行递增和递减操作,以及计算两个指针之间的距离。
下面是关于C语言指针算术运算的详细说明:
-
指针的加法运算:可以将一个整数值加到指针上,得到一个新的指针。这个整数值表示要移动的元素数量,而不是字节数量。例如,`ptr + 1` 将返回指针 `ptr` 后面一个元素的地址。如果 `ptr` 指向 int 类型的数组,那么 `ptr + 1` 将指向数组中下一个 int 元素的地址。
-
指针的减法运算:可以将一个整数值从指针中减去,得到一个新的指针。这个整数值表示要向前移动的元素数量。例如,`ptr - 1` 将返回指针 `ptr` 前面一个元素的地址。同样地,如果 `ptr` 指向 int 类型的数组,那么 `ptr - 1` 将指向数组中前一个 int 元素的地址。
-
指针之间的减法运算:可以计算两个指针之间的元素数量。结果是一个整数类型,表示两个指针之间的元素个数。例如,`ptr2 - ptr1` 将返回指针 `ptr1` 和 `ptr2` 之间的元素个数。
-
比较运算符:可以使用比较运算符(如 `<`、`>`、`<=`、`>=`、`==`、`!=`)来比较两个指针的关系。这些比较运算符可以用于判断指针是否指向同一块内存区域、指针的相对位置以及指针与NULL之间的关系。
需要注意的是,指针的算术运算应该谨慎使用,以避免越界访问或计算错误的地址。在进行指针算术运算时,应确保指针指向的内存区域是有效的,并且不会超出数组的边界。否则,将导致未定义行为和程序错误。
此外,还要注意指针的类型。指针的算术运算是根据指针所指向的对象类型来计算的。因此,在进行指针算术运算时,需要确保指针的类型与操作对象的类型匹配,以避免错误的计算结果。
总结起来,C语言中的指针算术运算提供了一种方便的方式来在指针上进行递增、递减和比较操作。正确使用指针算术运算可以简化代码,并且在处理数组和数据结构时非常有用。但是,需要谨慎处理指针的边界情况,以确保程序的正确性和健壮性。
cpp
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr; // 指向数组的第一个元素的指针
// 指针的加法运算
printf("第一个元素的值:%d\n", *ptr);
ptr++; // 移动到下一个元素
printf("第二个元素的值:%d\n", *ptr);
// 指针的减法运算
ptr--; // 移动回第一个元素
printf("再次打印第一个元素的值:%d\n", *ptr);
// 指针之间的减法运算
int *ptr2 = &arr[3]; // 指向数组的第四个元素的指针
int elements = ptr2 - ptr; // 计算两个指针之间的元素个数
printf("两个指针之间的元素个数:%d\n", elements);
// 比较运算符
if (ptr == &arr[0]) {
printf("ptr指向数组的第一个元素\n");
}
if (ptr < ptr2) {
printf("ptr指向的元素在ptr2指向的元素之前\n");
}
return 0;
}
输出结果:
cpp
第一个元素的值:1
第二个元素的值:2
再次打印第一个元素的值:1
两个指针之间的元素个数:3
ptr指向数组的第一个元素
ptr指向的元素在ptr2指向的元素之前
指向指针的指针
在C语言中,可以使用指向指针的指针(Pointer to Pointer)来间接引用指针。指向指针的指针是一个指针,它存储了另一个指针的地址。通过使用指向指针的指针,我们可以修改指针的值,进而改变所指向的数据。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
cpp
int **var;
下面是一个示例代码,演示了指向指针的指针的使用:
cpp
#include <stdio.h>
int main() {
int value = 10;
int *ptr = &value; // 指向value的指针
int **ptr2 = &ptr; // 指向ptr的指针
printf("value的值:%d\n", value);
printf("ptr指向的值:%d\n", *ptr);
printf("ptr2指向的值:%d\n", **ptr2);
// 修改value的值
*ptr = 20;
printf("修改后的value的值:%d\n", value);
printf("通过ptr2间接修改后的value的值:%d\n", **ptr2);
return 0;
}
在上述示例中,我们首先定义了一个整型变量 value,然后定义了指向 value 的指针 ptr,最后定义了指向 ptr 的指针 ptr2。
通过使用 *ptr,我们可以访问和修改 value 的值。而通过使用 **ptr2,我们可以间接地访问和修改 value 的值。
输出结果:
cpp
value的值:10
ptr指向的值:10
ptr2指向的值:10
修改后的value的值:20
通过ptr2间接修改后的value的值:20
这个示例展示了指向指针的指针的用法。通过多级间接引用,我们可以在需要时修改指针所指向的数据。指向指针的指针在某些情况下很有用,例如在函数中传递指针的地址,或者在动态内存分配和释放过程中进行操作。
需要注意的是,指向指针的指针可以有多级,但在实际应用中,过多的间接引用可能会导致代码难以理解和维护。因此,在使用指向指针的指针时,应根据实际需求谨慎选择合适的级别。
传递指针给函数
在C语言中,可以将指针作为参数传递给函数。这种方式被称为传递指针给函数(Passing Pointers to Functions),它可以让函数直接修改指针所指向的数据,从而避免了在函数内部进行数据的复制和传递,提高了程序的效率。
下面是一个简单的示例代码,演示了如何将指针作为参数传递给函数:
cpp
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
printf("交换前:x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后:x=%d, y=%d\n", x, y);
return 0;
}
在上述示例中,我们定义了一个 swap 函数,它接收两个整型指针作为参数。在函数内部,我们使用指针间接访问和修改了变量的值,从而实现了变量的交换。
在 main 函数中,我们定义了两个整型变量 x 和 y,并通过 & 运算符获取了它们的地址,并将这些地址作为参数传递给了 swap 函数。在函数执行完毕后,我们可以看到变量的值已经被交换了。
输出结果:
cpp
交换前:x=10, y=20
交换后:x=20, y=10
这个示例展示了如何将指针作为参数传递给函数,并在函数内部修改指针所指向的数据。通过使用指针,我们可以避免在函数内部进行数据的复制和传递,从而提高了程序的效率。
需要注意的是,在传递指针给函数时,应该确保指针所指向的数据是有效的,否则可能会导致程序崩溃或者出现不可预期的行为。同时,应该避免在函数内部修改指针本身的值,以免影响到调用函数的代码。
从函数返回指针
在C语言中,可以从函数中返回指针。这种方式被称为从函数返回指针(Returning Pointers from Functions)。通过返回指针,函数可以将动态分配的内存或者已存在的变量的地址传递给调用函数,使得调用函数能够访问和操作该内存或变量。
为了做到这点,必须声明一个返回指针的函数,如下所示:
cpp
int * myFunction()
{
.
.
.
}
注意:'*' 位置可在中间,也可在两边。
下面是一个简单的示例代码,演示了如何从函数中返回指针:
cpp
#include <stdio.h>
#include <stdlib.h>
int* createArray(int size) {
int* arr = (int*)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr;
}
int main() {
int size = 5;
int* array = createArray(size);
printf("数组元素:");
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
free(array);
return 0;
}
在上述示例中,我们定义了一个 createArray 函数,它接收一个整型参数 size,并返回一个 int 类型的指针。在函数内部,我们使用 malloc 函数动态分配了一个大小为 size 的整型数组,并将数组的首地址赋值给指针 arr。然后,我们使用循环初始化了数组的元素,并最终返回了指针 arr。
在 main 函数中,我们调用了 createArray 函数,并将返回的指针赋值给 array。然后,我们通过遍历指针所指向的数组,打印了数组的元素。最后,我们使用 free 函数释放了动态分配的内存。
输出结果:
cpp
数组元素:1 2 3 4 5
这个示例展示了如何从函数中返回指针。通过动态分配内存并返回指针,我们可以在函数外部访问和操作该内存。需要注意的是,在使用完返回的指针后,应该及时释放动态分配的内存,以防止内存泄漏。
另外,还需要注意以下几点:
- 返回指针时,应确保指针所指向的内存在函数返回后仍然有效。例如,不要返回局部变量的地址,因为局部变量的生命周期仅限于函数的执行过程。
- 如果函数无法成功分配内存或者出现其他错误,应该定义一种错误处理机制,例如返回一个特殊的指针值(例如 NULL)来表示错误。
- 在接收返回的指针时,应该进行必要的检查,确保指针不为空再进行后续操作,以避免空指针引发的问题。
总之,通过从函数返回指针,我们可以实现更灵活的内存管理和数据传递方式。但在使用时需要谨慎处理,以确保指针的有效性和正确释放内存。
函数指针
在C语言中,函数指针是指向函数的指针变量。它可以用来存储函数的地址,并允许我们通过指针调用函数或将函数作为参数传递给其他函数。函数指针使得我们可以动态地选择要调用的函数,从而实现更加灵活和通用的代码设计。
函数指针变量的声明:
cpp
typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
以下是关于C语言函数指针的详细说明:
1、函数指针的声明
函数指针的声明包括函数的返回类型、指针变量的名称以及函数的参数列表。例如,int (*ptr)(int, int) 声明了一个名为 ptr 的函数指针,该函数指针指向一个返回值为 int 类型、接受两个 int 类型参数的函数。
2、函数指针的赋值
函数指针可以通过赋值来指向特定的函数。赋值时,可以直接使用函数名作为函数指针,因为函数名本身就表示函数的地址。例如,ptr = add; 将函数 add 的地址赋值给函数指针 ptr。
3、函数指针的调用
通过函数指针调用函数时,可以使用间接运算符 * 或者函数指针名后加括号 ()。例如,result = (*ptr)(a, b); 或者 result = ptr(a, b); 都可以调用函数指针 ptr 所指向的函数,并将返回值赋给 result。
4、函数指针作为参数
函数指针可以作为参数传递给其他函数,从而实现回调函数的机制。通过将函数指针作为参数传递给其他函数,我们可以在调用函数时指定要执行的具体函数。这种技术在事件处理、排序算法等场景中非常有用。
下面是一个简单的示例代码,演示了函数指针的使用:
cpp
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
void calculator(int (*operation)(int, int), int a, int b) {
int result = operation(a, b);
printf("计算结果:%d\n", result);
}
int main() {
int (*ptr)(int, int);
ptr = add;
int sum = ptr(3, 5);
printf("3 + 5 = %d\n", sum);
ptr = subtract;
int difference = (*ptr)(8, 4);
printf("8 - 4 = %d\n", difference);
calculator(add, 2, 3);
calculator(subtract, 10, 7);
return 0;
}
在上述示例中,我们定义了两个函数 add 和 subtract,它们分别实现了加法和减法运算。然后,我们定义了一个名为 calculator 的函数,它接受一个函数指针 operation 和两个整型参数,并在内部调用了指针所指向的函数。
在 main 函数中,我们声明了一个名为 ptr 的函数指针。首先,我们将 ptr 指向 add 函数,并使用函数指针调用了加法运算。然后,我们将 ptr 指向 subtract 函数,并使用间接运算符 * 调用了减法运算。接下来,我们通过调用 calculator 函数,将 add 和 subtract 函数作为参数传递给 operation,并在内部调用了指定的函数。
输出结果:
cpp
3 + 5 = 8
8 - 4 = 4
计算结果:5
计算结果:3
这个示例展示了函数指针的基本用法。通过函数指针,我们可以在运行时选择要调用的函数,并将函数作为参数传递给其他函数。函数指针提供了一种灵活和通用的方式来设计和实现代码逻辑。
回调函数
在C语言中,回调函数是一种特殊的函数,它可以作为参数传递给其他函数,并在该函数内部被调用。回调函数通常用于实现事件处理、排序算法等场景,它允许我们在运行时指定要执行的具体操作,从而实现更加灵活和通用的代码设计。
以下是关于C语言回调函数的详细说明:
1、回调函数的定义
回调函数的定义与普通函数类似,包括返回值类型、函数名和参数列表。例如,void callback(int arg) 定义了一个名为 callback 的回调函数,该函数接受一个 int 类型参数,并没有返回值。
2、回调函数的使用
回调函数通常作为参数传递给其他函数,在该函数内部被调用。例如,void sort(int *array, int size, int (*compare)(int, int)) 函数接受一个整型数组 array、数组大小 size 和一个函数指针 compare,该函数指针指向一个接受两个 int 类型参数并返回 int 类型值的比较函数。在 sort 函数内部,可以通过调用 compare 函数来实现不同的排序方式。
3、回调函数的实现
回调函数的实现通常与具体的场景相关。例如,如果回调函数用于事件处理,可以在函数内部实现相应的事件处理逻辑;如果回调函数用于排序算法,可以在函数内部实现不同的比较方式。回调函数的实现需要考虑参数的类型和数量,以及返回值的类型和意义。
下面是一个简单的示例代码,演示了回调函数的使用:
cpp
#include <stdio.h>
void event_handler(int arg) {
printf("事件处理:%d\n", arg);
}
void sort(int *array, int size, int (*compare)(int, int)) {
for (int i = 0; i < size - 1; i++) {
for (int j = i + 1; j < size; j++) {
if (compare(array[i], array[j]) > 0) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
int ascending(int a, int b) {
return a - b;
}
int descending(int a, int b) {
return b - a;
}
int main() {
int array[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
int size = sizeof(array) / sizeof(int);
sort(array, size, ascending);
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
sort(array, size, descending);
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
event_handler(123);
return 0;
}
在上述示例中,我们定义了一个名为 event_handler 的回调函数,该函数接受一个 int 类型参数,并在内部输出事件处理信息。然后,我们定义了一个名为 sort 的排序函数,该函数接受一个整型数组 array、数组大小 size 和一个函数指针 compare,在内部通过调用 compare 函数实现不同的排序方式。
在 main 函数中,我们声明了一个名为 array 的整型数组,并将其传递给 sort 函数,分别使用升序和降序的方式对数组进行排序,并输出排序结果。然后,我们通过直接调用 event_handler 函数来执行事件处理操作。
输出结果:
cpp
1 1 2 3 3 4 5 5 6 9
9 6 5 5 4 3 3 2 1 1
事件处理:123
这个示例展示了回调函数的基本用法。通过回调函数,我们可以在运行时指定要执行的具体操作,并将函数作为参数传递给其他函数。回调函数提供了一种灵活和通用的方式来设计和实现代码逻辑。