爱上C语言:指针很难?来来来,看看这篇(基础篇)

简介: 爱上C语言:指针很难?来来来,看看这篇(基础篇)

🚀前言

大家好啊😉!今天阿辉将为大家介绍C语言的指针部分,包括认识指针、指针变量、指针运算、const关键字、assert断言以及二级指针,关注阿辉不迷路哦 😘 ,内容干货满满😋,接下来就跟着阿辉一起学习吧👊

🚀认识指针

在讲指针前,我们先了解一下内存

我们在购买电脑前都会很在意电脑的内存大小

内存是计算机存储数据的一种方式,它可以存储正在运行的程序和数据

如果内存容量太小,会导致计算机不能同时运行多个程序或运行大型应用程序时速度变慢。因此,在一定程度上,内存越大可以提高计算机的性能和运行速度

现在电脑普遍内存大小为4GB、8GB和16GB几种,这么大的内存,我们怎么样精确的找到我们想要的数据呢,这时智慧的科学家就把每个字节(byte也就是8个bit位)大小的空间都编上号,通过编号来精确定位,而这个编号就是指针也就是地址

  • 我们习惯上不说指针而是地址,通常口头上说的指针指的是指针变量

针指变量又是什么?别慌我们接着看👊

🚀指针变量

既然是变量,那它就也有类型,只不过指针变量存的是地址罢了

✈️指针变量的创建

type * p;
p是指针变量的名字
这里的type指的是所指向的数据类型是type
type可以是int、char、float、double、int[] ....
而这颗 * 表示p是指针
type*这俩一起表示p的类型

不管什么类型的指针,指针都是用来存放地址的,而指针变量开辟空间的大小也取决于地址

  • 32位平台下地址是32个bit位,指针变量大小是4个字节
  • 64位平台下地址是64个bit位,指针变量大小是8个字节
  • 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

那么指针的类型有什么用呢,别急我们接着看👇

✈️取地址操作符(&)

🌰栗子

int main()
{
  int a = 10;
  &a;
  return 0 ;
}

取地址操作符(&) 取出的是数据存储的在低地址位置的地址,因为很多数据并非一个字节,如short、int、doubt等并不需要取出每个字节的地址

上述代码中&a的地址就是0x00BFFC44

✈️解引用操作符(*)

我们知道指针是用来存储地址的,那指针到底有什么用呢?

其实指针可以通过解引用操作,由其存储的地址找到所指向的内存空间,从而改变该块空间的内容

🌰栗子

上述例子中取出a的地址赋给指针p,然后通过对p解引用操作找到a*p就等价于a*p = 1也就是a = 1所以打印出来的a的值为1

解引用操作与取地址操作互为反操作

🚀指针运算

✈️指针变量加减整数

跟字面意思一样,就是指针变量加上或者减去一个整数

ptr + n;
ptr - n;
ptr是指针变量,n为整数

那它有什么用呢?这里就与指针的类型有关了,指针的类型决定了指针加减整数跳过的空间大小

🌰栗子

int main()
{
  int a = 0;
  char c = 0;
  double d = 0.0;
  int* pa = &a;
  char* pc = &c;
  double* pd = &d;
  printf("pa = %p\n", pa);
  printf("pa + 1= %p\n", pa + 1);
  printf("pc = %p\n", pc);
  printf("pc + 1= %p\n", pc + 1);
  printf("pd = %p\n", pd);
  printf("pd + 1= %p\n", pd + 1);
  return 0;
}

上述图中我们可以看到,int*类型指针跳过4个字节,char*类型指针跳过一个字节而double*类型指针跳过8个字节(加减整数就是跳过该整数倍类型的空间大小,比如int*就是跳过8个字节)

结论:指针的类型决定了指针向前或向后移动一步的距离

✈️指针减指针

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

🌰栗子

切记指针与指针相减俩指针必须是同一块空间

✈️指针间关系运算

//指针的关系运算
#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;
}

✈️野指针

我们创建变量都会去给变量初始化,否则变量里面存的就是随机值,而如果指针创建没有初始化会造成野指针,野指针会导致程序崩溃或者产生难以调试的bug

导致野指针的几种情况

  • 指针未初始化
  • 指针越界或者非法访问
  • 指针所指向的空间已释放

🌰栗子

#include <stdio.h>
int main()
{
  //指针未初始化
  int* p1;//指针未初始化
  *p1 = 5;//在不知道指针指向的空间在哪时,强行改变指向空间的地址
  //指针越界访问
  int arr[5] = { 1,2,3,4,5 };
  int i = 0;
  int *p2 = arr;//p指向数组的首地址
  for (i = 0; i <= 10; i++)
  {
    printf("%d\n", *(p2++));
  }
  //当p2指向的空间超过数组的最后一个元素时,指针就非法访问,即越界访问了未知空间
  //指向的空间被释放了
  int j = 0;
  for (j = 0; j < 5; j++)
  {
    int a = 5;
    a += 1;
  }
  int* p3 = &a;//当循环结束之后,局部变量a向内存申请的空间就被释放了。
  //此时p3指针指向的空间就是已经被释放掉的空间
  return 0;
}

