C语言--指针简介

C语言–指针简介

指针(pointer)的实质:

变量的创建—内存中申请一块空间,用于存放数据
为了便于寻找,内存中的每块空间都有其编号
所谓指针,即为其所占内存(编号)

开始前介绍两种操作符:

&–取地址操作符
*–解引用操作符(间接访问操作符)

示例:

int* pa = &a;//编号=地址=指针
	//int---说明a的类型为int
	//*---说明pa为指针变量
	//pa---指针变量--存放指针(地址)
	printf("%d\n",a);
	*pa = 0;
	printf("%d\n", a);
	
	return 0;

输出结果:

10
0//显然,指针的改变会引起指针所指变量的改变
//通过解引用操作符,能够间接访问并改变变量的值

如何求指针变量的大小?

答:指针变量大小等于地址储存大小

示例:

int a = 10;
	int* pa = &a;
	int len = sizeof(pa);
	printf("%d", len);//8
	return 0;

输出结果: 8

32位机器–32个bit–4个字节
64位–64bit–8字节(大部分计算机)
注:与数据类型无关

指针变量的意义

示例:

int main()
{
	int a = 0x11223344;//8*4=32
	//0x000000F8502FF754  44 33 22 11  D3".---内存
	char pa = &a;   //无法转化--char字符范围:  -128----127
	//00 33 22 11
	int* pa = &a;
    *pa = 0;    //int--- 00 00 00 00---4个//char--- 00 33 22 11---1个
	return 0;
}

总结:
指针类型决定指针解引用的权限
例:
char*—1个
int*—4个
double*—8个

提醒:尽量不要乱用,可能出错

比如:

int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;
	printf("%p\n", pa);
	printf("%p\n", pc);
	//二者一致


	printf("%p\n", pa + 1);//输出结果不同
	//int:   pa+1-->地址+4(跳过4个字节)
  //同理:char:   pc+1-->地址+1(跳过1个字节)
	       
	return 0;
}

void*-----泛型指针

优点:各种指针都能够容纳
缺陷:
1无法算术运算(加减乘除)—只用于存放数据地址

int a = 10;
void* pa = &a;
char c = 'w';
void* pc = &c;
*pa += 1;//error!//无法进行算术运算
*pc += 1;//error!
printf("%p\n", pa);
printf("%p\n", pc);

2.类型安全缺失

int num = 10;
float f = 3.14;
void *ptr;

ptr = #    // OK
ptr = &f;      // 也OK,但类型信息丢失

// 使用时必须强制转换,容易出错
int *p = (int*)ptr;  // 如果ptr实际指向float,这里就错了
*p = 20;             // 可能破坏内存

3.内存管理困难

// 分配内存时不知道具体类型大小
void *alloc_memory(size_t size) {
    return malloc(size);
}

int *arr = (int*)alloc_memory(10 * sizeof(int));
// 如果调用者传错了size,会导致问题

4.解引用受限----不能直接赋值

int x = 100;
void *ptr = &x;

// *ptr = 200;     // ❌ 错误!不能直接解引用
*(int*)ptr = 200;  // ✅ 必须强制转换后才能操作

数组中指针的应用

示例:

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//目标:打印数组所有元素--
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int *pa = &arr[0];
	//取得第1个元素地址

for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(pa + i));
	}
	//*p=arr[0]-->*(p+i)表示arr数组中下标为i的元素


	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *pa);
		*pa += 1;//p++;  //p=p+1;
	}


	for (i = 0; i < sz; i++)
	{
		printf("%d\n", arr[i]);
	}

	//倒着打印:
	//取得最后一个元素地址(  *p=arr[sz-1];  )
	//*(p-i)或者 *p;  p--;

指针间的算术运算

前提:参与运算的指针都属于同一空间

指针相减的意义:两指针间元素个数

示例:

int main()
{
	int arr[10] = { 0 };
	printf("%d", &arr[9] - &arr[0]);//输出:9
	printf("%d", &arr[0] - &arr[9]);//输出:-9
	//数组下标增长--地址由低到高
	return 0;
}

练习:
利用指针设计函数来求字符串长度

int my_strlen(char*p)//int-> size_t最合适
{
	int count = 0;  //size_t
	while (*p != '\0')
	{
		p++;
		count++;
	}
	return count;
}

