C语言笔记9:指针

文章目录

C语言笔记9:指针

一、指针变量和地址

取地址

c 复制代码
int main()
{
	int a = 10;
	int * pa = &a;
	return 0;
}

&a取出来的地址是存放a的四个字节中,地址较小的那个。

指针变量的大小

指针变量的大小和平台有关

32位4字节

64位8字节

指针变量类型的意义

对指针变量的操作无非两种:1、指针±整数 2、指针解引用访问指向的空间

有了指针变量类型,指针±整数的时候跳过的地址数就由指针变量的类型决定。指针解引用访问指向的空间修改字节数,和修改的方式也由指针变量的类型决定。

void*类型

void*类型的指针不能直接进行指针±整数和解引用操作void*的作用是用来接收不同类型的指针变量。

const修饰指针

const修饰了变量之后变量本身不能修改,但是如果对该变量取地址然后再修改就可以。

const放在*左边和*右边是不同的效果,放在左边表示解引用操作不能修改指针指向的空间,放在右边表示指针不能进行±整数的操作了。

指针-指针、指针之间的大小比较

指针-指针只能发生在同类型的指针上。

指针之间可以通过>、<、==、来比较大小。

二、野指针

野指针成因

创建指针变量却未初始化

默认是cc cc cc cc
指针越界访问

c 复制代码
int main()
{
	int arr[10] = {0};
	for(int i = 0;i < 12;i++)
	{
		arr[i] = i;
	}
	return 0;
}

当指针对超出数组范围的空间进行修改时,就是越界访问了。
指针指向的空间释放了

c 复制代码
int * test()
{
	int n = 0;
	return &n;
}
int main()
{
	int * pn = test();
	*pn = 10;
	return 0;
}

函数返回局部变量的地址时,指针指向空间释放了。

规避野指针

c 复制代码
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

C和C++对于NULL在定义上有区别,C++引入了nullptr防止重载函数类型匹配的时候出现错误。因为C++的NULL是int类型。
针对未初始化、越界访问、指针指向空间释放三种原因,规避野指针的方法如下

1、始终初始化指针,不知道赋什么值就给NULL

2、小心别越界,越界之后把指针置空。

3、不返回局部变量地址,或者在使用完局部变量地址后将该指针变量置空。

assert断言

c 复制代码
#include <assert.h>
int test(int * pa)
{
	assert(pa != NULL);
	return 0;
}
int main()
{
	int a = 10;
	test(&a);
	
	return 0;
}

不想使用断言也很简单在头文件前加#define NDEBUG

c 复制代码
#define NDEBUG
#include <assert.h>

VS下提供选项,默认release模式是加了这个宏定义的。

三、传值调用和传址调用

模拟实现strlen

c 复制代码
size_t my_strlen(const char* str)
{
	assert(str != NULL);
	int count = 0;
	while(*str)
	{
		str++;
		count++;
	}
   return count;
}
int main()
{
   int ret = mystrlen("abcde");//"abcde"会被解释成一个指向只读数据段(静态区)的指针。const char*
   return 0;
}

swap

c 复制代码
void swap(int *a,int* b)
{
	int temp = 0;
	temp = *a;
	*a = *b;
	*b = temp;
}
int main()
{
	int x = 10;
	int y = 20;
	swap(&x,&y);
	return 0;
}

传址调用才能修改实参的值。

四、数组名的理解

数组名就是数组首元素的地址

c 复制代码
#include <stdio.h>

int main()
{
	int arr[10] = {0};
	printf("%zd\n",sizeof(arr));
	//结果为40   
}
c 复制代码
#include <stdio.h>

int main()
{
	int arr[10] = {0};
	printf("%p\n",&arr + 1);
	//跳过了一整个数组
}

数组名是数组首元素地址不假,但是arr的类型是int [10],&arr的类型是int(*) [10]。sizeof()计算这个类型的大小肯定是40,指针进行整数加减肯定跳过了整个数组。

