C语言编程---指针

本文详细介绍了C语言中的指针概念,包括指针的定义、指针的指针、指针数组、函数指针以及动态数组的使用。通过示例代码展示了如何通过指针操作数组,以及如何使用函数指针进行回调。文章还探讨了静态和动态数组的区别,并给出了使用malloc分配和释放内存的例子。

指针

指针定义

  • 指针就是地址,分配一块内存,存储其他数据的地址信息
  • 定义的变量来存储数据,而指针存储地址,是一个十六进制数据;
  • 定义指针
// 声明指针,分配内存,准备存储地址(此时未初始化,不能直接使用)
int* p1; // 分配4bytes or 8bytes内存

// int 表示指向的数据类型
// * 代表是指针  
// p1是指针变量(内存的名称),用于存储地址数据
int a = 10;
p1 = &a;  //初始化指针,指向a的地址(栈内存),才可以安全地使用;

float *p2;  // * 可以跟类型,也可以跟变量
double* p3;
char* p4;

int a = 10; // 变量是内存地址的名称

p1 = &a; // & 取地址运算符 与 *指针运算符 (优先级最高,从右到左结合) 
// 整型变量占用四个 字节,第一个字节的首地址存入p1;
printf("地址%d\n", p1);
printf("地址中的值%d\n", *p1); // * 解引用地址的值,相当于拿到了变量,可以赋值或读取值
  • 指针 p1++; --p1则移动一个数据类型的长度,如int则移动四个字节;
  • p1++先取地址,再加; ++p1 先加再取地址;
  • *p1 解引用,相当于变量,&(*p1)获取变量地址;*(&a) 获取变量
  • 一般初始化指针为NULL;
    在指针未存储任何地址时,初始化为NULL(0x0的地址)
int *p = NULL;
if(p){// 存在
	printf("指针p有指向");
}

if(!p){
	printf("指针p无指向");
}
  • 一维数组与指针
    • int arr[] = {1,2,3}, *p;
    • arr数组名称,即为数组的首地址,相当于&arr[0];
    • arr[i] 通过索引获取元素值,&arr[i] 获取元素地址
    • p = arr 赋值首地址,p++ 实现地址偏移,*p 获取元素值,*p++ 先获取值,在移动地址;也可以p[i]访问元素值;
#include <stdio.h>

// const定义一个常量
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针赋值 数组首地址 */
   ptr = var;
   
   // ptr = var + 1 // 等价 &var[1]
   
   /* 数组中最后一个元素的地址 */
   // ptr = &var[len-1];
  
   for ( i = 0; i < len; i++)
   {
 
      printf("地址:var[%d] = %p\n", i, ptr );
      printf("值:var[%d] = %d\n", i, *ptr );
 
      /* 指向地址 移动到下一个元素 */
      ptr++; 
   }
   return 0;
}

输出如下,
地址:var[0] = e4a298cc
值:var[0] = 10
地址:var[1] = e4a298d0
值:var[1] = 100
地址:var[2] = e4a298d4
值:var[2] = 200
 
指针是十六进制的整数地址,可以比较大小;
指针还可与数组元素的地址进行比较,如 ptr <= &var[len-1] 就可以一直遍历。

 

  • 二维数组与指针
    • int arr[2][3] = {{1,2,3}, {5,7,11}}
    • *(arr+0)、&arr[0]、&arr[0][0] 获取第一个元素1的地址;
    • 获取5的地址->*(arr+1);
    • 获取11的地址->*(arr+1) + 2,相当于arr[1] + 2;
    • 获取元素地址
      • arr[i] + j;
      • *(arr+i)+j
    • 获取元素值
      • 索引获取 arr[i][j];
      • 地址解引用 *(*(arr+i)+j)
// 为一个二维int 数组 连续赋值,然后依次输出所有元素
#include <stdio.h>


int main() {

	int arr[2][3] = { 0 }; // 仅初始化一个0,数组长度必须为常量值

	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			scanf("%d", *(arr + i) + j);
		}
	}

	printf("\n索引访问数组:\n");
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			// 索引访问
			printf("arr[%d][%d]=%d ", i, j, arr[i][j]);
		}
		printf("\n");
	}


	printf("\n地址访问数组:\n");
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			// 地址访问
			printf("arr[%d][%d]=%d ", i, j, *(*(arr+i)+j));
		}
		printf("\n");
	}

	return 0;
}

非线程安全问题:
在这里插入图片描述
解决:
项目上右键->属性->C/C++ /命令行 输入:/D xxxx ,然后应用即可;
在这里插入图片描述

 

指针(地址)解析

指针的定义:


int a; // 定义一个整形变量,分配4bytes内存(32位系统),变量即为该地址空间的名称
a = 5; // 存入5

int* p;
p = &a;  //赋值 a变量的首地址

 
指针的原理:
在这里插入图片描述

 
字符类型的指针,可表示字符串:

char* name;
name = "jack";
printf("姓名: %s\n", name); //指针指向首个字符的地址

 

