初识C语言 指针篇(初阶)

 指针是什么?

指针是c语言中的一个重要概念,也是C语言的一个重要的特色,正确而灵活地运用它,可以使程序简洁,紧凑,高效,每一个学习和使用c语言的人,都应当深入了解地学习和掌握指针,可以说,不掌握指针就是没有掌握C的精华也可以说

指针是C语言的灵魂

 在我们的生活中就有很多案例,比如324指向了一个宿舍号,那么324就是这个房间的地址,或者说324指向了该房间,就可以将地址形象化为“指针”,意思是可以通过它来找到对应地址的内容。

指针变量

 当我们通过&拿到一个地址时,需要将这个地址进行存储用于后期的使用,那么这个地址存在什么地方呢?答案是指针变量中。

#include <stdio.h>
int main()
{
int a = 10;
&a;
printf("%p\n",&a);
return 0;
}

比如在上述代码中,就是创建了整形变量a,内存中申请了4个字节,用于存放整型数字10,每个字节都有地址。上述代码中4个字节的地址分别是:

0x006FFD70
0x006FFD71
0x006FFD72
0x006FFD73

 在上述代码中使用了&,&a,打印的是0x006FFD70,我们知道了第一个字节的地址,int 类型占四个字节,顺藤摸瓜也能访问到4个字节的数据。

#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a; //取出a的地址并存储到指针变量pa中
*pa=0;
return 0;
}

int *为指针变量的类型,指针变量也是一种变量,这种变量是用来存放地址的,存放在指针变量中的值通常被称为地址。

那该如何理解指针变量呢? 

*表示pa是个指针变量,而前面的int表示pa指向的类型为int类型的对象。

 解引用操作符*  *pa是通过pa中存放的地址找到对应的内容,所以*pa就是a变量,所以*pa=0,这个操作符是把a改成了0。

可以理解为用pa来进行修改a的值,而不是直接修改a,多了一种途径,写代码就会更加灵活。

 指针变量的大小

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}

32位平台下地址是32个bit位,指针变量大小是4个字节

64位平台下地址是64个bit位,指针变量大小是8个字节

指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

 在同一平台下,指针类型不同,但是大小都是一样的,那么为什么要有不同的指针类型呢?

观察下面两个代码并想想输出的结果

//代码1
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
printf("%d\n",n);
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
printf("%d\n",n);
return 0;
}

上面代码1中会将4个字节全改为0,代码2中只是将最后一个字节改为0.

 

 

 

 代码1   通过监视发现代码1中int*会改变4个字节。

 代码2   char*会改变1个字节。

 结论 指针的类型决定了,对指针解引用能改变几个字节。

 指针+-整数

#include <stdio.h>
int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;
	printf("&n=%p\n", &n);
	printf("pc=%p\n", pc);
	printf("pc+1=%p\n", pc + 1);
	printf("pi=%p\n", pi);
	printf("pi+1=%p\n", pi + 1);
	return 0;
}

查看一下代码的运行结果:

 可以看出char* +1只改变1个字节,int*+1改变了4个字节。

所以说指针的类型决定了指针向前或者向后走一步有多大(多少字节)。

void* 指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是 void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算。

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	char* pc = &a;
	return 0;
}

 在上述代码中 将一个int类型的变量的地址赋值给char*类型的指针变量时,会显示类型不兼容的警告。

在使用void*接受不同类型的地址,但是 void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算。

#include <stdio.h>
int main()
{
	int a = 10;
	void* pa = &a;
	void* pc = &a;
	*pa = 10;
	*pc = 0;
	return 0;
}

 那么void*是有什么用呢?

void*类型的指针一般用于函数参数的部分,用于接收不同类型的地址。可以使一个函数可以处理多种类型的数据。

 const修饰指针

变量是可以修改的,如果把变量的地址交给一个指针变量,通过这个指针变量也可以修改这个变量。如果希望给这个变量一些限制,就可以用const修饰指针。

#include <stdio.h>
int main()
{
	int m = 0;
	m = 20; //m是可以修改的
	const int n = 0;
	n = 20; //n是不能被修改的
	return 0;
}
#include <stdio.h>
int main()
{
	const int n = 0;
	printf("n = %d\n", n);
	int* p = &n;
	*p = 20;
	printf("n = %d\n", n);
	return 0;
}

 使用const修饰int n,n不能直接进行修改。将n的地址取出,使用指针变量修改n就可以修改n的值。

#include <stdio.h>
//代码1
void test1()
{
	int n = 10;
	int m = 20;
	int* p = &n;
	*p = 20; //ok?
	p = &m; //ok?
}
void test2()
{
	//代码2
	int n = 10;
	int m = 20;
	const int* p = &n;
	*p = 20; //ok?
	p = &m; //ok?
}
void test3()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20; //ok?
	p = &m; //ok?
}
void test4()
{
	int n = 10;
	int m = 20;
	int const* const p = &n;
	*p = 20; //ok?
	p = &m; //ok?
}
int main()
{
	//测试⽆const修饰的情况
	test1();
	//测试const放在*的左边情况
	test2();
	//测试const放在*的右边情况
	test3();
	//测试*的左右两边都有const
	test4();
	return 0;
}

结论  const修饰指针的时侯。

  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本⾝的内容可变。
  • const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

 指针的运算

指针的运算分为三种:  

指针+-整数

指针+-指针

指针的关系运算

 数组在内存中是连续存放的,只要知道了一个地址,就能找到后面的全部元素。

在打印的时候就是用了这个方法。

#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 这⾥就是指针+整数  *(p+i)等价于p[i]
	}
	return 0;
}

 指针-指针

//指针-指针
#include <stdio.h>
int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')//对p这个指针变量解引用不为\0就检测下一个字符
		p++;
	return p - s;
}
int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

 上述代码为模拟实现strlen函数的指针方法,复习一下strlen函数是计算字符长度的函数,是到\0进行截至。

 指针的关系运算

//指针的关系运算
#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]);
	while (p < arr + sz) //指针的⼤⼩⽐较
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

 上述代码中arr为首元素的地址加上sz,通过while循环指针大小比较进行打印,p++转到下一个字符。

野指针

野指针就是指针指向的位置是不可知的

 野指针成因  包括

1.指针未初始化

2.指针越界访问

3.指针指向的空间释放

#include <stdio.h>
int main()
{
	int* 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++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}
#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();//出了函数,函数中的空间会释放
	printf("%d\n", *p);
	return 0;
}

 那么如何规避野指针呢?

1.指针初始化

2.小心指针越界访问

3.指针变量不再使用时,置NULL,指针使用之前检查有效性(是否未NULL)

 NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

 assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。

assert(p!=NULL)

 上面代码在验证p是否等于NULL,如果不等于就继续运行代码,如果等于就终止运行,并给出报错信息提示。

#define NDEBUG  //这个时宏定义  是assert断言函数的开关  加上就会禁用文件中所有的assert语句。
#include <assert.h>

 注意:assert断言只能在debug版本下使用

本节主要介绍了指针以及指针变量中的一部分初级需要了解的内容,还有别的内容请听下回分解。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值