指针数组和数组指针的分析:
1、数组类型:数组有自己特定的类型,数组的类型由数组元素和数组大小共同决定;
数组类型的定义:
基本语法:
typedef type(Name)[arraySize];
示例:
下列定义了一个数组类型,定义了int型的大小为5的一个数组类型,可以利用这个数组类型定义实际的数组;iArray就是该数组类型的一个实例;
typedef int(AINT5)[5];
AINT5 iArray;
数组指针:
数组指针是一个指针,指向数组;也就是数组指针是指向一个数组的;
注:数组名是数组首元素的其实地址,但是并不是数组的起始地址;
数组指针与普通指针的区别:
1、普通指针:int *p = arr;
在这种定义中,p是一个int型的指针,而且p指向的是数组的第一个元素位置;
2、数组指针:int (*p)[9] = &arr;
方式通常用于传递整个数组到函数,或者当数组本身作为一个单元需要被处理时使用。
在这种定义中,p是一个指向整个数组arr的指针;
图解:
代码示例:
int arr[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
int (*p)[9] = &arr;
// 访问数组元素
for (int i = 0; i < 9; i++) {
printf("%d ", (*p)[i]);
}
这里,(*p)[i]
实际上是解引用整个数组指针,然后访问第 i
个元素;
指针数组:
指针数组是一个普通的数组;指针数组的每一个元素都是一个指针;
指针数组的 定义方法:type *arrayName[arraySize];
示例:
int *ptrArray[5]; // 可以存储5个int类型的指针
指针数组的常见初始化:
int a = 10, b = 20, c = 30, d = 40, e = 50;
int *ptrArray[5] = {&a, &b, &c, &d, &e};
示例:
#include <stdio.h>
int main() {
// 定义一个指针数组,用于存储字符串
char *strArray[3] = {"Hello", "World", "C Language"};
// 定义一个二维数组
int numbers[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 定义一个数组指针,指向二维数组的第一行
int (*ptrToArray)[3] = numbers;
// 使用指针数组打印字符串
for (int i = 0; i < 3; i++) {
printf("%s\n", strArray[i]);
}
// 使用数组指针遍历二维数组并打印其内容
printf("Two-dimensional array elements:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", ptrToArray[i][j]);
}
printf("\n");
}
return 0;
}
多维数组和多级指针:
多级指针:多级指针(也称为多重指针或指针的指针)是一种指针变量,它指向另一个指针;多级指针提供了一个额外的间接层级,这允许程序不仅控制数据本身,还控制那些指向数据的指针。
单级指针:是一个指针变量,存储的是另一个变量的地址,也就是这个指针指向该变量;
二级指针:是一个指针变量,存储的是一个指针变量的地址,即它指向另一个指针;
更高级别的指针:例如三级指针,指向另一个二级指针的地址,依此类推。
定义方法:
二级指针:int **ptr;
三级指针:int ***ptr;
使用实例:
#include <stdio.h>
int main() {
int value = 5;
int *ptr1 = &value;
int **ptr2 = &ptr1;
printf("Original value: %d\n", value);
printf("Access using single pointer: %d\n", *ptr1);
printf("Access using double pointer: %d\n", **ptr2);
// 修改值
**ptr2 = 10;
printf("New value after modification: %d\n", value);
return 0;
}
多级指针的作用和用途:
1、动态内存管理;
2、修改函数外部的指针;
3、高级数据结构。
下列代码实现的功能是利用二级指针,修改函数外部的指针,指针变量作为参数传入函数,在函数中用一个二级指针来接收并操作。
#include <stdio.h>
#include <stdlib.h>
void modifyPointer(int **p) {
// 为指针分配新的内存,并验证是否成功
*p = (int *)malloc(sizeof(int));
if (*p == NULL) {
printf("Memory allocation failed\n");
exit(1); // 如果内存分配失败,退出程序
}
**p = 20; // 设置新分配内存的值
}
int main() {
int *ptr = NULL; // 初始化指针为NULL
printf("Initial value of pointer: %p\n", (void *)ptr);
// 调用函数,尝试修改指针
modifyPointer(&ptr);
printf("New value of pointer: %p\n", (void *)ptr);
if (ptr != NULL) {
printf("Value at new address: %d\n", *ptr);
}
// 释放分配的内存
free(ptr);
return 0;
}
二维数组:
二维数组在内存中是以一维的方式排布的。在c语言中,二维数组通常是以行主序存储,这代表第一行的元素先存储,然后是第二行的元素,以此类推。
二维数组中的第一维是一维数组,第二维才是具体的值,二维数组的数组名可以看做常量指针。
二维数组的定义方法:
type arrayName[rows][columns]; //rows 数组的行数 columns数组的列数
示例:
int matrix[3][4] = {
{1, 2, 3, 4}, // 第一行
{5, 6, 7, 8}, // 第二行
{9, 10, 11, 12} // 第三行
};
二维数组的数组名作用:
二维数组的数组名与一维数组的数组名具备相同点,但是又有差别。一维数组的数组名是一个指针,指向数组中的第一个元素;同理,二维数组的数组名也可以视为一个数组指针,指向第一行数组的地址。因为,二维数组在内存中是以一维数组的存储方式存储的。二维数组名可以参与指针运算:例如,对于一个名为 arr
的二维数组,arr + 1
将指向第二行的起始地址,因为 arr
本身被视为指向第一行的指针。
示例:
int matrix[2][3] = {{1,2,3},{4,5,6}};
数组名在使用的时候:
matrix可以获取到整个数组的地址;
*matrix可以访问第一行;
**matrix可以访问第一个元素,即1;
示例:下列代码中。a可以理解为一个数组指针,p也是一个数组指针;这里试图将p指向a。尽管 a
在表达式中退化为指向其第一行的指针(类型为 int (*)[5]
),但是尝试将类型为 int (*)[5]
的指针赋值给类型为 int (*)[4]
的指针 p
是类型不兼容的。这种类型不匹配在C语言中是不允许的,因为它会导致内存访问错误,可能会引起程序运行时错误。所以输出的结果是错误的-4;
#include <stdio.h>
int main()
{
int a[5][5];
int (*p)[4];
p = a;
printf("%d\n", &p[4][2] - &a[4][2]);
}
示例:综合二维数组和二级指针,在堆空间上申请一个二维数组的空间:
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3; // 行数
int cols = 4; // 列数
int i, j;
// 使用指针数组申请二维数组的空间
int **array = (int **)malloc(rows * sizeof(int *));
if (array == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
return 1;
}
for (i = 0; i < rows; i++) {
array[i] = (int *)malloc(cols * sizeof(int));
if (array[i] == NULL) {
fprintf(stderr, "Memory allocation failed for row %d\n", i);
// 释放已经分配的所有行
for (j = 0; j < i; j++) {
free(array[j]);
}
free(array);
return 1;
}
}
// 初始化二维数组
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
array[i][j] = i * cols + j; // 用一些值初始化数组
}
}
// 打印二维数组
printf("The 2D array is:\n");
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
// 释放二维数组的内存
for (i = 0; i < rows; i++) {
free(array[i]); // 释放每行的内存
}
free(array); // 释放指针数组的内存
return 0;
}
小结:
c语言中只有一维数组,并且数组大小必须在编译期就作为常数确定;c语言中的数组袁术可以是任何类型的数据,即数组的元素可以是另一个数组;c语言中只有数组的大小和数组首元素的地址是编译器直接确定的。
数组参数与指针参数的分析:
是什么参数?什么是形参和实参呢?
在c语言中,参数的定义是,在函数调用时传递给函数的值或者变量,用来传递信息给函数,便于函数进行操作。形参就是在声明或者定义函数时的参数,实参是在调用函数时传递过来的具体值或变量。
参数的值传递和地址传递:
值传递:实际参数的值呗复制到形参中,于是在函数调用过程中不会更改实际参数的值,这时候形参和实参在内存中是完全独立的。对形参的计算不会更改实参的值;安全性高。但是复制整个数据可能会导致效率较低。
地址传递:实参的地址被传送到形参中;而形参实际上是实参的一个别名(一般是一个指向实参地址的指针),此时的形参和实参在内存中是一块地址,函数中任何对形参的改变都会影响到实参。允许函数直接修改外部变量,需要更加小心地处理形参。
数组参数:
当函数的形参是一个数组的时候,数组就变成了参数,但是复制整个数组的传递方式会让程序变得繁琐,于是采用了将数组参数退化为指针参数的用法;因此在函数参数中使用数组时,通常传递的是数组的第一个元素的地址(即数组的指针)。这意味着当你传递一个数组给函数时,你实际上是传递了一个指向数组首元素的指针,而不是数组的所有元素的拷贝。
等价关系:
等价关系 | 等效的指针参数 |
一维数组: float a[5] | 指针:float *a |
指针数组: int *a[5] | 指针的指针: int **a |
二维数组: char a[3][4] | 数组指针:char (*a)[4] |
注:C语言中无法向一个函数传递任意的多维数组;为了提供正确的指针运算,在函数的参数传递中,必须提供除了第一维之外的所有长度;
函数与指针:
1、c语言中的函数有自己特定的类型;
2、函数的类型由返回值,参数类型和参数个数共同决定;
3、c语言中可以通过typedef关键字为函数类型重命名。
函数指针:
1、函数指针用于指向一个函数;
2、函数名是执行函数体的入口地址;
3、可以通过函数类型定义函数指针。
函数指针的定义方法:return_type (*pointer_name)(parameter_list);
- return_type 是函数返回的数据类型。
- pointer_name 是指针的名称。
- parameter_list 是函数接受的参数类型列表,与直接声明函数的参数列表相同
示例: 函数指针在代码中,是被定义来指向一个已经存在的函数的入口地址的。
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
// 定义一个函数指针并初始化
int (*func_ptr)(int, int) = add;
// 使用函数指针调用函数
int result = func_ptr(3, 5);
printf("Result: %d\n", result);
return 0;
}
回调函数:
函数指针是一个指针,指向一个函数的入口地址。所以函数指针允许在编程的时候将函数作为参数传递给其他函数。这就是回调函数的由来,这样就允许用户项函数传递一些特定的行为,这些行为在回调函数中被定义。
回调函数的编写流程:
1、定义回调函数:首先定义一个函数,这个函数将作为回调函数被调用。这个函数要满足特定的返回类型和参数列表;
2、传递函数指针:将上述这个函数的函数指针(函数入口地址)传递给另一个函数;这个另一个函数会在函数体的执行中执行回调操作;
3、触发回调:当达到执行回调的条件或者阶段时,库函数或者框架通过传递的函数指针调用回调函数。
回调函数示例:
#include <stdio.h>
void myCallback(int data)
{
printf(""Called back with %d\n", data);
}
void performAction(void (*callback)(int))
{
printf("Performing an action...\n");
if(callback)
{
callback(100); //调用回调函数
}
}
int main() {
performAction(myCallback); // 将回调函数传递给另一个函数
return 0;
}
函数指针数组:
函数指针数组是一个数组,数组中的元素都是函数指针。每个元素都是一个指向一个函数的指针,这些被指向的函数都具有相同的返回类型和参数列表。
函数指针数组的定义方法:return_type (*array_name[array_size])(parameter_types);
return_type
是所有函数的返回类型。array_name
是数组的名称。array_size
是数组中函数指针的数量。parameter_types
是这些函数接受的参数类型。
示例:
#include <stdio.h>
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 main() {
// 定义一个包含三个元素的函数指针数组
int (*operations[3])(int, int) = {add, subtract, multiply};
// 调用数组中的第一个函数(add)
int result1 = operations[0](10, 5);
printf("Add: %d\n", result1);
// 调用数组中的第二个函数(subtract)
int result2 = operations[1](10, 5);
printf("Subtract: %d\n", result2);
// 调用数组中的第三个函数(multiply)
int result3 = operations[2](10, 5);
printf("Multiply: %d\n", result3);
return 0;
}