AutoLeaaders控制组——C语言指针学习笔记

本文介绍了C语言中地址的概念,如何使用取地址符获取内存地址,指针的声明、解引用和操作,包括指针与函数、数组的关系,动态内存分配(malloc和free),以及注意事项如野指针和0地址的使用。
摘要由CSDN通过智能技术生成

AutoLeaaders控制组——C语言指针学习笔记

一、地址

在计算机运行时,数据会存放在内存中,内存会以字节为单位划分为多个存储空间,并且为每个字节默认设置一个对应的编号,这个编号就是地址,为十六进制数。

地址只是计算机规定的一个值,所以不会占用内存的存储空间,地址显示的长度会根据系统及编译器的位数确定。64位编译器显示的地址为16个16进制数,32位编译器显示的地址为8个16进制数。

二、取地址符&

在C语言中,使用取地址符&可以获取内存中数据的地址。
同样的数据在每次运算时存放的地址可能会产生变化。
%p占位符指定输出的数据为地址,不可以用%x占位符代替。

例:

int main()
{
	int a = 10;
	printf("&a=%p", &a);
	return 0;
}

在这里插入图片描述
000000689A5EF5C4就是变量a的地址

三、指针

指针 就是保存地址的变量。
普通变量 的值是实际的值
指针变量 的值是具有实际值的变量的地址。

1、声明指针

type *name ;
type:类型
name:指针变量名称

举例:

int main()
{
	int a = 1;
	int *pa;//*pa即为指针
	pa = &a;
	return 0;
}

这样就声明了一个指针*pa

同时声明两个指针应该 int *a, b;
而不是 int
a, b;

2、解引用操作符 *

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做左值,也可以做右值
  • 例:int k = *p; *p = k + 1;

例子:

int main(){
	int a = 1;
	int* pa = &a;//pa存放a的地址
	printf("&a = %p\n", &a);
	printf("pa = %p\n", pa);
	printf("a = %d\n", a);
	printf("*pa = %d\n", *pa);
	*pa = 2;
	printf("a = %d\n", *pa);
	return 0;
}

在这里插入图片描述

通过解引用符*,可以访问到指针中地址所指的变量,并对对应变量进行操作。

3、指针与函数

  1. 交换两个变量的值
void swap(int *pa, int *pb) {
	int t = *pa;
	*pa = *pb;
	*pb = t;
}	

int main() {
	int a = 1, b = 2;
	printf("a=%d, b=%d\n", a, b);
	swap(&a, &b);
	printf("a=%d, b=%d\n", a, b);
	return 0;
}

在这里插入图片描述

通过指针,在函数里面可以访问到外面的变量,从而可以写出一个交换数值的函数

  1. 函数返回多个值,某些值就只能通过指针返回;
    传入的参数实际上是需要保存带回的结果的变量;
void minmax(int a[], int len, int* min, int* max);

int main() {
	int a[] = { 4,45,23,12,9,12,30,38 };
	int min, max;
	minmax(a, sizeof(a) / sizeof(a[0]), &min, &max);
	printf("min = %d, max = %d\n", min, max);
	return 0;
}

void minmax(int a[], int len, int* min, int* max)
{
	*min = *max = a[0];
	for (int i = 0; i < len; i++) {
		if (a[i] < *min) {
			*min = a[i];
		}
		if (a[i] > *max) {
			*max = a[i];
		}
	}
}

在这里插入图片描述
通过指针,返回最大值与最小值两个值

  1. 函数返回运算的状态,结果通过指针返回;
    常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错;
    -1或0(在文件操作会看到大量的例子);
    但是当任何数值都是有效的可能结果时,就得分开返回;
int divide(int a, int b, int* result);

int main() 
{
	int a, b;
	scanf("%d %d", &a, &b);
	int c;
	if (divide(a, b, &c)) {
		printf("%d/%d=%d\n", a, b, c);
	}
	return 0;
}

int divide(int a, int b, int* result) {
	int ret = 1;
	if (b == 0) ret = 0;
	else *result = a / b;
	return ret;
}

分母不为0:
在这里插入图片描述
分母为0:
在这里插入图片描述

如果分母为0,那么返回值为0,判断为假,不输出结果;分母为0,返回值不为0,判断为真,则输出结果。
结果便通过指针存在变量c中返回;

4、指针与数组

例一

void f(int a[]) {
	printf("sizeof(a) = %d\n", sizeof(a));
	printf("*a = %d\n", *a);
}
int main()
{
	int a[] = { 1,2,3,4 };
	printf("sizeof(a) = %d\n", sizeof(a));
	f(a);
	printf("sizeof(int*) = %d", sizeof(int*));
	return 0;
}