我们可以通过以下几种方式来规避野指针

  1. 指针初始化时明确指向的内容,不明确时设置为空指针(NULL)
  2. 小心指针越界:使用指针是,仔细检查,指针是否会有越界的情况发生
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性

🚀const关键字

当变量被const修饰后,该变量就具有了常属性,本质依然是变量但是不能被修改

🌰栗子

如上图,在vs2022编译器上变量被const修饰后,如果变量在赋值操作符左边会直接报错

我们来看看一种神奇的操作👇

a虽然被const修饰了,但是照样被改了

我们发现我们不能直接改a的值,但是我们可以通过指针间接改掉a的值

我们也可以给指针也用const修饰,但是const修饰指针复杂了一丢丢,我直接上代码👊

int main()
{
  const int a = 0,b = 0,c = 0;
  下面这两种const都在*左边表示不能通过解引用操作更改所指向空间的内容
  但是可以把p1和p2内存的地址给改了
  const int* p1 = &a;
  int const* p2 = &b;
  下面这种表示const在*右边表示不能更改p3的值,就是不能把p3内存的地址给改了
  但是可以通过解引用操作更改c的值
  int* const p3 = &c;
  return 0;
}

结论:const修饰指针变量的时候

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

🚀assert断言

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

例如:

assert(p != NULL);

上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示

assert()的好处:

  • 能自动标识文件和出问题的行号
  • 如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前面,定义⼀个宏 NDEBUG

例如:

#define NDEBUG
#include <assert.h>

assert() 是一种在程序中插入的断言,用于在运行时检查特定条件是否为真。其缺点之一是引入额外的检查,可能会增加程序的运行时间。在开发过程中,可以在调试版本中使用 assert() 来帮助程序员排查问题,而在发布版本中禁用 assert() 来提高程序的效率。在一些集成开发环境中,如Visual Studio,发布版本中通常会自动优化掉 assert()。因此,使用 assert() 需要在调试和发布版本之间进行适当的管理,以兼顾程序员的开发需求和最终用户的使用效率。

🚀二级指针

二级指针就是存储指针变量地址的指针

int a = 1![请添加图片描述](https://ucc.alicdn.com/images/user-upload-01/d52ded127def45478ddafeabedd09966.png)
0;
int * p = &a;
int* * pp = &p;
两颗*指的是pp是一个二级指针
左边的int*是pp所指向的变量的类型是int*
int* *是pp的类型
*(pp)等价于p
*(*(pp))等价于a

图解

不仅有二级指针还有三级、四级、五级指针等等,不过一般最多用到三级指针,更高的用不上


到这里,阿辉今天关于指针的基础知识分享就结束了,不过这一篇仅仅是深入指针的基础,希望这篇博客能让大家有所收获, 如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力🌹

相关文章
|
18小时前
|
存储 编译器 程序员
从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针(下)
从C语言到C++④(第二章_类和对象_上篇)->类->封装->this指针
4 0
|
1天前
|
存储 编译器 C语言
C语言进阶⑱(文件上篇)(动态通讯录写入文件)(文件指针+IO流+八个输入输出函数)fopen+fclose(下)
C语言进阶⑱(文件上篇)(动态通讯录写入文件)(文件指针+IO流+八个输入输出函数)fopen+fclose
7 0
|
1天前
|
C语言
C语言进阶⑱(文件上篇)(动态通讯录写入文件)(文件指针+IO流+八个输入输出函数)fopen+fclose(中)
C语言进阶⑱(文件上篇)(动态通讯录写入文件)(文件指针+IO流+八个输入输出函数)fopen+fclose
8 0
|
1天前
|
存储 数据库 C语言
C语言进阶⑱(文件上篇)(动态通讯录写入文件)(文件指针+IO流+八个输入输出函数)fopen+fclose(上)
C语言进阶⑱(文件上篇)(动态通讯录写入文件)(文件指针+IO流+八个输入输出函数)fopen+fclose
8 0
|
1天前
|
C语言
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror(下)
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror
5 0
|
1天前
|
安全 C语言
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror(中)
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror
10 0
|
1天前
|
C语言
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror(上)
C语言进阶⑬(字符串函数)+(指针编程题)strlen+strcpy+strcat+strstr+strtok+strerror
9 0
|
1天前
|
算法 C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(下)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
5 0
|
1天前
|
C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(中)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
11 0
|
1天前
|
C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(上)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
11 0