目录
前言1. 深入理解指针(一)1.1 指针概括1.2 内存和地址1.3 指针变量和地址1.3.1 指针变量1.3.2 地址1.3.3 指针变量和地址的关系 1.4 解引用操作符1.5 指针变量的大小1.6 指针变量类型的意义1.6.1 指针的解引用1.6.2 指针的简单加减1.6.3 void* 指针 1.7 const 修饰指针1.7.1 const修饰变量1.7.2 const修饰指针变量 1.8 指针运算1.8.1 指针 + 整数1.8.2 指针 - 指针1.8.3 指针的关系运算 1.9 野指针1.9.1 野指针成因1.9.2 如何规避野指针1.9.2.1 指针初始化1.9.2.2 小心指针越界1.9.2.3 指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性 1.10 assert断言1.10.1 assert断言理解1.10.2 assert断言举例 1.11 指针的使用和传址调用1.11.1 strlen的模拟实现1.11.2 传值调用和传址调用1.11.2.1 传值调用(Call by Value)1.11.2.2 传址调用(Call by Reference) 2. 深入理解指针(二)2.1 数组名2.2 使用指针访问数组2.3 一维数组传参的本质2.4 冒泡排序2.5 二级指针2.6 指针数组2.7 指针数组模拟二维数组 3. 深入理解指针(三)3.1 指针变量3.1.1 字符指针变量3.1.2 数组指针变量3.1.3 函数指针变量 3.2 二维数组传参的本质3.3 函数指针数组3.4 转移表3.4.1 基于数组的转移表3.4.2 基于链表的转移表3.4.3 计算器的⼀般实现 4. 深入理解指针(四)4.1 回调函数4.2 qsort函数4.2.1 qsort函数概括4.2.2 使用qsort函数排序整型数据4.2.3 使用qsort函数排序结构数据 4.3 qsort函数的模拟实现4.4 sizeof 和 strlen4.4.1 两者概括4.4.2 两者区别 5. 深入理解指针(五)5.1 十道经典指针编程题5.2 参考答案 结语
上期回顾: 【C语言】操作符详解
个人主页:C_GUIQU
归属专栏:C语言
前言
各位小伙伴大家好!
今日天有晴,阳光灿烂;今日地有情,花团锦簇;今日海有情,浪迭千重;今日人有情,欢聚一堂!
指针是C语言的灵魂,同时也是大多数人的噩梦。不懂指针,相当于没学C语言。
上期小编给大家讲解了C语言中的操作符,接下来我们好好地细说指针!
1. 深入理解指针(一)
1.1 指针概括
【指针概念】指针是一个变量,它的值是一个内存地址。
当我们说一个指针指向一个变量时,实际上是指它存储了该变量的内存地址。
指针的类型:指针有特定的类型,这决定了它可以指向的数据类型。例如,int* 是一个指针类型,它可以指向一个整数类型的变量。
指针的声明:指针需要声明其类型。例如,int* ptr; 声明了一个整数指针变量 ptr。
指针的初始化:指针在使用前需要被初始化,即给它一个有效的内存地址。这通常通过取变量地址来实现,例如int num = 10; int* ptr = & num;。
指针的解引用:指针的解引用是指通过指针访问它所指向的变量。例如,*ptr 表示指针 ptr 所指向的变量。
指针的算术运算:指针可以进行算术运算,以移动其在内存中的位置。例如,ptr += 1 会将 ptr 移动到下一个内存地址。
指针与数组:指针可以用来访问数组中的元素。例如,int arr[10]; int* ptr = arr; 之后,ptr[3] 将会访问数组 arr 的第四个元素。
指针与函数:指针可以作为函数的参数传递。这允许函数修改调用者提供的内存地址所指向的数据。
指针与动态内存分配:指针可以用来分配和释放动态内存,例如使用 malloc、calloc、realloc 和 free 函数。
1.2 内存和地址
“内存”通常指的是存储器,它用来存储程序和数据。
“地址”是指向内存中特定位置的指针。
【理解】内存单元的编号 = 地址 = 指针
在计算机中我们把内存单元的编号称为地址。C语⾔中给地址起了一个新的名字:指针
1.3 指针变量和地址
1.3.1 指针变量
指针变量是一种特殊类型的变量,它存储的是内存地址,而不是数据本身。指针变量的值就是它所指向的数据在内存中的地址。指针变量的类型决定了它能够指向的数据类型。例如,int* 是一个指针类型,它可以指向一个整数类型的变量。1.3.2 地址
地址是指向内存中特定位置的指针。每个变量在内存中都有一个唯一的地址,这个地址是该变量在内存中的位置标识。通过指针,程序可以访问和修改内存中的数据,因为指针提供了数据在内存中的位置信息。1.3.3 指针变量和地址的关系
指针变量存储的值是内存地址。指针变量的类型决定了它能够指向的数据类型。通过指针变量,程序可以间接地访问它所指向的内存地址中的数据。1.4 解引用操作符
解引用操作符通常表示为 *
【用法】
1.5 指针变量的大小
在大多数现代计算机系统中,指针的大小取决于指针所指向的数据类型。指针本身通常占用的空间与操作系统和编译器有关,但通常情况下,指针的大小是固定的,并且与指针所指向的数据类型无关。
在64位操作系统中,指针的大小通常是8字节(64位),而在32位操作系统中,指针的大小通常是4字节(32位)。这意味着,无论指针指向什么类型的数据,指针本身的大小都是固定的。
例如,假设有一个整数指针 int* ptr;
,在64位系统中,ptr
的大小是8字节,无论它指向什么整数类型的变量。同样,在32位系统中,ptr
的大小是4字节。
需要注意的是,指针的大小与指针所指向的数据类型的大小是不同的。例如,一个整数指针 int* ptr;
指向一个整数 int num = 10;
,在这个例子中,num
的大小取决于它存储的整数的大小,例如在大多数系统上,整数通常是4字节。而指针 ptr
的大小,如前所述,取决于操作系统和编译器。
【结论】
• 32位平台下地址是32个bit位,指针变量大小是4个字节。
• 64位平台下地址是64个bit位,指针变量大小是8个字节。
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
1.6 指针变量类型的意义
1.6.1 指针的解引用
【解引用基本语法】
数据类型 *指针变量名;
【解引用的步骤】
声明一个指针变量,并指定它将指向的数据类型。初始化指针变量,使其指向一个有效的内存地址。使用解引用操作符 *来访问指针所指向的内存地址中的值。【解引用的示例】
//假设我们有一个整数变量 int num = 10;,我们想要通过一个指针来访问这个变量的值。int num = 10; // 声明并初始化整数变量int *ptr; // 声明一个整数指针变量ptr = # // 初始化指针变量,使其指向 num 的内存地址// 解引用指针变量,访问 num 的值int value = *ptr; // value 将被设置为 num 的值//在这个例子中,*ptr 表示解引用操作,它访问了指针 ptr 所指向的内存地址中的值,并将这个值赋给了变量 value。
1.6.2 指针的简单加减
指针加整数:当指针加上一个整数时,它指向的内存地址会增加或减少相应数量的内存单元。例如,如果 ptr 是一个指向整数的指针,那么 ptr += 1 会将 ptr 移动到下一个整数的地址。指针减整数:当指针减去一个整数时,它指向的内存地址会减少或增加相应数量的内存单元。例如,如果 ptr 是一个指向整数的指针,那么 ptr -= 1 会将 ptr 移动到前一个整数的地址。指针加减指针:指针还可以进行加减运算,这通常用于数组操作。例如,如果 ptr1 和 ptr2 都是指向数组中元素的指针,那么 ptr1 += 2 会将 ptr1 移动到数组中第三个元素的地址,而 ptr1 -= ptr2 会将 ptr1 移动到 ptr2 所指向的元素的下一个元素的地址。指针加减指针类型:指针还可以进行加减运算,其中一个是整数类型,另一个是指针类型。这通常用于数组操作和动态内存分配。例如,如果 ptr1是一个指向数组的指针,那么 ptr1 += 2 会将 ptr1 移动到数组中第三个元素的地址。1.6.3 void* 指针
在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的±整数和解引⽤的运算。
【示例】
#include <stdio.h>int main(){int a = 10;int* pa = &a;char* pc = &a;return 0;}
【使用void*类型的指针接收地址】
#include <stdio.h>int main(){int a = 10;void* pa = &a;void* pc = &a;*pa = 10;*pc = 0;return 0;}
1.7 const 修饰指针
1.7.1 const修饰变量
const
关键字可以用来修饰变量,以表明该变量是不可变的,即其值在程序运行过程中不能被改变。使用 const
修饰变量可以提高代码的清晰度和可维护性,因为它明确地告诉程序员该变量是一个常量。
以下是关于 const
修饰变量的几个要点:
const
关键字定义常量。例如:const int MAX_VALUE = 100; // 定义一个整数常量
局部变量:在函数内部,可以使用 const
来修饰局部变量,以表明该变量在函数执行过程中不会被修改。例如:void myFunction() { const int x = 10; // 定义一个局部常量 // x = 20; // 错误,常量不能被重新赋值}
全局变量:在函数外部,可以使用 const
来修饰全局变量,以表明该变量在程序运行过程中不会被修改。例如:const int GLOBAL_CONSTANT = 100; // 定义一个全局常量
指针和引用:在C和C++中,const
也可以用来修饰指针和引用,以表明指针或引用所指向的数据是不可变的。例如:const int *ptr; // 指针指向的数据是常量const int &ref = num; // 引用所指向的数据是常量
使用 const
修饰变量时,需要确保在声明时已经初始化,因为常量在声明后不能被修改。此外,const
修饰的变量在内存中通常会有特殊处理,以保证其值不会被意外修改。
1.7.2 const修饰指针变量
const
关键字可以用来修饰指针变量,这有助于区分指针指向的数据是否可以被修改。const
修饰指针变量有几种不同的用法:
const
修饰指针变量时,它表示指针本身是可变的,即指针可以被重新赋值以指向其他内存地址,但是指针所指向的数据不能被修改。例如:const int *ptr; // 指针指向的数据不可修改
指向常量的指针:当 const
放在指针变量后面时,它表示指针指向的数据是可变的,但是指针本身是不可变的,即指针不能被重新赋值以指向其他内存地址。例如:int *const ptr; // 指针本身不可变
指向常量的常量指针:当 const
关键字同时放在指针变量前面和后面时,表示指针本身和它所指向的数据都是不可变的。这意味着指针不能被重新赋值,也不能修改它所指向的数据。例如:const int *const ptr; // 指针本身和指向的数据都是不可变的
使用 const
修饰指针变量时,需要根据实际需要选择合适的修饰方式,以确保指针的行为符合预期,并且能够有效地保护数据不被意外修改。
1.8 指针运算
【分类】
• 指针± 整数
• 指针-指针
• 指针的关系运算
1.8.1 指针 + 整数
#include <stdio.h>int main(){int arr[10] ={ 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++){printf("%d ",*(p+i));//p+i 这⾥就是指针+整数}return 0;}
1.8.2 指针 - 指针
#include <stdio.h>int my_strlen(char *s){ char *p = s; while(*p != '\0' ) p++; return p-s;}int main(){ printf("%d\n", my_strlen("abc")); return 0;}
1.8.3 指针的关系运算
#include <stdio.h>int main(){int arr[10] ={ 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);while(p<arr+sz) //指针的⼤⼩⽐较{printf("%d ",*p);p++;}return 0;}
1.9 野指针
【概念】野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1.9.1 野指针成因
【指针未初始化】
#include <stdio.h>int main(){ int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0;}
【指针越界访问】
#include <stdio.h>int main(){ int arr[10] = {0}; int *p = &arr[0]; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0;}
【指针指向的空间释放】
#include <stdio.h>int* test(){int n = 100;return &n;}int main(){int* p = test();printf("%d\n",*p);return 0;}
1.9.2 如何规避野指针
1.9.2.1 指针初始化
#include <stdio.h>int main(){ int num = 10; int*p1 = # int*p2 = NULL; return 0;}
1.9.2.2 小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是
越界访问。
1.9.2.3 指针变量不再使⽤时,及时置NULL,指针使用之前检查有效性
#include<stdio.h>int main(){int arr[10] ={ 1,2,3,4,5,67,7,8,9,10 };int* p = &arr[0];for(i=0; i<10; i++){*(p++) = i;}//此时p已经越界了,可以把p置为NULLp = NULL;//下次使⽤的时候,判断p不为NULL的时候再使⽤//...p = &arr[0];//重新让p获得地址if(p != NULL) //判断{//...}return 0;}
1.10 assert断言
1.10.1 assert断言理解
在C语言中,assert
是一个宏,用于在程序运行时进行断言检查。断言是一种条件表达式,它在程序执行时被评估。如果断言表达式的值为 true
,程序继续执行;如果为 false
,程序会停止执行并抛出一个错误。
以下是关于 assert
断言的一些要点:
assert
是一个宏,通常在C语言标准库中定义。在C中,它通常位于 <assert.h>
头文件中。使用:assert
宏的基本语法如下:assert(表达式);
这里,表达式
是一个条件表达式。如果 表达式
的值为 true
,程序继续执行;如果为 false
,程序将调用预定义的错误处理函数,通常会输出错误信息并终止程序。默认行为:如果 表达式
的值为 false
,assert
宏会调用预定义的错误处理函数,通常包括打印错误信息并终止程序。在C中,默认行为是调用 abort()
函数;在C++中,默认行为是调用 std::abort()
函数。自定义行为:在某些情况下,你可能希望自定义 assert
的行为。这可以通过定义一个 assert
函数来实现,该函数在 assert
宏被调用时被调用。例如:void my_assert(const char *expr, const char *file, int line) { fprintf(stderr, "Assertion failed: %s, file %s, line %d\n", expr, file, line); abort(); // 终止程序}
调试与发布:在调试模式下,assert
通常会被编译器包含在程序中,并且在断言失败时会抛出错误。在发布模式下,可以通过预处理器指令(如 NDEBUG
)来禁用 assert
,以提高程序的性能和减少不必要的错误消息。总之,
assert
是一个非常有用的工具,用于在程序运行时进行断言检查。它可以帮助开发者发现潜在的错误,并确保程序在预期条件下正常工作。它通常在调试模式下使用,以确保程序的稳定性和正确性。在发布模式下,可以通过预处理器指令来禁用 assert
,以提高程序的性能和减少不必要的错误消息。 1.10.2 assert断言举例
在C语言中,assert
宏通常用于调试目的,以确保程序在特定条件下运行。以下是一些使用 assert
宏的例子:
#include <stdio.h>#include <assert.h>int add(int a, int b) { assert(a >= 0 && b >= 0); // 确保参数非负 return a + b;}int main() { int result = add(-1, 2); printf("Result: %d\n", result); return 0;}
检查数组边界:#include <stdio.h>#include <assert.h>void printArray(int arr[], int size) { assert(size > 0); // 确保数组不为空 for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n");}int main() { int arr[] = {1, 2, 3, 4, 5}; printArray(arr, 0); // 断言失败,因为数组为空 return 0;}
检查动态内存分配:#include <stdio.h>#include <stdlib.h>#include <assert.h>int* createArray(int size) { int *ptr = malloc(size * sizeof(int)); assert(ptr != NULL); // 确保动态内存分配成功 return ptr;}int main() { int *arr = createArray(0); // 断言失败,因为尝试分配零大小 return 0;}
在这些例子中,assert
宏用于检查某些条件是否满足,这些条件通常是程序运行的基础假设。如果这些条件不满足,assert
宏将抛出一个错误,并终止程序。这有助于在开发过程中及时发现潜在的问题,并确保程序在实际运行时不会遇到未定义的行为。在发布版本中,可以通过预处理器指令 NDEBUG
来禁用 assert
宏,以提高程序的性能。
1.11 指针的使用和传址调用
1.11.1 strlen的模拟实现
strlen
函数是C语言标准库中的一个函数,用于计算字符串的长度,不包括字符串末尾的 \0
字符。在某些情况下,如果标准库不可用或者需要更深入理解字符串处理,可以模拟实现 strlen
函数。
以下是 strlen
函数的模拟实现:
#include <stdio.h>int my_strlen(const char *s){ int length = 0; while (*s++) { length++; } return length;}int main(){ char str[] = "Hello, World!"; printf("The length of the string is: %d\n", my_strlen(str)); return 0;}
在这个实现中,我们定义了一个名为 my_strlen
的函数,它接受一个指向字符串的指针 s
。函数内部使用一个循环来遍历字符串中的每个字符,并使用 length
变量来计数。循环继续直到遇到字符串末尾的 \0
字符,此时循环结束,length
变量中存储的就是字符串的长度。
需要注意的是,my_strlen
函数是按值传递字符串的,这意味着它不会修改原始字符串。如果需要修改原始字符串,可以使用指针来传递字符串。此外,my_strlen
函数返回的是字符串的长度,不包括 \0
字符。
1.11.2 传值调用和传址调用
在C语言中,函数参数的传递方式主要有两种:传值调用(Call by Value)和传址调用(Call by Reference)。
1.11.2.1 传值调用(Call by Value)
在传值调用中,函数接收的是参数的值,而不是参数的地址。这意味着在函数内部对参数的任何修改都不会影响原始变量。传值调用适用于基本数据类型(如整数、浮点数、字符等)和数组。示例代码:void swap(int a, int b) { int temp = a; a = b; b = temp;}int main() { int x = 10, y = 20; swap(x, y); printf("x = %d, y = %d\n", x, y); // 输出:x = 10, y = 20 return 0;}
1.11.2.2 传址调用(Call by Reference)
在传址调用中,函数接收的是参数的地址,而不是参数的值。这意味着在函数内部对参数的任何修改都会影响原始变量。传址调用通常通过指针来实现,适用于指针和数组。示例代码:void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp;}int main(){ int x = 10, y = 20; swap(&x, &y); printf("x = %d, y = %d\n", x, y); // 输出:x = 20, y = 10 return 0;}
在C语言中,数组名实际上是一个指向数组首元素的指针,因此数组可以通过指针进行传址调用。然而,在函数内部,数组名不能直接作为指针使用,因为数组名是一个常量指针,不能被重新赋值。要解决这个问题,可以使用指针作为参数,或者使用数组指针(指向数组的指针)作为参数。
2. 深入理解指针(二)
2.1 数组名
在C语言中,数组名是一个指针,它指向数组的第一个元素。这意味着数组名可以被看作是一个指向数组第一个元素的指针,这个指针是不可变的,即它不能被重新赋值。
以下是关于数组名的几个要点:
int arr[10];
,那么 arr
是一个指针,它指向 arr[0]
。数组名作为函数参数:当将数组作为函数参数传递时,通常传递的是数组名,而不是数组的副本。这意味着函数将接收数组的第一个元素的地址。例如:void printArray(int arr[]) { // arr 是一个指向 int 类型的指针,它指向 arr[0]}
数组名作为函数返回值:函数可以返回数组的第一个元素的地址,即数组名。例如:int* createArray(int size) { int *newArr = malloc(size * sizeof(int)); return newArr; // 返回新分配数组的第一个元素的地址}
数组名作为指针解引用:数组名可以被解引用,即通过数组名获取它所指向的值。例如:int value = *arr; // 获取 arr 指向的值,即 arr[0]
数组名作为指针的比较:数组名可以与指针进行比较,以确定它们是否指向同一个数组。例如:if (arr == anotherArr) { // arr 和 anotherArr 指向同一个数组}
需要注意的是,虽然数组名是一个指针,但它不能被重新赋值,这意味着你不能通过数组名来改变它所指向的数组。此外,数组名通常用于传递数组的第一个元素的地址,而不是整个数组的副本。
2.2 使用指针访问数组
在C语言中,使用指针访问数组是一种常见的做法,因为数组名本身就是一个指向数组第一个元素的指针。通过指针,可以有效地访问数组的元素,并进行各种操作。
以下是使用指针访问数组的几个例子:
int arr[10];int *ptr = arr;int value = *ptr; // 访问 arr[0]value = *(ptr + 1); // 访问 arr[1]
使用指针遍历数组:int arr[10];int *ptr = arr;for (int i = 0; i < 10; i++) { value = *(ptr + i); // 访问 arr[i] // 对 value 进行操作}
使用指针修改数组元素:int arr[10];int *ptr = arr;*ptr = 10; // 修改 arr[0]*(ptr + 1) = 20; // 修改 arr[1]
使用指针进行数组复制:int src[10], dest[10];int *src_ptr = src, *dest_ptr = dest;for (int i = 0; i < 10; i++) { *(dest_ptr + i) = *(src_ptr + i); // 复制 src 数组到 dest 数组}
使用指针进行数组排序:int arr[10];int *ptr = arr;// 实现排序算法,例如冒泡排序
在使用指针访问数组时,需要注意以下几点:
指针的加减运算:指针的加减运算用于移动指针在内存中的位置。例如,ptr += 1
将指针移动到下一个元素的地址,ptr -= 1
将指针移动到前一个元素的地址。指针的比较:指针可以进行比较运算,以确定它们是否指向同一个数组或指向数组中的相同元素。例如,if (ptr == arr)
用于比较指针和数组名是否指向同一个数组。数组越界:在使用指针访问数组时,需要注意不要访问数组范围之外的内存,否则可能导致程序崩溃或数据损坏。例如,ptr = arr + 10;
将指针移动到数组范围之外,这是不允许的。总之,使用指针访问数组可以提高代码的效率和灵活性,但同时也需要注意安全和避免数组越界。
2.3 一维数组传参的本质
在C语言中,一维数组作为函数参数传递时,其本质是传递数组首元素的地址。这是因为数组名在C语言中本质上是一个指向数组首元素的指针。
具体来说,当一个函数接受一个数组作为参数时,它实际上接收的是数组首元素的地址。这意味着函数内部的任何对数组元素的修改都会影响原始数组,因为它们共享同一个内存位置。
以下是关于一维数组作为函数参数传递的本质的几个要点:
int arr[10];
,那么 arr
是一个指针,它指向 arr[0]
。传递数组名:当将数组作为函数参数传递时,传递的是数组名,即首元素的地址。例如:void printArray(int arr[]) { // arr 是一个指向 int 类型的指针,它指向 arr[0]}
修改数组元素:在函数内部,可以通过指针来修改数组元素。例如:void modifyArray(int arr[]) { *arr = 10; // 修改 arr[0]}
传递数组和指针的区别:虽然数组名可以作为指针使用,但在传递数组名时,实际上传递的是数组首元素的地址。与指针不同,数组名是一个不可变的指针,它不能被重新赋值。函数参数传递:当函数的参数是数组名时,函数内部可以通过指针来访问数组中的元素。例如:void printArray(int arr[]) { for (int i = 0; i < 10; i++) { printf("%d ", *(arr + i)); // 打印 arr[i] } printf("\n");}
总结来说,一维数组作为函数参数传递的本质是传递数组首元素的地址。这使得函数可以间接地访问和修改数组中的元素,同时保持了原始数组的状态。
2.4 冒泡排序
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
以下是冒泡排序的步骤:
比较相邻的元素。如果第一个比第二个大,就交换它们两个。对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。针对所有的元素重复以上的步骤,除了最后已经排序好的元素。持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。【示例】
#include <stdio.h>void bubbleSort(int arr[], int n) { int i, j, temp; for (i = 0; i < n-1; i++) // Last i elements are already in place for (j = 0; j < n-i-1; j++) // Traverse the array from 0 to n-i-1 // Swap if the element found is greater // than the next element if (arr[j] > arr[j+1]) { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; }}// Function to print an arrayvoid printArray(int arr[], int size) { int i; for (i=0; i < size; i++) printf("%d ", arr[i]); printf("\n");}// Driver code to test aboveint main(){ int arr[] = {64, 34, 25, 12, 22, 11, 90}; int n = sizeof(arr)/sizeof(arr[0]); bubbleSort(arr, n); printf("Sorted array: \n"); printArray(arr, n); return 0;}
在这个示例中,bubbleSort 函数接受一个整数数组 arr 和数组的长度 n。它使用两层嵌套循环来实现冒泡排序:外层循环控制需要进行的遍历次数,内层循环负责进行相邻元素的比较和交换。
printArray 函数用于打印排序后的数组。
main 函数是程序的入口点,它定义了一个待排序的数组,调用 bubbleSort 函数进行排序,然后调用 printArray 函数打印排序后的数组。
2.5 二级指针
在C语言中,指针可以用来指向其他指针,这样的指针称为二级指针(Double Pointer)。二级指针通常用于传递指针的指针,或者用于数组的动态内存分配。
以下是关于二级指针的一些要点:
int *ptr; // 指针变量int **pptr; // 二级指针变量
指向指针:二级指针指向一个指针,这意味着它存储的是另一个指针的地址。例如:int **pptr = &ptr; // pptr 是一个二级指针,它指向 ptr
解引用:二级指针可以通过两次解引用来访问它所指向的指针所指向的数据。例如:int **pptr = &ptr;int *ptr2 = *pptr; // ptr2 是一个指针,它指向 pptr 所指向的指针所指向的数据int value = **pptr; // value 获取 ptr2 所指向的值
动态内存分配:在C语言中,二级指针常用于动态内存分配。例如,使用 malloc
或 calloc
函数为数组分配内存时,通常会使用二级指针来接收分配的内存地址。例如:int **pptr = (int **)malloc(sizeof(int *) * size); // 分配 size 个 int 指针的空间
传递指针:在函数参数传递时,可以使用二级指针来传递指针。例如:void modifyPointer(int **ptr) { *ptr = 10; // 修改指针所指向的值}int main() { int *ptr = malloc(sizeof(int)); *ptr = 5; modifyPointer(&ptr); printf("Value: %d\n", *ptr); // 输出:Value: 10 return 0;}
在使用二级指针时,需要特别小心,以确保不会访问不存在的内存区域。此外,二级指针通常用于指针的指针和动态内存分配,因此在处理指针时需要谨慎。
2.6 指针数组
在C语言中,指针数组(Pointer Array)是一个数组,其元素都是指针类型。这意味着指针数组中的每个元素都存储了另一个变量的地址。
以下是关于指针数组的几个要点:
int *ptr_array[10]; // 定义一个指针数组,可以存储 10 个 int 类型的指针
初始化:指针数组可以通过初始化来为每个元素分配内存地址。例如:int *ptr_array[10] = {&a, &b, &c, &d, &e, &f, &g, &h, &i, &j}; // 初始化指针数组,每个元素指向一个整数
访问元素:可以通过索引来访问指针数组中的元素。例如:int *ptr = ptr_array[0]; // 获取第一个元素,即指向整数 a 的指针
使用指针数组:指针数组通常用于存储指向多个不同变量的指针,这有助于提高代码的灵活性和可扩展性。例如,在处理多个字符串时,可以使用指针数组来存储每个字符串的地址。指针数组与函数:指针数组可以作为函数参数传递,以便函数可以操作多个数据。例如:void processStrings(char *strings[], int count) { // 函数可以遍历指针数组并处理每个字符串}int main() { char *strings[10] = {"string1", "string2", "string3", "string4", "string5"}; int count = 5; processStrings(strings, count); return 0;}
在使用指针数组时,需要注意以下几点:
指针数组的每个元素必须指向有效的内存地址,否则可能导致程序崩溃或数据损坏。指针数组的大小是在编译时确定的,因此它的大小不能在运行时改变。如果需要动态分配指针数组的大小,可以使用动态内存分配。总之,指针数组是一个数组,其元素都是指针类型,它常用于存储指向多个不同变量的指针。
2.7 指针数组模拟二维数组
在C语言中,指针数组可以用来模拟二维数组。这是因为指针数组中的每个元素都是指向相同数据类型的指针,这些指针指向的数据可以看作是二维数组的行。
以下是使用指针数组模拟二维数组的步骤:
malloc
或 calloc
)为指针数组中的每个元素分配内存,每个元素指向一个整数数组(即二维数组的行)。初始化指针数组:使用循环为指针数组中的每个元素分配内存,并为每个元素指向的整数数组分配内存。使用指针数组:通过指针数组中的元素来访问二维数组的行,并通过行中的指针来访问每个元素。 以下是使用指针数组模拟二维数组的C语言代码示例:
#include <stdio.h>#include <stdlib.h>int main(){ int rows = 3; // 二维数组的行数 int cols = 4; // 二维数组的列数 int **array = (int **)malloc(rows * sizeof(int *)); // 为指针数组中的每个元素分配内存 for (int i = 0; i < rows; i++) { array[i] = (int *)malloc(cols * sizeof(int)); } // 初始化二维数组 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { array[i][j] = i * cols + j; } } // 打印二维数组 printf("Initialized 2D array:\n"); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { printf("%d ", array[i][j]); } printf("\n"); } // 释放内存 for (int i = 0; i < rows; i++) { free(array[i]); } free(array); return 0;}
在这个示例中,我们首先定义了一个指针数组 array
,它的大小与二维数组的行数相同。然后,我们使用 malloc
为指针数组中的每个元素分配内存,每个元素指向一个整数数组(即二维数组的行)。接下来,我们使用循环为每个元素指向的整数数组分配内存,并初始化二维数组。最后,我们使用指针数组来访问二维数组的行,并通过行中的指针来访问每个元素。
【注意】在使用指针数组模拟二维数组时,需要特别小心内存管理,确保在不再需要时释放分配的内存。
3. 深入理解指针(三)
3.1 指针变量
3.1.1 字符指针变量
在C语言中,字符指针变量(Character Pointer Variable)是一个指针变量,它存储字符串的地址。字符指针变量通常用于处理字符串和动态内存分配。
以下是关于字符指针变量的几个要点:
char*
。例如:char *str; // 定义一个字符指针变量
初始化:字符指针变量可以通过初始化来赋予一个字符串的地址。例如:char *str = "Hello, World!"; // 初始化 str 为 "Hello, World!" 的地址
错误使用:通过字符指针变量来访问和修改字符串。例如:char *str = "Hello, World!";str[0] = 'H'; // 修改 str 指向的字符串的第一个字符str[5] = ' '; // 修改 str 指向的字符串的第六个字符
【注意】这段代码实际上修改了原始的字符串常量 “Hello, World!”,这是不推荐的做法,因为字符串常量在程序编译时就已经确定,无法被修改。在实际编程中,我们应该使用动态分配的内存来存储字符串,并在需要修改时使用动态字符串库函数(如 strcpy、strcat 等)来进行操作。
4. 字符串长度:字符指针变量可以用来计算字符串的长度。例如:
char *str = "Hello, World!";int length = strlen(str); // 获取 str 指向的字符串的长度
字符串处理函数:字符指针变量可以用来调用字符串处理函数,例如 strcpy
、strcmp
、strcat
等。例如:char *str1 = "Hello";char *str2 = "World";strcpy(str1, str2); // 将 str2 指向的字符串复制到 str1 指向的字符串
在使用字符指针变量时,需要注意以下几点:
字符指针变量存储的是字符串的地址,因此它指向的数据是可变的。字符指针变量可以用来修改字符串中的字符,也可以用来传递字符串给函数。在使用字符串处理函数时,需要确保字符指针变量指向有效的字符串,否则可能导致程序崩溃或数据损坏。总之,字符指针变量是一个指针,它存储字符串的地址,并可用于处理字符串和动态内存分配。
3.1.2 数组指针变量
在C语言中,数组指针变量(Array Pointer Variable)是一个指针变量,它存储数组的地址。数组指针变量通常用于处理数组和动态内存分配。
以下是关于数组指针变量的几个要点:
数组类型*
。例如,如果有一个整数数组 int arr[10];
,那么指向这个数组的指针变量可能是 int *ptr;
。初始化:数组指针变量可以通过初始化来赋予一个数组的地址。例如:int arr[10];int *ptr = arr; // 初始化 ptr 为 arr 的地址
使用:可以通过数组指针变量来访问和修改数组中的元素。例如:int arr[10];int *ptr = arr;ptr[0] = 10; // 修改 arr[0] 的值为 10
数组长度:数组指针变量可以用来计算数组的长度。例如:int arr[10];int *ptr = arr;int length = sizeof(arr) / sizeof(arr[0]); // 获取 arr 的长度
数组处理函数:数组指针变量可以用来调用数组处理函数,例如 memcpy
、memcmp
、memmove
等。例如:int arr1[10], arr2[10];memcpy(arr1, arr2, sizeof(arr2)); // 将 arr2 的内容复制到 arr1
在使用数组指针变量时,需要注意以下几点:
数组指针变量存储的是数组的地址,因此它指向的数据是可变的。数组指针变量可以用来修改数组中的元素,也可以用来传递数组给函数。在使用数组处理函数时,需要确保数组指针变量指向有效的数组,否则可能导致程序崩溃或数据损坏。总之,数组指针变量是一个指针,它存储数组的地址,并可用于处理数组和动态内存分配。
3.1.3 函数指针变量
在C语言中,函数指针变量(Function Pointer Variable)是一个指针变量,它存储函数的地址。函数指针变量通常用于实现函数的动态选择和回调函数。
以下是关于函数指针变量的几个要点:
函数类型*
。例如,如果有一个返回整数且接收两个整数参数的函数 int add(int a, int b)
,那么指向这个函数的指针变量可能是 int (*ptr)(int, int);
。初始化:函数指针变量可以通过初始化来赋予一个函数的地址。例如:int add(int a, int b) { return a + b;}int (*ptr)(int, int) = add; // 初始化 ptr 为 add 函数的地址
使用:可以通过函数指针变量来调用函数。例如:int add(int a, int b) { return a + b;}int (*ptr)(int, int) = add;int result = ptr(3, 4); // 调用 add 函数,结果为 7
函数指针作为参数:函数指针可以作为函数的参数传递。例如,可以创建一个函数,它接受一个函数指针作为参数,并调用该函数。函数指针数组:可以创建一个函数指针数组,其中每个元素都是一个函数指针。例如:int add(int a, int b) { return a + b;}int subtract(int a, int b) { return a - b;}int (*operations[])(int, int) = {add, subtract}; // 创建一个函数指针数组int result = operations[0](3, 4); // 调用 add 函数,结果为 7
在使用函数指针变量时,需要注意以下几点:
函数指针变量存储的是函数的地址,因此它指向的数据是可变的。函数指针变量可以用来调用函数,也可以用来传递函数给其他函数。在使用函数指针时,需要确保它指向一个有效的函数,否则可能导致程序崩溃或数据损坏。总之,函数指针变量是一个指针,它存储函数的地址,并可用于实现函数的动态选择和回调函数。
3.2 二维数组传参的本质
在C语言中,二维数组作为函数参数传递时,其本质是传递数组首元素的地址。这是因为二维数组在内存中是以连续的内存块形式存储的,数组名实际上指向了该连续内存块的起始地址。
具体来说,当一个函数接受一个二维数组作为参数时,它实际上接收的是数组首元素的地址。这意味着函数内部的任何对数组元素的修改都会影响原始数组,因为它们共享同一个内存位置。
以下是关于二维数组作为函数参数传递的本质的几个要点:
int arr[3][4];
,那么 arr
是一个指针,它指向 arr[0][0]
。传递数组名:当将二维数组作为函数参数传递时,传递的是数组名,即首元素的地址。例如:void printArray(int arr[][4]) { // arr 是一个指向 int 类型的指针,它指向 arr[0][0]}
修改数组元素:在函数内部,可以通过指针来修改数组元素。例如:void modifyArray(int arr[][4]) { arr[0][0] = 10; // 修改 arr[0][0]}
传递数组和指针的区别:虽然数组名可以作为指针使用,但在传递数组名时,实际上传递的是数组首元素的地址。与指针不同,数组名是一个不可变的指针,它不能被重新赋值。函数参数传递:当函数的参数是数组名时,函数内部可以通过指针来访问数组中的元素。例如:void printArray(int arr[][4]) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d ", *(*(arr + i) + j)); // 打印 arr[i][j] } printf("\n"); }}
总结来说,二维数组作为函数参数传递的本质是传递数组首元素的地址。这使得函数可以间接地访问和修改数组中的元素,同时保持了原始数组的状态。
3.3 函数指针数组
在C语言中,函数指针数组(Function Pointer Array)是一个数组,其元素都是函数指针。这种数组允许你存储多个函数的地址,并且可以通过索引来选择要调用的函数。
以下是关于函数指针数组的几个要点:
int (*func_array[])(int, int) = {add, subtract, multiply, divide};
初始化:函数指针数组可以通过初始化来赋予多个函数的地址。例如:int add(int a, int b) { return a + b; }int subtract(int a, int b) { return a - b; }int multiply(int a, int b) { return a * b; }int divide(int a, int b) { return a / b; }int (*func_array[])(int, int) = {add, subtract, multiply, divide};
使用:可以通过索引来调用函数指针数组中的函数。例如:int (*func_array[])(int, int) = {add, subtract, multiply, divide};int result = func_array[0](3, 4); // 调用 add 函数,结果为 7
动态选择函数:函数指针数组允许你在运行时动态选择要调用的函数,这在实现回调函数或编写多态性代码时非常有用。函数指针作为数组元素:函数指针数组中的每个元素都是一个函数指针,它存储了一个函数的地址。在使用函数指针数组时,需要注意以下几点: 函数指针数组中的每个元素必须指向一个有效的函数,否则可能导致程序崩溃或数据损坏。函数指针数组的大小是在编译时确定的,因此它的大小不能在运行时改变。如果需要动态分配函数指针数组的大小,可以使用动态内存分配。
总之,函数指针数组是一个数组,其元素都是函数指针,它允许你存储多个函数的地址,并通过索引来选择要调用的函数。
3.4 转移表
在C语言中,转移表(Transfer Table)并不是一个标准的数据结构,但它可以根据需要通过不同的数据结构来实现。以下是三个可能的例子,展示了如何在C语言中使用不同的数据结构来模拟转移表的功能:
3.4.1 基于数组的转移表
#include <stdio.h>#include <stdlib.h>// 假设我们有一个简单的程序控制流转移表typedef struct { int from; int to;} TransferEntry;// 初始化一个转移表TransferEntry transferTable[10] = { {0, 1}, {1, 2}, {2, 3}, // ... 其他转移条目 {9, 0} // 循环结束};// 函数,根据当前状态找到下一个状态int getNextState(int currentState) { for (int i = 0; transferTable[i].from != currentState; i++) { if (transferTable[i].from == currentState) { return transferTable[i].to; } } return -1; // 没有找到匹配的转移}int main() { int currentState = 0; while (1) { currentState = getNextState(currentState); if (currentState == -1) { break; } printf("Transfer to state: %d\n", currentState); } return 0;}
3.4.2 基于链表的转移表
#include <stdio.h>#include <stdlib.h>// 假设我们有一个简单的链表实现的转移表typedef struct TransferNode { int from; int to; struct TransferNode *next;} TransferNode;// 初始化一个转移表TransferNode *transferTable = NULL;// 函数,根据当前状态找到下一个状态int getNextState(int currentState) { TransferNode *current = transferTable; while (current != NULL) { if (current->from == currentState) { return current->to; } current = current->next; } return -1; // 没有找到匹配的转移}// 函数,添加一个新的转移条目到转移表void addTransfer(int from, int to) { TransferNode *newNode = (TransferNode *)malloc(sizeof(TransferNode)); newNode->from = from; newNode->to = to; newNode->next = NULL; if (transferTable == NULL) { transferTable = newNode; } else { TransferNode *current = transferTable; while (current->next != NULL) { current = current->next; } current->next = newNode; }}int main() { addTransfer(0, 1); addTransfer(1, 2); addTransfer(2, 3); // ... 添加更多转移条目 int currentState = 0; while (1) { currentState = getNextState(currentState); if (currentState == -1) { break; } printf("Transfer to state: %d\n", currentState); } return 0;}
3.4.3 计算器的⼀般实现
#include <stdio.h>int add(int a,int b){return a + b;}int sub(int a,int b){return a - b;}int mul(int a,int b){return a * b;}int div(int a,int b){return a / b;}int main(){int x,y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d",&input);switch(input){case 1:printf("输⼊操作数:");scanf("%d %d",&x,&y);ret = add(x,y);printf("ret = %d\n",ret);break;case 2:printf("输⼊操作数:");scanf("%d %d",&x,&y);ret = sub(x,y);printf("ret = %d\n",ret);break;case 3:printf("输⼊操作数:");scanf("%d %d",&x,&y);ret = mul(x,y);printf("ret = %d\n",ret);break;case 4:printf("输⼊操作数:");scanf("%d %d",&x,&y);ret = div(x,y);printf("ret = %d\n",ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while(input);return 0;}
4. 深入理解指针(四)
4.1 回调函数
在C语言中,回调函数(Callback Function)是一种可以在程序执行过程中被其他函数调用的函数。回调函数通常作为参数传递给其他函数,并在满足特定条件时由该函数调用。
以下是关于C语言中回调函数的一些要点:
以下是C语言中回调函数的一个简单示例:
#include <stdio.h>// 定义一个回调函数void printNumber(int number) { printf("Number: %d\n", number);}// 另一个函数,它接受一个回调函数作为参数void processNumbers(int numbers[], int size, void (*callback)(int)) { for (int i = 0; i < size; i++) { callback(numbers[i]); }}int main() { int numbers[] = {1, 2, 3, 4, 5}; int size = sizeof(numbers) / sizeof(numbers[0]); // 传递回调函数 processNumbers(numbers, size, printNumber); return 0;}
在这个例子中,printNumber
函数是一个回调函数,它被传递给 processNumbers
函数。当 processNumbers
函数遍历数组中的每个数字时,它会调用 printNumber
函数来打印数字。
4.2 qsort函数
4.2.1 qsort函数概括
在C语言中,qsort
函数是一个标准库函数,用于对数组中的元素进行排序。qsort
函数使用快速排序算法(Quick Sort)来对数组进行排序。
以下是关于 qsort
函数的一些要点:
qsort
函数位于 <stdlib.h>
头文件中,用于对数组进行排序。使用:qsort
函数的基本语法如下:void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));
这里,base
是待排序数组的起始地址;num
是数组中的元素数量;size
是单个元素的大小(以字节为单位);compar
是比较函数,用于比较两个元素的大小。比较函数:compar
函数必须符合一定的签名,它接受两个指向待比较元素的指针,并返回一个整数。如果第一个元素小于第二个元素,返回负数;如果两个元素相等,返回零;如果第一个元素大于第二个元素,返回正数。例如:int compare(const void *a, const void *b) { int x = *(int*)a; int y = *(int*)b; return x - y;}
排序类型:qsort
函数可以对数组进行升序或降序排序,这取决于比较函数的返回值。示例:以下是一个使用 qsort
函数对整数数组进行排序的示例:#include <stdio.h>#include <stdlib.h>#include <string.h>int compare(const void *a, const void *b) { return (*(int*)a - *(int*)b);}int main() { int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; int n = sizeof(arr) / sizeof(arr[0]); // 对数组进行排序 qsort(arr, n, sizeof(arr[0]), compare); // 打印排序后的数组 for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0;}
注意事项:在使用 qsort
函数时,需要注意数组元素的类型和大小,以及比较函数的返回值。此外,qsort
函数是递归的,因此在排序大量数据时可能会消耗较多的栈空间。总之,
qsort
函数是一个非常有用的工具,用于对数组中的元素进行快速排序。它常用于需要对数据进行排序的场景,如文件排序、数据处理等。 4.2.2 使用qsort函数排序整型数据
在C语言中,qsort
函数是一个标准库函数,用于对数组中的元素进行排序。qsort
函数使用快速排序算法(Quick Sort)来对数组进行排序。
以下是使用 qsort
函数对整型数据进行排序的示例代码:
#include <stdio.h>#include <stdlib.h>#include <string.h>// 比较函数,用于比较两个整数的大小int compare(const void *a, const void *b) { return (*(int*)a - *(int*)b);}int main() { // 创建一个整型数组 int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; int n = sizeof(arr) / sizeof(arr[0]); // 数组中元素的个数 // 使用 qsort 函数对数组进行排序 qsort(arr, n, sizeof(arr[0]), compare); // 打印排序后的数组 for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0;}
在这个示例中,我们首先定义了一个整型数组 arr
,并计算出数组中元素的个数 n
。然后,我们使用 qsort
函数对数组进行排序,并传递一个比较函数 compare
作为参数。这个比较函数比较两个整数的大小,并根据比较结果返回一个整数。最后,我们打印出排序后的数组。
当运行这段代码时,它会输出排序后的整型数组:
1 1 2 3 3 4 5 5 5 6 9
在这个例子中,我们使用了升序排序。如果需要进行降序排序,可以修改比较函数中的比较逻辑。
4.2.3 使用qsort函数排序结构数据
在C语言中,qsort
函数也可以用来对包含结构体成员的数组进行排序。结构体数组的每个元素都是一个结构体,因此我们需要为每个结构体成员提供一个比较函数。
以下是使用 qsort
函数对结构体数据进行排序的示例代码:
#include <stdio.h>#include <stdlib.h>#include <string.h>// 定义一个结构体typedef struct { int id; char name[50];} Person;// 比较函数,用于比较两个 Person 结构体对象的 id 成员int compareById(const void *a, const void *b) { Person *personA = (Person *)a; Person *personB = (Person *)b; return personA->id - personB->id;}// 比较函数,用于比较两个 Person 结构体对象的 name 成员int compareByName(const void *a, const void *b) { Person *personA = (Person *)a; Person *personB = (Person *)b; return strcmp(personA->name, personB->name);}int main() { // 创建一个 Person 结构体数组 Person people[] = { {1, "Alice"}, {2, "Bob"}, {3, "Charlie"}, {4, "David"}, {5, "Eve"} }; int n = sizeof(people) / sizeof(people[0]); // 数组中元素的个数 // 使用 qsort 函数对数组进行排序 qsort(people, n, sizeof(people[0]), compareById); // 按照 id 排序 printf("Sorted by ID:\n"); for (int i = 0; i < n; i++) { printf("ID: %d, Name: %s\n", people[i].id, people[i].name); } printf("\n"); qsort(people, n, sizeof(people[0]), compareByName); // 按照 name 排序 printf("Sorted by Name:\n"); for (int i = 0; i < n; i++) { printf("ID: %d, Name: %s\n", people[i].id, people[i].name); } printf("\n"); return 0;}
在这个示例中,我们定义了一个 Person
结构体,它包含一个整数 id
和一个字符串 name
。我们为 qsort
函数提供了两个比较函数:compareById
和 compareByName
。compareById
函数用于根据 id
成员对 Person
结构体数组进行排序,而 compareByName
函数用于根据 name
成员进行排序。
当运行这段代码时,它会输出按照 id
和 name
排序后的 Person
结构体数组。
4.3 qsort函数的模拟实现
快速排序(Quick Sort)是一种高效的排序算法,其基本思想是选择一个基准值,将数组分为两部分,一部分小于基准值,一部分大于基准值,然后递归地对这两部分进行排序。qsort
函数是C语言标准库中实现快速排序的函数。
以下是一个简单的qsort
函数的模拟实现,用于对整数数组进行排序:
#include <stdio.h>#include <stdlib.h>// 比较函数,用于比较两个整数的大小int compare(const void *a, const void *b) { return (*(int*)a - *(int*)b);}// 快速排序函数void quickSort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *)) { if (num <= 1) { return; } int pivotIndex = num / 2; int pivot = *(int *)((char *)base + pivotIndex * size); int *low = (int *)((char *)base); int *high = (int *)((char *)base + (num - 1) * size); while (low <= high) { while (*low < pivot) { low = (int *)((char *)low + size); } while (*high > pivot) { high = (int *)((char *)high - size); } if (low <= high) { int temp = *low; *low = *high; *high = temp; low = (int *)((char *)low + size); high = (int *)((char *)high - size); } } quickSort((void *)((char *)base), (size_t)(low - (int *)base), (size_t)(size), compar); quickSort((void *)((char *)base + ((low - (int *)base) + 1) * size), (size_t)(num - (high - (int *)base) - 1), (size_t)(size), compar);}int main() { int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; int n = sizeof(arr) / sizeof(arr[0]); // 对数组进行排序 quickSort(arr, n, sizeof(arr[0]), compare); // 打印排序后的数组 for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0;}
在这个模拟实现中,我们首先检查数组的大小,如果小于等于1,则直接返回,因为已经是最小单位,不需要排序。然后,我们选择一个基准值(这里我们选择数组中间的元素作为基准),并将其与数组中所有元素进行比较。在每次比较后,我们将基准值的位置调整到正确的位置,然后递归地对数组的两部分进行排序。
请注意,这个实现是针对整数数组的,并且假设比较函数可以正确地比较整数。如果你需要对其他类型的数据进行排序,你需要修改比较函数和数组元素的类型。
4.4 sizeof 和 strlen
4.4.1 两者概括
在C语言中,sizeof
和 strlen
是两个用于操作数组和字符串的函数。
sizeof
是一个运算符,而不是函数,用于返回数据类型或变量的大小,以字节为单位。语法:sizeof(类型名)
或 sizeof(变量名)
。示例:sizeof(int)
返回整数类型的大小,sizeof(arr)
返回数组 arr
的大小(以字节为单位)。 strlen 函数: strlen
是C语言标准库中的一个函数,用于计算字符串的长度,不包括字符串末尾的 \0
字符。语法:strlen(字符串)
。示例:strlen("Hello, World!")
返回字符串 “Hello, World!” 的长度,不包括字符串末尾的 \0
。 以下是一些示例代码:
#include <stdio.h>#include <string.h>int main() { int arr[] = {1, 2, 3, 4, 5}; int size = sizeof(arr) / sizeof(arr[0]); printf("Array size: %d bytes\n", size); char str[] = "Hello, World!"; int length = strlen(str); printf("String length: %d characters\n", length); return 0;}
在这个示例中,我们首先计算整数数组 arr
的大小,然后计算字符串 str
的长度。sizeof
运算符用于计算数组的大小,而 strlen
函数用于计算字符串的长度。
4.4.2 两者区别
sizeof
和 strlen
在C语言中是两个不同的概念,它们用于不同的目的:
sizeof
是一个运算符,而不是函数,用于计算数据类型或变量的大小。它可以用来计算数组的大小,但需要注意,sizeof
返回的是数组所占用的字节数,而不是数组中元素的数量。它不能直接用于计算字符串的长度,因为字符串是以 \0
结尾的,sizeof
会计算 \0
的字节数。 strlen 函数: strlen
是C语言标准库中的一个函数,用于计算字符串的长度,不包括字符串末尾的 \0
字符。它专门用于计算字符串的长度,可以直接用于字符串,并且不会计算 \0
字符。它不能用于计算数组的大小,因为数组和字符串是不同的数据类型。 以下是一些示例代码,展示了它们之间的区别:
#include <stdio.h>#include <string.h>int main() { int arr[] = {1, 2, 3, 4, 5}; printf("Array size: %zu bytes\n", sizeof(arr)); // 输出数组所占用的字节数 char str[] = "Hello, World!"; printf("String length: %zu characters\n", strlen(str)); // 输出字符串的长度,不包括 '\0' printf("Size of 'int': %zu bytes\n", sizeof(int)); // 输出 int 类型的大小 printf("Size of 'char': %zu bytes\n", sizeof(char)); // 输出 char 类型的大小 return 0;}
在这个示例中,我们首先使用 sizeof
运算符计算数组 arr
的大小,然后使用 strlen
函数计算字符串 str
的长度。我们还展示了如何使用 sizeof
运算符来计算基本数据类型的大小。
5. 深入理解指针(五)
5.1 十道经典指针编程题
1.通过地址运算符&获得地址值
2.输入a,b,按从小到大的顺序输出
3.用指针法访问数组元素
4.从键盘输入10个整数,放入一堆数组a中,然后将该数组中的元素值依次输出
5.将10个数的最小值换到最前面的位置
6.求二维数组元素的最大值
7.用指针法实现字符串的复制
8.将具有10个元素的整数型数组中的元素值按逆序存放后输出
9.用一个函数求10个学生成绩的最高分,最低分和平均成绩
10.求10个数中的最大值,通过函数返回最大值元素的地址的方法来实现
5.2 参考答案
1.通过地址运算符&获得地址值
#include<stdio.h>main(){int a,*p1;p1=&a;*p1=123;printf("%d,%d\n",a,*p1);scanf("%d",p1);printf("%d,%d\n",a,*p1);}
2.输入a,b,按从小到大的顺序输出
#include<stdio.h>main(){int a,b,*p=&a,*q=&b,*t;scanf("%d,%d",p,q);if(*p<*q){t=p;p=q;q=t;}printf("a=%d,b=%d\n",a,b);printf("最大值=%d,最小值=%d\n",*p,*q);}
3.用指针法访问数组元素
#include<stdio.h>main(){int a[10],i,*p=a;for(i=0;i<10;i++)scanf("%d",p+i);for(i=0;i<10;i++)printf("%4d",*(p+i));printf("\n");}
4.从键盘输入10个整数,放入一堆数组a中,然后将该数组中的元素值依次输出
#include<stdio.h>main(){int *p,i,a[10];p=&a[0];for(i=0;i<10;i++)scanf("%d",p++);p=&a[0];for(i=0;i<10;i++)printf("%4d",*p++);printf("\n");}
5.将10个数的最小值换到最前面的位置
#include<stdio.h>main(){int t,a[10],*p,*q;for(p=a;p<=a+9;p++)scanf("%d",p);for(q=a,p=a+1;p<=a+9;p++) if(*p<*q)q=p; printf("最小值:%d\n",*q); printf("最小值的位置:%d\n",q-a); t=*a;*a=*q;*q=t; printf("交换之后的10个数是:\n"); for(p=a;p<a+10;p++) printf("%4d",*p); printf("\n");}
6.求二维数组元素的最大值
#include<stdio.h>main(){int a[3][4]={{5,1,-8,11},{26,-7,10,129},{2,18,7,16}},*p,max;for(p=&a[0][0],max=*p;p<&a[0][0]+12;p++)if(*p>max)max=*p;printf("MAX=%d\n",max);}
7.用指针法实现字符串的复制
#include<stdio.h>main(){char a[80],b[80];char *p1,*p2;gets(a);for(p1=a,p2=b;*p1!='\0';p1++,p2++)*p2=*p1;*p2='\0';printf("字符串a中的内容:%s\n",a);printf("字符串b中的内容:%s\n",b);}
8.将具有10个元素的整数型数组中的元素值按逆序存放后输出
#include<stdio.h>void swap(int *x,int *y){int t;t=*x;*x=*y;*y=t;}main(){int a[10],i;for(i=0;i<10;i++)scanf("%d",&a[i]);for(i=0;i<=4;i++)swap(&a[i],&a[10-i-1]);for(i=0;i<10;i++)printf("%4d",a[i]);printf("\n");}
9.用一个函数求10个学生成绩的最高分,最低分和平均成绩
#include<stdio.h>float fun(int *x,int n,int *p1,int *p2){int i;float s=0;*p1=*p2=x[0];for(i=0;i<n;i++){s=s+x[i];if(*p1<x[i]) *p1=x[i];else if(*p2>x[i])*p2=x[i];}return s/n;}main(){int i,a[10],max,min;float ave;for(i=0;i<10;i++)scanf("%d",&a[i]);for(i=0;i<10;i++)printf("%4d",a[i]);ave=fun(a,10,&max,&min);printf("\n 平均值=%6.2f,最大值=%d,最小值=%d\n",ave,max,min);}
10.求10个数中的最大值,通过函数返回最大值元素的地址的方法来实现
#include<stdio.h>int *fun(int *x,int n){int i,*y;y=x;for(i=1;i<n;i++)if(*(x+i)>*y)y=x+i;return y;}main(){int a[10],*p,i;for(i=0;i<10;i++)scanf("%d",&a[i]);p=fun(a,10);printf("最大值=%d\n",*p);}
结语
以上就是小编对指针的详细讲解。
如果觉得小编讲的还可以,还请一键三连。互三必回!
持续更新中~!
我一直在遇见许多不同的、可爱的、善良的人,继而又不舍的与他们分别。能脚踏实地,也仰望星空;既有随处可栖的江湖,也有追风逐梦的骁勇。善良,优秀,勇敢,祝我们所有人。