设计完毕

const–常变量

例:

const int a = 10;//常变量
 a = 20;//无法修改--语法限制--本质为变量
        //C++环境中为常量
int* pa = &a;//C++中无法进行--C语言可以


const int* pa = &a;//避免有人通过指针修改指向对象变量--限制*pa--pa可改(地址)
int* const pa = &a;    //限制pa--*pa可改(对象)
count int* const pa=&a;//指针变量与指针指向对象都无法修改


野指针:

定义:指针指向位置不可知

可能原因:

1.未初始化

int main()
{
	int* pa;//使用未初始化的内存“pa”。
	*pa = 20;
	printf("%d", *pa);
	return 0;//代码生成失败
}

2.指针访问越界

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i <= sz; i++)
{
	printf("%d\n", *p);
	p++;
}//输出:1 2 3 4 5 6 7 8 9 10 0(越界)
return 0;

3.指针指向空间释放

int* test()
{
	int n = 100;
	return &n;
}//变量n被销毁
int main()
{
	int*p=test();
	
	printf("%d\n", *p);
	//输出:100--正确--运气而已
	printf("hehe\n");

  printf("%d\n", *p);//输出错误
	return 0;
}

注意事项:
指针初始化
明确–直接赋值
不明确–NULL

NULL的作用:初始化野指针,避免干扰

NULL------空指针

野指针–野狗–危险
空指针–栓住–无害

int* p = NULL;//p未指向有效空间

指针变量不再使用时应及时置空–NULL

要使用时再先判断是否为空

既然如此,我们怎么判断呢?

由此引出断言语句—assert语句

注:使用前需要assert.h头文件定义宏assert()

运行逻辑:

确保程序符合条件–不符合即报错–终止运行

示例:

#include <assert.h>

int main()
{
	int a = 10;
	int* pa = &a;
		pa = NULL;
	assert(pa != NULL);//pa不为空指针--若空则报错

	printf("%d\n", *pa); 
	return 0;
}

输出结果:(报错)

Assertion failed: pa != NULL, file C:\Users\su273\OneDrive\文档\Project.c\Project1\Project1\源.cpp, line 2029

传值调用与传址调用

示例:

void swap1(int x, int y)//形参是实参的临时拷贝
{
	int z = 0;
	z = x;
	x = y;
	y = z;//x,y值调换不会影响ab

}

void swap2(int* pa, int* pb)
{
	int z = 0;
	z = *pa;
	*pa = *pb;
	*pb = z;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前:a=%d,b=%d\n",a,b);
	int* pa = &a;
	int* pb = &b;
	swap1(a,b);
	printf("交换后:a=%d,b=%d\n",a,b);//传值调用--错误
	swap2(&a, &b);//传址调用--需要改变实参(主调函数中变量)是要用
	printf("交换后:a=%d,b=%d\n",a,b);
	return 0;
}

输出结果

交换前:a=10,b=20
交换后:a=10,b=20//swap1:错误
交换后:a=20,b=10//swap2:正确

一般来说:
数组名是数组首元素地址

然而,有2个例外
sizeof、&-------代表整个数组的地址

