指针定义
1. 指针是一个值为内存地址的变量。
2. 指针本质上是地址,是计算机存放数据的空间。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int a = 1; printf("%p", &a); return 0; } //%p用来打印地址(16进制形式打印)。 //整形变量a占据4个字节,取出的是第一个字节的地址(较小的)。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int pooh = 5; int* ptr = &pooh; } //ptr指向了pooh。 //ptr是变量,&pooh是常量。 //ptr的值是pooh的地址。
指针的声明
1. 指针使用之前需要声明。
int a; int* pa=&a; //int* 说明指针的类型 //int 说明指针指向变量的类型 //* 说明这是一个指针 //pa 说明了指针的名字
解引用操作
1. 指针类型决定了在进行解引用操作时访问的空间大小。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int a = 20; int* pa = &a;//创建指针变量pa来存放地址 printf("%d", *pa);//*是解引用操作符,*pa==a return 0; }
泛型指针
1. 可以接收任意类型的地址,但是不能进行加减运算和解引用操作。
2. 用于存放未知类型数据的地址。
3. 用于接收存储,不能对其进行操作。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { int a = 20; void* pa = &a; return 0; }
const 保护
const 修饰普通变量
1. const表示常属性,普通变量被修饰后,就不能再被修改。
2. 普通变量被修饰后,可以通过对指针解引用的方式来修改。
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { const int a = 20;//a变成了常变量 a = 10;//这一步报错,a的内容不能改变 return 0; }
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { const int a = 20; int* pa = &a; *pa = 10;//通过解引用来修改常变量的值 printf("%d", a);//10 return 0; }
const 修饰指针变量
const 如果放在 * 的左边:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { const int a = 20; int b = 10; int const * pa = &a; //const放在*左边,修饰* pa,使得无法改变* pa(20),但可以改变指针指向的对象(&a改成&b)。 *pa = 10;//报错 pa = &b;//正确 printf("%d", *pa);//10 return 0; }
const 如果放在 * 的右边:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { const int a = 20; int b = 10; int* const pa = &a; //const放在*右边,修饰pa,使得无法改变pa(&a),但可以改变*pa(20)。 *pa = 10;//正确 pa = &b;//报错 printf("%d", a);//10 return 0; }
指针运算
1. 指针与整数之间的运算,实际上要看指针指向的数据类型。
2. 指针指向的数据类型决定了指针的步长。
指针与整数运算
#include <stdio.h> //指针+-整数 int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int* p = &arr[0]; int i = 0; int sz = sizeof(arr)/sizeof(arr[0]); for(i=0; i<sz; i++) { printf("%d ", *(p+i));//(p+i)这⾥就是指针+整数,实际上是地址运算。 } return 0; }
指针与指针比较
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> //地址之间进行比较 int main() { int arr1[10] = { 0 }; int* p = arr1; int sz = sizeof(arr1) / sizeof(arr1[0]); while (p < sz + arr1)//地址之间也能进行比较 { printf("%d", *p); p++; } return 0; }
野指针
1. 未初始化的指针就是野指针,野指针可以指向任何地方,可能会造成非法访问内存地址。
2. 野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。
3. 尽量避免野指针的形成。
int* p;//p未初始化,为野指针,指向任意值。
野指针成因
指针未初始化
#include <stdio.h> int main() { int* p;//局部变量指针p未初始化,默认为随机值,即生成野指针。 *p = 20;//不能对野指针进行解引用赋值操作。 return 0; }
指针越界访问
#include <stdio.h> int main() { int arr[10] = { 0 }; int* p = &arr[0]; int i = 0; for (i = 0; i <= 11; i++) { *(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针,直接报错。 } return 0; }
指针释放后未置空
int main() { int *p = NULL; p = malloc(10 * sizeof(int)); if (p==NULL) //开辟内存失败 { exit(1); } else //成功开辟内存,可以操作内存。 { free(p);//释放指针p指向的空间后,此时指针p任然存在,只不过指向了无效空间。 p = NULL;//为了防止指针p形成野指针,就把其赋值NULL。 return 0; } }
空指针
1. 一个指针不指向任何数据,我们就称之为空指针,空指针用NULL表示。
2. 如果明确知道指针指向哪里,就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL.
3. NULL 是C语言中定义的⼀个标识符常量,值是0,地址也是0,这个地址是无法使用的,读写该地址会报错。所以在解引用之前,必须确保它不是一个NULL指针。
4. 我们常用的动态内存开辟函数,如malloc,calloc,realloc,如果它们开辟动态内存失败就会返回空指针,所以动态开辟函数后,都要判断是否开辟成功。
#include <stdio.h>//NULL是标准库里面的内容 int main() { int num = 10; int* p1 = # int* p2 = NULL; return 0; }
#include <stdio.h> #include <stdlib.h> int main() { int num = 0; scanf("%d", &num); int arr[num] = {0}; int* ptr = (int*)malloc(num*sizeof(int));//开辟一个(4*num)大小的空间 if(NULL != ptr)//判断ptr指针是否为空 { int i = 0; for(i=0; i<num; i++) { *(ptr+i) = 0; } } free(ptr);//释放ptr所指向的动态内存 ptr = NULL;//避免重复释放 return 0; }
assert 断言
1. assert()用来判断程序是否符合指定条件,如果不符合,就报错并终止程序运行。
2. 需要引入<assert.h>头文件。
3. 如果已经确认程序没有问题,就不需要再做断言,可以在 #include 语句的前面,定义⼀个宏 NDEBUG ,那么assert()将不会发挥作用。
#define _CRT_SECURE_NO_WARNINGS #include <assert.h> int main() { int a = 20; int* pa = &a; int* pb = NULL; assert(pb != NULL);//报错,退出程序 }
#define NDEBUG #include <assert.h>
传值调用和传址调用
1. 传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。
2. 如果函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用;
3. 如果函数内部要修改主调函数中的变量的值,就需要传址调用。
传值调用
1. 实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。
#include <stdio.h> void Swap1(int x, int y) { int tmp = x; x = y; y = tmp; } int main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); printf("交换前:a=%d b=%d\n", a, b); Swap1(a, b); printf("交换后:a=%d b=%d\n", a, b); return 0; } //没有实现效果
传址调用
1. 将main函数中将a和b的地址传递给Swap函数,Swap 函数里边通过地址,间接操作main函数中的a和b,达到交换的效果。
#include <stdio.h> void Swap2(int* px, int* py) { int tmp = 0; tmp = *px; *px = *py; *py = tmp; } int main() { int a = 0; int b = 0; scanf("%d %d", &a, &b); printf("交换前:a=%d b=%d\n", a, b); Swap2(&a, &b); printf("交换后:a=%d b=%d\n", a, b); return 0; }
致谢
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!