C语言初阶之指针

1.指针是什么

指针理解的两个要点:

1.指针是内存中一个最小单元的编号,也就是地址。

2.口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结:编号 == 地址 ==指针

指针变量:通过取地址操作符(&)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。 

int main()
{
	int a = 10;//在内存中开辟一块空间
	int* pa = &a;//取出变量a的地址,可以使用&操作符
	//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址放在pa变量中
	//pa是专门用来存放地址(指针)的,这里的pa就被称为指针变量。
	//pa的类型是int *
	//int表示pa指向的a类型是int
	//*表示pa是指针

	return 0;
}

内存的产生: 

  • 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
  • 在64位的机器上,地址是64个0或者1组成二进制序列,那地址就得用8个字节的空间来存储,所以一个指针变量的大小就应该是8个字节。

总结:

  • 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
  • 指针的大小在32位平台是4个字节,在64位平台上是8个字节。

2.指针和指针类型

2.1指针类型所占字节大小在同一平台下是固定的。

  •    在32位机器下均占4个字节。
  •    在64位机器下均占8个字节。

2.2指针类型的意义

2.1指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)。

type *  p

type : 1.p 指向的对象的类型 2.p 解引用的时候访问的对象的大小是sizeof(type)

* :说明p 是指针变量

  •         int * 的指针解引用访问4个字节
  •         char * 的指针解引用访问1个字节

2.2 指针类型决定指针进行+/-1操作时的步长。

type *  p

+/-n    跳过的是:n*sizeof(type)这么多个字节

 

  •         int * 的指针+1跳过4个字节
  •         char * 的指针+1跳过1个字节 

3.野指针

 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

3.1野指针的成因

3.1.1指针未初始化
int main()
{
	int* p;//局部变量不初始化的时候,内容是随机值
	//p被称为野指针
	*p = 20;
	printf("%d\n", *p);
	return 0;
}
3.1.2 指针越界
int main()
{
	int arr[10] = { 0 };
	int * p = arr;// 数组名表是首元素地址,p指向第一个元素
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		//当指针指向的范围超出数组arr的范围时,p就是野指针
		*(p++) = i;
	}
	return 0;
}
3.1.3 指针所指向的空间释放

3.2 如何规避野指针

3.2.1指针初始化

1.明确知道指针应该初始化为谁的地址,就直接初始化

2.不知道指针初始化为什么值,暂时初始化为NULL 

int main()
{
	int a = 10;
	int* p = &a;//初始化指针

	int* ptr = NULL;//ptr是一个空指针,没有指向任何有效的空间。这个指针不能直接使用

	return 0; 
}
3.2.2小心指针越界
int main()
{
	int arr[10] = { 0 };
	int * p = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++) = i;
	}
	return 0;
}
3.2.3指针指向空间释放,及时置NULL
int* ptr = NULL;
3.2.4避免返回局部变量的地址
int* test()
{
	int a = 110;
	return &a;
}
3.2.5指针使用之前检查有效性
if (ptr != NULL)
	{
		//使用ptr所指向的空间
	} 

4.指针运算

4.1指针+-整数(指针加减整数)

int main()
{
	int arr[10] = { 0 };
	//不使用下标访问数组
	int * p = &arr[0];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	//使用地址给数组赋值
	//法一:p在变
	for (i = 0; i < sz; i++)
	{
		*p = i;
		p++;
	}
	//法二:p不变
	p = arr;
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i;//*解引用就是访问数组第p+i个元素
	}
}

4.2指针-指针(指针减指针)

指针-指针得到的数值的绝对值是指针和指针之间的元素个数。

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &arr[9] - &arr[0]);
	printf("%d\n", &arr[0] - &arr[9]);
	return 0;
}

两个指针相减运算的前提条件是:指针和指针指向了同一块空间。


int main()
{
	int arr[10] = { 0 };
	char ch[5] = { 0 };
	printf("%d\n", &ch[4] - &arr[9]);//所指空间不相同,相减无意义
}

 经典例题:

计算字符数组中元素个数

//指针-指针(指针减指针)
//版本一:
int my_strlen(char* s)
{
	char* start = s;
	while (*s != '\0')
	{
		s++;
	}
	return s - start;
}
//版本2:
int my_strlen(char* s)
{
	char* start = s;
	while (*s++);
	return s - start-1;
}
int main()
{
	char arr[] = "abcdef";
	int len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

4.3指针的关系运算(即比大小)

地址是有大小的,指针的关系运算就是比较指针的大小

int main()
{
	float arr[5] = { 0 };
	float* p = NULL;
	//倒着初始化数组内容
	for (p = &arr[5]; p > &arr[0];)
	{
		*--p = 0;
	}
	return 0;
}

 简化一下代码:

int main()
{
	float arr[5] = { 0 };
	float* p = NULL;
	for (p = &arr[4]; p >= &arr[0]; p--)
	{
		*p = 0;
	}
	return 0;
}

 代码简化以后,绝大部分编译器是可以顺利编译的,但应该避免这样写,因为标准并不保证他可行。

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较大小,但是不允许与指向第一个元素之前的那个内存位置的指针比较大小。

5.指针和数组

指针变量就是指针变量,不是数组,指针变量的大小是4/8个字节,专门是用来存放地址的。

数组就是数组,不是指针,数组是一块连续的空间,可以存放1个或者多个类型相同的数据。

联系:数组中,数组名其实就是数组首元素的地址,数组名 == 地址 == 指针。

当我们知道数组的首元素地址时,由于数组是一块连续的空间,因此可以通过指针来遍历访问数组——数组可以通过指针来访问。

5.1利用指针打印数组地址 
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%p == %p\n",p+i, &arr[i]);
	}
	return 0;
}
  
5.2利用指针打印数组元素(解引用操作符) 
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

6.二级指针

二级指针变量就是用来存放一级指针的地址

int main()
{
	int a = 10;
	int * p = &a;//p 是一级指针变量,变量也是变量。
	//变量是在内存中开辟空间的,是变量就有地址。
	int * * pp = &p;//pp 是二级指针变量
	//二级指针变量就是用来存放一级指针的地址
	//* * 说明 pp 是指针变量
	//int 和 * * 合起来指的是 pp 指向的 p 的完整类型是 int*
	return 0;
}

7.指针数组

指针数组其实就是数组,是存放指针的数组。

整型数组——存放整型的数组

字符数组——存放字符的数组

指针数组——存放指针(地址)的数组

1.指针数组举例:

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "hello world";
	char arr3[] = "cuihua";

	char * parr[] = { arr1,arr2,arr3 };
	//parr就是指针数组
	//数组名arr1,arr2,arr3相当于首元素地址
	//要把这个三个存起来,得用指针

	return 0;
}

2.用指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			//法一:
			printf("%d ", parr[i][j]);
			//法二:
			printf("%d ", *(parr[i] + j));//使用解引用操作符
		}
		printf("\n");
	}
	return 0;
}

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

囚徒玩电脑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值