例1:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 
printf("%zd\n", sizeof(arr));//输出:40?
 //若arr为数组首元素地址,为什么不是4/8呢?
 return 0;`

理由显而易见:
sizeof(arr)代表整个数组

例2:

int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);

	printf("%p\n", arr+1);
	printf("%p\n", &arr[0]+1);
	printf("%p\n", &arr+1);//跳过了整个数组
	

输出结果:

00000023D5DBFC88
00000023D5DBFC88
00000023D5DBFC88//数组地址从值上与首元素地址一致
00000023D5DBFC8C
00000023D5DBFC8C
00000023D5DBFCB0

原因:指针类型不一致

&arr代表了整个数组

利用指针访问数组

int arr[10] = { 0 };
//存放1-10;
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = arr;
for (i = 0; i < sz; i++)
{
	scanf("%d", p);
	p++;
}
p = arr;
for (i = 0; i < sz; i++)
{
	printf("%d\n", *p);
	
	p++;
}
for (i = 0; i < sz; i++)
{
	printf("%d\n", arr[i]  /*或者  i[arr]*/  );//实质均为*(arr+i)

}
for (i = 0; i < sz; i++)
{
	printf("%d\n", *(arr+i));

}//三种方式完全等价
 

注:函数内不适合求参数部分的数组元素个数
例:

void test(int* arr)
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz2);//Error!
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz1);
	test(arr);
	return 0;
}

输出:

10
2

函数内输出结果明显错误

利用指针设计冒泡排序:

不用指针:

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	int count = 0;
	for (i = 0; i < sz-1; i++)//sz-1次:每次只移动最大的未移动数
	{
		int flag = 1;//默认有序
		int j = 0;
		for (j = 0; j < sz-i-1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int t = 0;
				t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
				count++;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;//有序直接输出,加快运行速度
		}
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d  ", arr[i]);
	}
	printf("\n交换次数为%d\n", count);
}
int main()
{
	int arr[10] = { 10,1,8,7,6,5,4,3,2,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	return 0;
}

指针的简洁写法:

void bubble_sort(int *arr, int sz)
{
    int count = 0;
    int sorted = 0;  // 记录已排序部分的边界
    
    for (int i = 0; i < sz - 1; i++)
    {
        int flag = 1;
        int *p = arr;  // 指针指向数组开始
        
        // 内层循环使用指针
        for (; p < arr + sz - 1 - sorted; p++)
        {
            if (*p > *(p + 1))
            {
                // 交换
                int temp = *p;
                *p = *(p + 1);
                *(p + 1) = temp;
                count++;
                flag = 0;
            }
        }
        
        sorted++;  // 已排序部分增加
        
        if (flag)
            break;
    }
    
    // 输出结果
    printf("排序结果: ");
    int *p = arr;
    while (p < arr + sz)
    {
        printf("%d ", *p++);
    }
    printf("\n交换次数: %d\n", count);
}

int main()
{
    int arr[10] = { 10, 1, 8, 7, 6, 5, 4, 3, 2, 9 };
    bubble_sort(arr, 10);
    return 0;
}

二级指针

二级指针变量–指向对象为int*类型

int main()
{
	int a = 10;
	int* pa = &a;   //一级指针变量
	int** ppa = &pa;
	//存放
	printf("%d\n", **ppa);

	return 0;
}

同理,可引出三级指针,四级指针……
此处不再赘述

指针数组–存放指针的数组

示例:

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	int* arr[3] = { pa,pb,pc };
	return 0;
}

练习:
使用指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };
	//arr为指针数组:1    2    3
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d   ", arr[i][j]);
		}
		printf("\n");
	}
}

数组指针变量

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;//整个数组地址--int(*)[10]类型
	//p--数组指针变量 
	//int                 (*p)               [10]  =  &arr
	//数组元素类型   数组指针变量名   p指向数组元素个数
	return 0;
}

数组与指针两种形式对比:

void test(int (*arr)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(arr + i) + j))//i行j列
			     // arr[i][j]--数组形式       *(*(arr+i)+j)--指针形式
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
	test(arr, 3, 5);
	return 0;
}

函数指针变量–存放函数地址,指向函数

类推:
怎么得到数组地址?–&数组名
怎么得到函数地址?–&函数名

示例:

int Add(int x, int y)
{
	return x + y;
}
int* test(char* x, double y)
{
	return 0;
}
int main()
{
	int(*p)(int,int) = &Add;//pf--函数指针变量
	int* (*t)(char*, double) = &test;
	int r = p(10, 20);//p的*可加可不加(编译器不会读取)
	printf("%d\n", r);

	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

至此,指针的大致内容已介绍完毕

本人大一,第一次写博客,请各位大佬喷轻点o(*  ̄︶ ̄* )o
如果有错误恳请指正,十分感谢!

另外:
两个有意思的指针:
1.(* (void(*)())0)();
2.void ( * signal(int, void( *)(int))) (int);
请解释代码:

答案如下:

1.void(* p)();//函数指针类型
// 将0强制转化为函数指针类型
// (* (void(* )())0);//对0地址进行解引用,调用0地址处的函数
2.声明函数类型为void,名字为signal,参数为int
// 该signal函数有两个参数,
// 第1个为int,第2个为函数指针类型–指向函数参数为int,返回类型为void

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值