指针的指针

定义一个指针,来存储其他指针的地址,这个指针就是指向指针的指针。

//定义在main函数
int arr[3];  // 定义整型数组,并分配内存
arr[0] = 3;
arr[1] = 20;
arr[2] = 30;

//定义指针
int* p;
p = arr;  //数组的名字就是首地址(指针)
// 定义指针的指针
int** pp = &p;
printf("arr[%d]:%d\n", 0, **pp); //引用地址的最终数值
// *pp 解引用p内存中的地址
// *(*pp) 再次解引用p内地址 存储的值

指针的指针原理:
在这里插入图片描述

 

指针数组

  • 指针组成的数组;
  • 数组中的每个元素都是一个指针。
// 定义在main函数
char* arr[3]; // {"jack", "lucy"}这种只能定义变量的同时 赋值
arr[0] = "jack";  //定义数组后,再赋值只能根据索引逐一赋值
arr[1] = "tom";
arr[2] = "lucy";

// 打印
for(int i=0; i < 3; i++){
	printf("arr[%d]: %s\n", i, arr[i]);
}

 

函数的指针

指向函数的指针。

// 定义函数
int testP(int a, int b){
	printf("testP running...");
	return a + b;
}


//指向函数的指针   定义在main函数
int (*p)(int, int) = &testP; // & 可以省略

//调用
p(3, 5);
  • 交换两个变量的值,变量传参,未符合预期
// 利用函数的指针 实现a、b变量值的互换
int swapVal(int a, int b) {
	int temp;
	temp = a;
	a = b;
	b = temp;

	printf("交换后,函数内部a: %d\n", a);
	printf("交换后,函数内部b: %d\n", b);
	return 0;
}

int main() {
	
	// 定义两个变量
	int a, b;
	a = 11;
	b = 22;

	// 定义函数的指针,即指向函数的指针
	int (*ptr)(int, int) = &swapVal;

	// 指针执行函数
	ptr(a, b); // (整型、浮点型、字符、枚举)基本类型 通过变量传入数值

	printf("原始a:%d\n", a);
	printf("原始b:%d\n", b);  // 可以发现 函数swapVal并没有实现a、b数值的交换
	return 0;
}
  • 交换两个变量的值,指针传参,符合预期

// 利用函数的指针 实现a、b变量值的互换
int swapVal(int* a, int *b) {
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;

	printf("交换后,函数内部a: %d\n", *a);
	printf("交换后,函数内部b: %d\n", *b);
	return 0;
}

int main() {
	
	// 定义两个变量
	int a, b;
	a = 11;
	b = 22;

	// 定义函数的指针,即指向函数的指针
	int (*ptr)(int*, int*) = &swapVal;

	// 指针执行函数
	ptr(&a, &b); // (整型、浮点型、字符、枚举)基本类型 通过变量传入数值

	printf("原始a:%d\n", a);
	printf("原始b:%d\n", b);  // 可以发现 函数swapVal并没有实现a、b数值的交换
	return 0;
}

 

  • 基于指针实现冒泡排序

void bubbleSort(int *arr, int size) { // 除了main函数外,其他函数中不能使用sizeof
	int temp;
	for (int i = 0; i < size - 1; i++) {
		for (int j = 0; j < size - 1 - i; j++) {
		
			if (*(arr+j) > *(arr + j + 1)) {
				temp = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = temp;
			}
		}
	}
}


int main() {
	
	int arr[] = { 4,3,2,1,5 };
	bubbleSort(arr, 5);

	for (int i = 0; i < 5; i++) {
		printf("arr[%d]=%d\n", i, arr[i]);
	}
	
	return 0;
}
  • 基于指针实现:求整型二维数组中每行的最大值,并对其累加求和;

int cumSum(int arr[][3], int row, int col) { // 二维数组必须指定列
	int result = 0; // 局部变量必须初始化
	for (int i = 0; i < row; i++) {
		int curMax = **(arr + i); // 获取每行的第一个值,假设为最大
		int curVal;
		for (int j = 0; j < col; j++) {
			curVal = *(*(arr + i) + j);
			if (curVal > curMax) {
				curMax = curVal;
			}
		}

		// 当前行的最大值,累加
		result += curMax;
	}

	return result;  //返回数值, 不能在函数内部创建数组并返回首地址,局部变量内存空间会释放,导致首地址无效

}


int main() {
	
	int arr[][3] = {4,3,2,1,5,7};
	int result = cumSum(arr, 2, 3);
	printf("%d\n", result);
	return 0;
}

 

函数返回指针

  • 不能在函数内部创建数组,并返回其首地址(弹栈、释放内存、首地址失效);
  • 可以将外部变量的地址传给函数,并在函数内部返回该地址;
// 传递数组给函数,形参定义必须为数组或者指针
char* testArr(char arr[], int arrLen){
	for(int i=0; i < arrLen; i++){
		printf("arr[%d]: %c\n", i, arr[i]);
	}
	return arr; // 返回数组(即指针)
}