五、使用指针访问数组

[]下标引用操作符的理解

c 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = {0,1,2};
	int * pa = arr;
	printf("%d\n",arr[0]);
	printf("%d\n",*(pa + 0));
}

arr[0] 等价于 *(arr + 0),这和我们刚刚说arr是int [10]类型似乎有些冲突。这是因为当我们对arr进行整数±或解引用的时候,它会退化成int*类型

那二维数组呢?

c 复制代码
#include <stdio.h>

int main()
{
	int arr[3][5] = { {1,2}, { 3,4 }, { 5,6 } };
	int (*pa)[5] = arr;
	printf("%d\n", arr[0][0]);
	printf("%d\n", *(*(pa + 0) + 0));
}

六、一维数组传参的本质

c 复制代码
#include <stdio.h>

size_t test(int arr[])
{
	int sz = sizeof(arr)/sizeof(arr[0]);
	return sz;
}
int main()
{
	int arr[10] = {0};
	int sz1 = sizeof(arr)/sizeof(arr[0]);
	int sz2 = test(arr);
	printf("sz1= %zd,sz2= %zd\n",sz1,sz2);
	
	return 0;
}


上述代码和结果说明,test函数内部认为arr的类型就是int*,所以写形参的时候,写成int*类型和写成int arr[]是一样的。

七、冒泡排序

c 复制代码
#include <stdio.h>
void swap(int* a, int* b)
{
   int tmp = 0;
   tmp = *a;
   *a = *b;
   *b = tmp;
}
void bubble_sort(int arr[], int sz)
{
   for (int i = 0; i < sz - 1; i++)
   {
       int flag = 0;
       for (int j = 0; j < sz - 1 - i; j++)
       {
           if (arr[j] > arr[j + 1])
           {
               flag = 1;
               swap(&arr[j], &arr[j + 1]);
           }
       }
       if (flag == 0)
       {
           break;
       }
   }
}
int main()
{
   int arr[10] = { 3,6,2,1,4,7,9,0,8,5 };
   int sz = sizeof(arr) / sizeof(arr[0]);
   bubble_sort(arr, sz);
   for (int i = 0; i < sz; i++)
   {
       printf("%d ", arr[i]);
   }
   printf("\n");
   return 0;
}

八、二级指针

c 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	**ppa = 20;
	printf("%d\n",a);
	return 0;
}

存指针变量的地址就是二级指针

九、指针数组

c 复制代码
#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };

	int* parr[3] = { arr1,arr2,arr3 };

	printf("%d %d %d\n", parr[0][0], parr[1][0], parr[2][0]);

	return 0;
}


parr的类型是int* [3]。

十、字符指针变量

c 复制代码
int main()
{
	char str1[] = "hello,world.";
	char str2[] = "hello,world.";
	
	const char* str3 = "hello,world.";
	const char* str4 = "hello,world.";
	
	return 0;
}

str1,和str2是栈上的不同字符数组的首元素地址,而str3和str4则指向静态区的只读数据段。

字符串字面量不同于其他字面量,它在代码段中表现为一个指针(指向只读数据段),而真正存放字符串内容的是只读数据段中的一块区域

十一、数组指针变量

c 复制代码
int (*pa1) [5];
int* pa2 [5];

pa1是数组指针变量,pa2是指针数组。由于[]下标引用操作符的优先级高于*,所以加了()的让*pa1结合,没加()的让pa1[5]结合了。

二维数组传参的本质

前面我们知道int arr[]类型的一维数组传参其实是传int *类型。那同理,二维数组传的也是指针,只不过是数组指针。

c 复制代码
#include <stdio.h>
void test(int (*pa) [5])
{
	printf("%d\n",pa[0][0]);
}
int main()
{
	int arr[3][5] = {{1,2},{3,4},{5,6}};
	test(arr);
	
	return 0;
}

