C语言中的指针(自我学习总结)

一、指针基础知识

1. 内存和地址

  • 在计算机中,内存被组织成字节的线性序列,每个字节都有一个唯一的地址。地址通常以十六进制形式表示。
  • 我们可以简单理解为:内存单元的编号 == 地址 == 指针

2. 指针变量和地址

指针变量用来存储内存地址。例如:

int var = 10;
int *p = &var; // p 存储了 var 的地址,&为取地址符

3. 指针变量类型的意义

指针的类型决定了它所指向的变量类型,以及解引用时的解释方式。例如:

int *ip;   // 指向 int
double *dp; // 指向 double

 4. const修饰指针

const 可以修饰指针,使其指向的值不可变,const在解引用即*之前还是之后要做出区分:

const int *p; // const在*左边
int const *p; // 同上,const在*左边
int *const p; // const在*右边
  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
  • const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。 

5. 指针运算

指针的基本运算有三种,分别是:

  • 指针+- 整数
  • 指针-指针
  • 指针的关系运算

6. 野指针

野指针是指向未定义或已释放内存的指针,使用它们可能导致程序崩溃:

int *p; // 未初始化的指针,可能指向任何地方

 我们应尽量规避野指针的出现,主要有以下几种方法:

①指针初始化

  • 如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL。
  • NULL 是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。
  • 初始化如下:
#include <stdio.h>
int main()
{
    int num = 10;
    int*p1 = &num;
    int*p2 = NULL;
    return 0;
}

② 小心指针越界

  • 一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。 

③ 指针变量不再使用时,及时置NULL,指针使用之前检查有效性 

  • 当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的一个规则就是:只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否为NULL。
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];
    int i = 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;
}

④ 避免返回局部变量的地址

  • 不要返回局部变量的地址。

7. assert断言

  • assert 用于检查程序中的条件,如果条件为假,则程序终止,使用需要包涵头文件<assert.h>,可用于验证变量p 是否等于NULL。
#include <assert.h>
int main() 
{
    int *p == NULL;
    assert(p != NULL); // 如果指针p不为 NULL,程序将终止
}

8. 指针的使用和传址调用

通过指针,可以在函数中修改外部变量的值:

void increment(int *p) 
{
    (*p)++;
}

int main() 
{
    int x = 0;
    increment(&x);
    printf("%d\n", x); // 输出 1
}

 

二、指针与一维数组的联系

1. 数组名的理解

数组名在大多数表达式中被视为指向数组首元素的指针:

int arr[5];
int *p = arr; // p 指向 arr 的第一个元素

2. 使用指针访问数组

指针可以用于遍历数组:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++)
{
    printf("%d\n", *(p + i)); // 输出数组元素
}

3. 一维数组传参的本质

数组作为参数传递给函数时,实际上是传递了数组首元素的地址:

void printArray(int *parr, int size) 
{
    for (int i = 0; i < size; i++) 
    {
        printf("%d ", *(parr + i));
    }
    printf("\n");
}

int main() 
{
    int myArray[] = {1, 2, 3};
    printArray(myArray, 3);
}

4. 字符指针变量

字符指针常用于字符串操作:

char *str = "Hello, World!";
printf("%s\n", str);

三、指针与二维数组的联系

1. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放的地方就是二级指针

int a = 0;
int * pa = &a
int ** pa = &pa

**ppa 先通过*ppa 找到pa ,然后对pa 进行解引用操作: *pa ,那找到的是a . 

**ppa = 0;
//等价于*pa = 0;
//等价于a = 0;

2. 数组指针变量

数组指针变量指向一个数组:

​int arr[5] = { 1,2,3,4,5 };
int (*p)[5] = &arr

3. 指针数组

指针数组的每个元素是地址,又可以指向一块区域。

4. 指针数组模拟二维数组