//main中测试
char arr[3];
arr[0] = 'a';
arr[1] = 'g';
arr[2] = 't';
testArr(arr, 3);

在这里插入图片描述
 

综合应用

技术点:

  • 指针数组
  • 字符串及其比较
  • 指针的指针
  • 指针的算术运算
    遍历数组中的所有的用户名,将值为‘jack’的用户名输出,并停止循环
char* getUser() {
	char* users[] = { "tom\a", "lucy\n", "jack\t", "jack" };
	char* tt = "jack";
	char** ptr = users; // 数组的名称,即数组指针
	while (ptr <= &users[3]) {
		if (strcmp(*ptr, tt) == 0) {
			printf("目标用户:%s\n", *ptr);
			//break;
			return *ptr;
		}
		ptr++; // 指向下一个元素
	}
	return "null";
}

#include <string.h>  //预处理器,编译时文本替换的步骤
int main() {
	
	char* result = getUser();
	printf("获取的结果:%s\n", result);
	
	system("pause");
	return 0;
}

 

再探数组

数组指针
数组的名称,即首元素地址,即指向数组的指针;
另外数组的声明及初始化尽量放函数内部。

动态数组与静态数组

  • 静态数组
    • 声明时需要指定长度;
    • 编译时分配内存,大小固定;
    • 生命周期与作用域有关;
  • 动态数组
    • 运行时手动分配内存,大小可变;
    • 生命周期由程序员控制。
    • 使用动态数组时,需要分配和释放内存,以避免内存泄漏和访问无效的内存
    • 使用malloc/calloc分配内存;

静态数组:

int staticArray[5]; // 静态数组声明
int staticArray[] = {1, 2, 3, 4, 5}; // 静态数组声明并初始化
// 静态数组,可以使用 sizeof 运算符来获取数组长度,例如:
int length = sizeof(staticArray) / sizeof(staticArray[0]);
//以上代码中 sizeof(array) 返回整个数组所占用的字节数,而 sizeof(array[0]) 返回数组中单个元素的字节数,将两者相除,就得到了数组的长度。

动态数组:
分配一个长度为10的整数数组空间,分别按照动态数组、指针的形式赋值并输出;

#include <stdio.h>
#include <stdlib.h>

int main() {
    int size = 10; //指定要分配的长度
    int *dynamicArray = (int*)malloc(size * sizeof(int)); // 动态数组内存分配
		
	// 分配失败
    if (dynamicArray == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }

    for (int i = 0; i < size; i++) {
        scanf("%d", &dynamicArray[i]); // 动态数组
    }

    for (int i = 0; i < size; i++) {
        printf("%d ", dynamicArray[i]);
    }
	
	// 动态数组内存释放
    free(dynamicArray); 

    return 0;
}


// 指针形式
//int main() {
//	
//	int* dynamicArr = (int*)malloc(10 * sizeof(int));
//	if (dynamicArr == NULL) {
//		printf("分配地址失败");
//	}
//	else {
//		for (int i = 0; i < 10; i++) {
//			*dynamicArr = pow(i+1, 2); // 取幂计算
//			dynamicArr++;
//		}
//
//		dynamicArr -= 10;
//		for (int i = 0; i < 10; i++) {
//			printf("%d\n", *dynamicArr);
//			dynamicArr++;
//		}
//	}
//	dynamicArr -= 10;
//	free(dynamicArr);
//	return 0;
//}

分配内存后,需要检查分配是否成功(即 dynamicArray== NULL),以避免在内存分配失败时发生错误。
 

函数指针

  • 指向函数的指针,该指针可以直接调用(像函数一样);
  • 回调函数,将一个函数传递给另一个函数,在其内部调用。python里属于高阶函数范畴。
    定义函数指针
// 定义求和函数
int sum(int arr[], int size){
	int result = 0;
	for(int i = 0; i < size; i++){
		result += arr[i];
	}
	return result;
}

// 定义main
int main() {
	// 定义函数指针
	int (*ptr)(int[], int) = &sum;  // 获取函数的地址

	int arr[] = { 1, 3, 10 };
	int result = ptr(arr, 3);
	printf("函数指针: %d\n", result);


	system("pause"); // 系统调用
	return 0;
}

回调函数
将一个函数A以实参形式,传给另一个函数B,在其内部调用。
此时,B函数的形参必须定义为函数指针形式。

// 定义一个函数,计算数组的均值,同时将以上的sum函数,传递进去,用于回调。
double calcAvg(int arr[], int size, int(*ptr)(int[], int)){
	int calcSum = ptr(arr, size);
	return (double) calcSum / size;
}

int main() {
	int arr[] = { 1, 3, 10 };
	double avg = calcAvg(arr, 3, sum); // 实参 传入sum函数
	printf("函数指针: %f\n", avg);

	system("pause"); // 系统调用  (调cmd命令)
	return 0;
}

 
 
[上一篇]:C语言编程—函数与头文件
[下一篇]:C语言编程—字符串与结构体

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

laufing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值