↓
上期回顾: 【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 解引用操作符
解引用操作符通常表示为 *
【用法】
- 声明一个指针变量。例:int* ptr;
- 初始化指针,使其指向一个变量。例:int num = 10; ptr = & num;
- 使用解引用操作符获取指针所指向的值。例:int value = *ptr; 此时 value 将被设置为 num 的值。
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
修饰变量的几个要点:
- 定义常量:在C和C++中,可以使用
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置为NULL
p = 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语言中,数组名是一个指针,它指向数组的第一个元素。这意味着数组名可以被看作是一个指向数组第一个元素的指针,这个指针是不可变的,即它不能被重新赋值。
以下是关于数组名的几个要点:
- 数组名是一个指针:在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语言中本质上是一个指向数组首元素的指针。
具体来说,当一个函数接受一个数组作为参数时,它实际上接收的是数组首元素的地址。这意味着函数内部的任何对数组元素的修改都会影响原始数组,因为它们共享同一个内存位置。
以下是关于一维数组作为函数参数传递的本质的几个要点:
- 数组名是首元素的地址:在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 array
void printArray(int arr[], int size) {
int i;
for (i=0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
// Driver code to test above
int 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语言中,二维数组作为函数参数传递时,其本质是传递数组首元素的地址。这是因为二维数组在内存中是以连续的内存块形式存储的,数组名实际上指向了该连续内存块的起始地址。
具体来说,当一个函数接受一个二维数组作为参数时,它实际上接收的是数组首元素的地址。这意味着函数内部的任何对数组元素的修改都会影响原始数组,因为它们共享同一个内存位置。
以下是关于二维数组作为函数参数传递的本质的几个要点:
- 数组名是首元素的地址:在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语言中回调函数的一些要点:
- 定义:回调函数是在其他函数中调用的函数。它通常作为参数传递给其他函数,并在该函数执行到特定点时被调用。
- 使用场景:回调函数常用于事件驱动编程、图形用户界面(GUI)编程、网络编程等。
- 示例:在C语言中,回调函数可以通过指针传递给其他函数。例如,如果有一个函数需要执行某个操作,并且该操作可以通过一个函数来完成,那么可以将这个操作的函数作为回调函数传递给该函数。
- 编程语言:回调函数在各种编程语言中都有应用,如C、C++、Java、Python等。
- 注意事项:在设计回调函数时,需要确保其函数签名(参数和返回类型)与调用它的函数期望的一致。此外,回调函数通常不应该修改全局状态,以避免潜在的副作用。
以下是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(变量名)
。 - 示例:
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
是一个运算符,而不是函数,用于计算数据类型或变量的大小。- 它可以用来计算数组的大小,但需要注意,
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);
}
结语
以上就是小编对指针的详细讲解。
如果觉得小编讲的还可以,还请一键三连。互三必回!
持续更新中~!
我一直在遇见许多不同的、可爱的、善良的人,继而又不舍的与他们分别。能脚踏实地,也仰望星空;既有随处可栖的江湖,也有追风逐梦的骁勇。善良,优秀,勇敢,祝我们所有人。