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
626

被折叠的 条评论
为什么被折叠?