在这里插入图片描述
由此可知,sizeof(a) = sizeof(;
函数参数表上的数组实际上是指针;
因此 void f(int a[]) 与 void f(int *a) 是等价的。

例二

void f(int *a) {
	printf("*a = %d\n", *a);
	printf("a[0] = %d\n", a[0]);
	printf("a[1] = %d\n", a[1]);
}
int main()
{
	int a[] = { 1,2,3,4 };
	f(a);
	return 0;
}

在这里插入图片描述
由此可知,虽然传入函数的是一个指针,但是还是可以用数组的运算符[]进行计算,且传入的指针指向数组第一个数的地址。

数组变量是特殊的指针

  • 数组变量本身表达地址,所以
  • int a[10];int *p = a;//无需用&取地址
  • 但是数组的单元表达的是变量,需要用&取地址
  • a == &a[0]

例三

int main()
{
	int a = 10;
	int* pa = &a;
	printf("*pa=%d\n", *pa);
	printf("pa[0]=%d\n", pa[0]);
	return 0;
}

在这里插入图片描述

  • []运算符可以对数组做,也可以对指针做
  • 数组变量是const的指针,也就是 int b[] <==> int* const b,所以不能赋值。

5、指针与const

  1. 指针是const
  • 表示一旦得到了某个变量的地址,不能再指向其他变量
int a = 10, b = 20;
int *const p = &a;//q是const
*p = 26;//OK
p = &b;//ERROR
  1. 所指是const
int a = 10, b = 20;
const int *p = &a;//(*p) 是const
p = &b;//OK
*p = 30;//ERROR

判断哪个被const了的标志是const在*的前面还是后面

  1. 转化
  • 总是可以把一个非const的值转换成const的
void f(const int *x);
int a = 10;//a是非const
f(&a);//OK
const int b = a;//转化为const
f(&b);//OK
b = a + 1;//ERROR,因为b是const
  • 当要传递的餐胡的类型比地址打的时候(如:结构体),这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改
  1. const数组
  • const int a[] = {1,2,3,4,5,6};
  • 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int,所以必须通过初始化进行赋值。
  1. 保护数组值
  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
  • 为了保护数组不被函数破坏,可以设置参数为const
  • 如:int sum(const int a[], int length);

6、指针运算

例一

int main()
{
	char c[] = { 0,1,2,3,4 };
	char* pc = c;
	printf("pc = %p\n", pc);
	printf("pc + 1 = %p\n", pc + 1);
	int i[] = { 0,1,2,3,4 };
	int* pi = i;
	printf("pi = %p\n", pi);
	printf("pi + 1 = %p\n", pi + 1);
	return 0;
}

在这里插入图片描述
为什么两种类型的指针加1,而char的地址加1,int的地址加4呢?
想到sizeof(char) = 1,sizeof(int) = 4;
由此可以得出 指针加1,地址加的是对应类型的大小

例二

int main()
{
	int a[] = { 1,2,3,4 };
	int* p = a;
	printf("a[0]=%d, *p=%d\n", a[0], *p);
	printf("a[1]=%d, *(p+1)=%d\n", a[1], *(p + 1));
	printf("a[2]=%d, *(p+2)=%d\n", a[2], *(p + 2));
	return 0;
}

在这里插入图片描述

由上面结论可知
*p -> a[0] //*p指向a[0]
*(p + 1) -> a[1]
*(p + 2) -> a[2]

所以可以得出,给一个指针加i,表示要让指针指向下i个变量;同理,给一个指针减i,表示要让指针指向上i个变量
但,如果指针不是指向一片连续分配的空间(如:数组),则这种运算没有意义。

例三

int main()
{
	int a[] = { 1,2,3,4,5 };
	int b[] = { 6,7,8,9,10 };
	int* p = a, * q = b;
	printf("p=%p\n", p);
	printf("q=%p\n", q);
	printf("q - p = %d\n", q - p);
	return 0;
}

在这里插入图片描述
两个指针相减表示 指针所指的地址之间还能存放几个该类型的数

结论

  • 可以给指针加,减一个整数(+,+=,-,-=)
  • 递增递减(++,–)
  • 两个指针相减

“p++”:

  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  • *的优先级虽然高,但是没有++高
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令

指针比较

  • <, >, <=, >=, != 都可以对指针操作
  • 可以比较它们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的

7、指针的类型

  • 无论只想什么类型,所有的指针的大小都是一样的,因为都是地址
  • 但是指向不同类型的指针时不能直接互相复制的
  • 这是为了避免用错指针

8、指针的类型转化

  • void* 表示不知道指向什么东西的指针
  • 计算时与char*相同(但不相通)
  • 指针也可以转化类型
    int p = &i; void q = (void*)p;
  • 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量

四、动态内存分配

malloc函数

#include <stdlib.h>
声明:void* malloc(size_t size);

  • 向malloc申请的空间的大小是以字节为单位的
  • 返回的结果是void*,需要类型转化为自己需要的类型
  • 格式:(int*)malloc(n*sizeof(int))

type* name = (type*)malloc(n*sizeof(type));
type:自己需要的指针类型
name:分配空间到的指针名
n:需要的空间数量

free函数

  • 把申请的来的空间还给’系统‘
  • 申请过的空间,最终都应该要还
  • 只能还申请来的空间的首地址

例:

int main()
{
	int* p;
	int n;
	scanf("%d", &n);
	p = (int*)malloc(n * sizeof(int));
	for (int i = 0; i < n; i++) {
		p[i] = i;
		printf("%d\n", p[i]);
	}
	free(p);
	return 0;
}

在这里插入图片描述

常见问题
  1. 申请了没free——>长时间运行内存逐渐下降
  2. free过了再free
  3. 地址变过了,直接去free

五、注意:

野指针

概念:野指针就是指向的内存地址是未知的(随机的,不正确的,没有明确限制的)。
说明:指针变量也是变量,是变量就可以任意赋值。但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。
注:野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。

int a = 10;
int *p;
*p = a;			//无意义
p = 0x1145114;	//无意义
*p = 20;		//出错

因为*p并没有定义指向任何变量,因此p存的地址是随机的。
若再对其所指的内存空间赋值,那么会将20写在不该写的地方而出错。
解决方法:声明指针是先初始化,即 int *p = NULL;

0地址

  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 所以指针不应该具有0值
  • 因此可以用0地址来表示特殊的事情:
  • 返回的指针是无效的
  • 指针没有被真正初始化(先初始化为0)
  • NULL是一个预定定义的符号,表示0地址
  • 有的编译器不愿意你用0来表示0地址
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值