C语言基础 —— 指针

前言

学习 STM32 需要一些 C语言基础,其中 结构体 和 指针尤其重要,我们接下来我们就来学习一下 指针

——————————————————————————————————————————

一. 指针是什么?

 1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

指针其实就是地址,而我们平时说的指针,指的就是指针变量,我们也可以理解为: 内存

 内存单元  -  编号  -  地址  -  指针 (这四个其实就是一回事)

而我们的指针变量: 我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以放                                     到一个变量中

看一个例子

#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *ps = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
printf("%d\n",sizeof(pa));
return 0;
}

但要注意的是,指针在32位平台上是4个字节,而在64位平台是8个字节

____________________________________________________________________________

二.指针与指针类型

我们都知道,变量有不同的类型,整形,浮点型等。指针也有这些类型

#include <stdio.h>
int main()
{
	char* pc;
	int* pa;
	double* pd;
	printf("%d\n", sizeof(pc));
	printf("%d\n", sizeof(pa));
	printf("%d\n", sizeof(pd));
	return 0;	
}

————————————

我们会发现这些打印出来的都是4个字节(不管什么指针类型的大小都是一样的)

既然大小都一样,那我们是不是随便用一个指针类型就可以了呢?答案明显是否定的

————————————

2.1 指针类型的意义——指针加减整数

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

指针的类型决定了指针  向前  或者  向后 一步的距离有多大  int一下跳了4个字节,但是 char 只跳了一个字节

——————————

2.2 指针的解引用

#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;  //重点在调试的过程中观察内存的变化。
return 0;
}

这样子我们会发现 a 的地址会变成 n 的 44 33 22 11(倒着放进去的),但是我们的 pi ,他的地址就变成 00 00 00 00 了,我们改变了他的4个字节

______________________

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

但是在这里,我们的 pc 的地址,只有一个变成了 00 ,其他并没有变化,我们只改变了一个字节

所以——指针的类型就决定了解引用的时候一次能访问几个字节

————————————

我们接下来使用只能来访问一个数组

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//倒着打印
	int *q = &arr[9]
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *q)
			q--;
	}
	return 0	
}

只要我们拿到了地址,我们就可以把他轻松拿出来了

在上面的例子中  int*p = arr,是从数组的首元素的地址开始的,但是 int *q = arr[9]是从地址的最后一个元素开始的,要注意的是,我们的数组的地址 是从 下标 0 开始的 

————————————————————————————————————————

3. 野指针

指针的指向是不可知的

————————————————

3.1 野指针成因

1.指针未初始化

#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
  *p = 20;
return 0;
}

如果不给他赋值,编译器会随机给他一个值,编译器也会报错 

2.指针越界访问

#include <stdio.h>
int main()
{
  int arr[10] = {0};
  int *p = arr;
  int i = 0;
  for(i=0; i<=11; i++)
 {
    //当指针指向的范围超出数组arr的范围时,p就是野指针
    *(p++) = i;
 }
  return 0;
}

比较需要注意的是,我们的指针赋值填到地址上,也就是从我们的下标 0 开始,arr[10]一共就是 10个元素 (下标从 0 到 9),但是我们这边却又11个元素,是越界的,就变成野指针了  

3. 指针指向的空间释放

——————————

3.2 如何避免野指针

1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性

————————

我接下来给出一个例子 

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

当我们不知道 *p  取的到底是谁的地址,那我们最好的办法就是给他赋一个空指针

#include <stdio.h>
int main()
{
  int *p = NULL;
  //....
  int a = 10;
  p = &a;
  if(p != NULL)
 {
    *p = 20;
 }
  return 0;
}

     同时我们还要注意 指针的有效区间,如果指针是空(NULL)的,我们就不要使用这个指针

————————————————————————————————————————

4. 指针的运算

1.   指针   +-    整数
2.   指针   -      指针
3.   指针的关系运算

——————————————

4.1 指针 +- 整数

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr; // 指向首元素地址
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i  
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

我们理解一下, 我们每次循环 都相当于 把 arr 数组里的元素 i 用我们的 指针 p+i 给取出来(相当于 p++) , (p+i),p的值是不变,但 i 一直在变

————————————————

4.2 指针 - 指针

指针 + 指针 是没有意义的

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%d\n", &a[9] - &a[0]);
	printf("%d\n", &a[0] - &a[9]);
	return 0;
}

最后的出的答案会是 9 与 -9

指针 - 指针的前提是 : 两个指针指向同一空间 

指针 - 指针得到的是 : 两个指针之间的元素个数

——————————————————

在说之前,我们来看一个函数

int main()
{
	// 求字符串长度的函数
	int len = strlen("abc");
	printf("%d\n", len);
	return 0;
}
//1.计数器的方法
int my_strlen(char* s)
{
	int count = 0;
	while  (*s != '\0')
	{
		count++;
		s++;
	}
	return count;
}
//2. 指针 - 指针
int my_strlen(char* s)
{
	char* start = s;
	while (*s != '\0')
	{
		s++;
	}
	return s - start;
}

上面我们就有了用指针 - 指针 来代替这个函数

——————————————

4.3 指针的关系运算

#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp = &values[N_VALUES]; vp > &values[0];)
{
  *--vp = 0;
}

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

————————————————————————————-————————

5. 指针数组

我们来看一个例子

#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
  printf("%p\n", arr);
  printf("%p\n", &arr[0]);
  return 0;
}

接下来我们看看他的出的结果

所以我们可以得出 数组名表示的是数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能

#include <stdio.h>
int main()
{
  int arr[] = {1,2,3,4,5,6,7,8,9,0};
  int *p = arr; //指针存放数组首元素的地址
  int sz = sizeof(arr)/sizeof(arr[0]);
  for(i=0; i<sz; i++)
 {
    printf("&arr[%d] = %p  <====> p+%d = %p\n", i, &arr[i], i, p+i);
 }
  return 0;
}

而下面是我们运行的结果

所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组

int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}

——————————————————————————————————————————

6 .二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在 二级指针

 我们 *ppa 其实就是访问我们的 pa

int b = 20;
*ppa = &b;//等价于 pa = &b;

————————————————————————————————————

7. 指针数组

说白了 ,指针数组,其实就是存放指针的数组

例如

int* arr3[5];

arr3是一个数组,有五个元素,每个元素是一个整形指针

而这 就是我们的指针数组

int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	
	int** arr2[4];

	//int* arr[10];  //指针数组-存放的是int*
	//char* ch[5];   //指针数组-存放的是char*
	return 0;
}

——————————————————————————————————————————

指针进阶

-————————————

一 · 数组指针

指向 数组的指针

	//指向字符的指针
	char ch = 'a';
	char* p = &ch;
	//指向整型的指针
	int* p;
    int* p1[10];//指针数组


	int(*p2)[10];//数组指针
   //p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
   //指针,指向一个数组,叫数组指针

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
——————————————

二·&数组名VS数组名

猜你喜欢

转载自blog.csdn.net/ArtoriaLili/article/details/122540014
今日推荐