#include <stdio.h>
int main()
{
    int arr1[] = {1,2,3,4,5};
    int arr2[] = {2,3,4,5,6};
    int arr3[] = {3,4,5,6,7};
    //数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中
    int* parr[3] = {arr1, arr2, arr3};
    int i = 0;
    int j = 0;
    for(i=0; i<3; i++)
    {
        for(j=0; j<5; j++)
        {
        printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素。

5. 二维数组传参的本质

二维数组作为函数参数时,实际上是传递了指向数组首行的指针。

四、指针与函数的联系

1. 函数指针变量

如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针非常类似。如下:

void test()
{
    printf("hehe\n");
}

void (*pf1)() = &test;
void (*pf2)()= test;

int Add(int x, int y)
{
    return x+y;
}

int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

2. 函数指针数组

把函数的地址存到一个数组中,那这个数组就叫函数指针数组:

int (*parr[3])();

 

五、指针的应用

1. 冒泡排序

冒泡排序通过交换相邻元素实现排序,可以使用指针简化代码:

void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
    int i = 0;
    for(i=0; i<sz-1; i++)
    {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
        {
            if(arr[j] > arr[j+1])
            {
            int tmp = arr[j];
            arr[j] = arr[j+1];
            arr[j+1] = tmp;
            }
        }
    }
}

int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

2. 回调函数

回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数
时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

​void doSomething(int n, void (*callback)(int)) 
{
    // 处理 n
    callback(n);
}

void callbackFunction(int n) 
{
    printf("Callback with %d\n", n);
}

int main() {
    doSomething(10, callbackFunction);
}

3. qsort使用举例

qsort 是C标准库中的快速排序函数,使用函数指针指定比较规则:

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

//整型比较
int cmp_int(void *p1, void *p2)
{
	return *(int*)p1 - *(int*)p2;
}

//字符比较
int cmp_char(void* p1, void* p2)
{
	return *(char*)p1 - *(char*)p2;
}

//整型打印
int Print_int(int* parr, int len)
{
	int i = 0;
	for (; i < len; i++)
	{
		printf("%d ", *(parr + i));
	}
	printf("\n");
}

//字符打印
int Print_char(char* pstr, int len)
{
	int i = 0;
	for (; i < len; i++)
	{
		printf("%c ", *(pstr + i));
	}
	printf("\n");
}

int main()
{
	int arr[] = { 5,6,8,9,7,3,2,1,0,4 };
	char str[] = "zkeorwpqs";

	int len1 = sizeof(arr) / sizeof(arr[0]);
	int len2 = strlen(str);

	qsort(arr, len1, sizeof(arr[0]), cmp_int);
	qsort(str, len2, sizeof(str[0]), cmp_char);

	Print_int(arr, len1);
	Print_char(str, len2);
	
	return 0;
}

4. qsort函数的模拟实现

使用回调函数,模拟实现qsort(采用冒泡的方式):

//模仿qsort的功能实现一个通用的冒泡排序
#include<stdio.h>
#include<string.h>

//结构体创建
struct Stu
{
	char str[1000];
	int score;
};



//数据交换
void Swap(char* p1, char* p2, int len)
{
	int i = 0;
	for (; i < len; i++)
	{
		char tmp = *(p1 + i);
		*(p1 + i) = *(p2 + i);
		*(p2 + i) = tmp;
	}
}




//通用冒泡排序函数
void Bubble_sort(char* base, int mun, int len, int (*com)(void* p1, void* p2))
{
	int i = 0,j = 0;//趟数
	for (; i < mun - 1; i++)
	{
		for (j = 0; j < mun - 1; j++)
		{
			int compar = com(base + j * len, base + (j + 1) * len);
			if (compar > 0)
			{
				Swap(base + j * len, base + (j + 1) * len, len);
			}
		}
	}
}




//整型比较
int cmp_int(void *p1, void *p2)
{
	return *(int*)p1 - *(int*)p2;
}

//字符比较
int cmp_char(void* p1, void* p2)
{
	return *(char*)p1 - *(char*)p2;
}

//结构体整型比较
int cmp_Str_int(void* p1, void* p2)
{
	return ((*(struct Stu*)p1).score) - ((*(struct Stu*)p2).score);
}

//结构体字符串比较
int cmp_Str_str(void* p1, void* p2)
{
	return strcmp(((struct Stu*)p1)->str,((struct Stu*)p2)->str);
}




//整型打印
void Print_int(int* parr, int len)
{
	int i = 0;
	for (; i < len; i++)
	{
		printf("%d ", *(parr + i));
	}
	printf("\n");
}

//字符打印
void Print_char(char* pstr, int len)
{
	int i = 0;
	for (; i < len; i++)
	{
		printf("%c ", *(pstr + i));
	}
	printf("\n");
}

//结构体打印 
void Print_struct(struct Stu* pa, int len)
{
	int i = 0;
	for (; i < len; i++)
	{
		printf("%s ", (pa + i)->str);
	}
	printf("\n");
}





int main()
{
	//整型数组排序
	int arr[] = { 5,6,8,9,7,3,2,1,0,4 };
	int len1 = sizeof(arr) / sizeof(arr[0]);
	Bubble_sort(arr, len1, sizeof(arr[0]), cmp_int);
	Print_int(arr, len1);
	

	//字符数组排序
	char str[] = "zkeorwpqs";
	int len2 = strlen(str);
	Bubble_sort(str, len2, sizeof(str[0]), cmp_char);
	Print_char(str, len2);
	

	//结构体排序
	struct Stu s[] = { {"abcde",96},{"bcdef",97},{"cdefg",89},{"defgh",92},{"efghi",94} };
	int len3 = sizeof(s) / sizeof(s[0]);
	//按其中整型排序
	Bubble_sort(s, len3, sizeof(s[0]), cmp_Str_int);
	Print_struct(s, len3);
	//按其中字符串排序
	Bubble_sort(s, len3, sizeof(s[0]), cmp_Str_str);
	Print_struct(s, len3);

	return 0;
}

5. 转移表

函数指针数组的用途:转移表
举例:计算器的一般实现:

#include<stdio.h>

void Menu()
{
	printf("********   计算器   *******\n");
	printf("********   1.相加   *******\n");
	printf("********   2.相减   *******\n");
	printf("********   3.相乘   *******\n");
	printf("********   4.相除   *******\n");
	printf("********   0.退出   *******\n");
	printf("***************************\n");
}

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0, y = 0;
	int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		Menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			printf("请输入两个操作数:");
			scanf("%d%d", &x, &y);
			int ret = pfArr[input](x, y);
			printf("结果为:%d\n", ret);
		}
		else if (input == 0)
		{
			printf("已退出计算器!");
		}
		else
		{
			printf("无效输入,请重新输入!\n");
		}

	}
	while(input);
	return 0;
}

六、总结

指针是C语言的核心特性之一,它们提供了对内存的直接操作能力。正确使用指针可以提高程序的性能和灵活性,但同时也需要小心处理,以避免野指针等常见错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值