前面说数组名是数组首元素地址,一维数组int arr[10]的数组元素是int,那二维数组int arr[3][5]的数组元素是什么呢?是int [5]类型的数组。

十二、函数指针

c 复制代码
#include <stdio.h>

int Add(int x,int y)
{
	return x + y;
}
void test()
{
	printf("haha\n");
}
int main()
{
	int (*pf1) (int,int) = Add;
	void (*pf2) () = test;
	return 0;
}

函数指针变量创建方式。

拆解复杂函数指针

代码1:

c 复制代码
(*(void (*) ())0)();

这是一个函数调用从右边的*开始拆分。

第一步:void (*) ()
第二步:(void (*) ())0
第三步:(*(void (*) ()) 0)()

表示将0强制类型转换成void (*) ()类型的函数指针,然后调用
代码2:

c 复制代码
void (*signal(int ,void(*)(int)))(int);

这是一个函数声明,从右边的*开始拆分

第一步:void(*)(int)
第二步:signal(int ,void(*)(int))
第三步:void (*signal(int, void(*)(int)))(int)

表示声明一个signal函数,参数是(int,void(*)(int)),返回值类型是一个void(*)(int)类型的函数指针。

使用typedef

简化代码2:

c 复制代码
typedef void (*pfun_t) (int);
pfun_t signal(int,pfun_t);

函数指针数组

c 复制代码
//先看一个函数指针
int Add(int x,int y)
{
	return x + y;
}
int (*pf) (int,int) = Add;
//再看一个函数指针数组
int (*pf[4]) (int,int) = {Add,Sub,Mul,Div};

使用函数指针数组通过传递不同的下标,就能调用相同类型的不同函数了。

十三、回调函数

简单来说,回调函数就是一个通过函数指针调用的函数。

实现一个函数之后,把这个函数作为一个参数传递给另一个函数,这个函数在符合某些条件的时候调用这个函数指针。

利用回调函数实现qsort

c 复制代码
void swap(void* p1,void* p2,int ele_size)
{
   char tmp = 0;
	for(int i = 0;i < ele_size;i++)
   {
       tmp = *((char*)p1 + i);
       *((char*)p1 + i) = *((char*)p2 + i);
       *((char*)p2 + i) = tmp;
   }
}

void my_qsort(void* base,int arr_count,int ele_size,int (*cmp) (void*,void*))
{
	for(int i = 0;i < arr_count - 1;i++)
	{
		int flag = 0;
		for(int j = 0;j < arr_count - 1 - i;j++)
		{
			if(cmp( (char*)base + j*ele_size, (char*)base + (j+1)*ele_size ) > 0)
			{
				flag = 1;
				swap( (char*)base + j*ele_size ,(char*)base + (j+1)*ele_size ,ele_size);
			}
		}
		if(flag == 0)
			break;
	}
}
相关推荐
菩提小狗9 小时前
sqlmap输入命令回车自动退出故障排查
笔记·安全·web安全
廋到被风吹走9 小时前
【Spring】AOP深度解析:代理机制、拦截器链与事务失效全解
java·spring·缓存
没有天赋那就反复9 小时前
JAVA length
java·开发语言·算法
saoys9 小时前
Opencv 学习笔记:图像绘制(直线 / 圆 / 椭圆 / 矩形 / 多边形 + 文字添加)
笔记·opencv·学习
逑之9 小时前
C语言笔记13:数据在内存中的存储
c语言·开发语言·笔记
步步为营DotNet9 小时前
深度探索.NET 中ValueTask:优化异步性能的轻量级利器
java·spring·.net
数据轨迹0019 小时前
AAAI AMD:多尺度预测MLP反杀Transformer
经验分享·笔记·facebook·oneapi·twitter
Aliex_git9 小时前
性能优化 - Vue 日常实践优化
前端·javascript·vue.js·笔记·学习·性能优化
栈与堆9 小时前
LeetCode-88-合并两个有序数组
java·开发语言·数据结构·python·算法·leetcode·rust