C语言基础(指针基础加进阶详解)

简介: 开头必须是我的快乐校园生活,今早咱8点30起床,吐糟一下每日英语听力,居然不开VIP,不给翻译,难受,不知那个大佬有每日英语听力的VIP呀!借小弟用一用,下午看了沈腾的新电影《独行月球》,给我的感觉很好,沈腾作为资本的宠儿就是不一样,他的电影质量在国内还是很高的,然后就睡觉,睡到了下午3点,舍友(林某人)非要与我单挑王者荣耀这个小学生游戏,那身为高手的我自然是给他一通乱打,打的他毫无还手之力。

一、今天我们就来介绍一下指针,首先我们要知道指针是C语言中的一个重要概念,它能有效地表示和处理复杂的数据结构,特别善于处理处理动态数据结构,正确灵活的使用指针,我们就可以把许多困难的问题以更加简洁、高效的的方法去实现(如动态内存的分配和从函数获得多个返回值的问题);同时如果指针不能运用恰当,就会导致一些隐蔽而严重的程序问题,所以指针就是一个非常容易引起混淆的概念,所以在我们的学习过程中我们应该要学的扎实一些。

1.扎实使用指针第一步。


(一、)所以指针是用来干嘛的呢?这边就举一个简单的例子:在一个代码中,所有的变量都是存在内存中的,不同的变量就有着不同的存储位置,然而假如我要使用这个变量的时候,我就可以通过指针来标示变量在内存中的位置,然后通过这个指针来使用这个变量;形象一点来说就是(指针和变量之间存在着类型于门牌号码和房子的关系;所以从(指针的角度)来看就是指针标示了一个内存中的某一个“位置”;然而从(变量的角度)来看就是指针标示的是变量在内存中的“位置”),所以总的来说指针的使用就有一点像是数组中(下标的使用)。但是不管到底是一个什么东西,看不明白也不怕,主要是能在代码中融会贯通的使用就行了,所以接下来请看代码:

#include<stdio.h>//这个代码的意思就是找出数组中的最大值
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int i = 0;
  int max = 0;
  int* pa = arr;//这个位置就是指针的使用,意思就是让指针指向arr这个数组的首元素
  int* pmax = arr;//同上
  for (i = 0; i <= 10; i++)
  {
    if (arr[max] < arr[i])
    {
      max = i;//这个就是结合那个if判断语句把数组中的最大值赋给max这个变量
    }
  }
  printf("最大值是:%d\n", arr[max]);
  for (i = 0; i <= 10; i++)
  { 
    if (*pmax <= *pa)//这边这个就是和上面那个if语句一个意思,但是要注意的是这边是对arr数组中的元素就行的比较(所以不能写成 if(pmax <= pa),因为如果这样写,比较的就是数组中的元素的地址(因为此时我的pa和pmax代表的是一个指针,然后它们指向的是数组中的首元素地址,所以这样比较比较的就是地址)),所以我们要进行解引用使用
    {
      pmax = pa;
      pa++;
    }
  }
  printf("最大值是:%d\n", *pmax);//且这边打印打印的是数组中的具体的一个元素,所以要进行解引用,不然就是地址而不是具体的元素
  return 0;
}

上述代码就是一个典型的指针的使用场景,但是有几个小知识点,现在给大家讲一下:

1.首先从代码可知:int* pa = arr;这步就是指针的使用,*pa代表的就是一个用来指向arr数组首元素的一个指针变量,并且 *pa 这个指针的类型是(int类型)。

这句话中有两个知识点:

(一、)第一个知识点就是,为什么指针指向的是数组arr的首元素地址而不是整个数组arr,这边就会涉及到一个千年的知识点,逢考必考(就是当一个数组摆在你眼前的时候,这个数组名代表的并不是整个数组,代表的就只是首元素的地址而已,除了唯一两个特殊的情况:1.就是&arr时;2.就是sizeof(arr)时),只有这两个情况时数组arr代表的是整个数组,其它时候代表的一律都是首元素的地址,切记!切记!,这个真的必考。

(二、)第二个知识点就是(也是非常的关键,也是经常考的东西),指针的类型(同理于比变量的类型(int ,char,short,double,float ,long ,longlong)),不过就只是写成(int*,char*,short*,double*,float*,long*,longlong*)这些就是指针的类型(看到这么多,各位不要怕,这些平常应该都用不到,反正我是大部分都没用到过,你只要知道有这个东西就行),我们 平常能看到的一般就只有int * 和 char * ,所以不要害怕,这边介绍这个主要的目的呢就是给大家讲一下一个指针所对应的所占内存的大小(就是相当于int所占内存的大小是4个字节),所以每一个指针它们也有相对应的所占的内存的大小(单位字节):

这边再给大家分一个点,因为内容较多且较为重要(关于不同类型的指针所占内存的大小和一系列使用方法)

1.首先这边强调,无论是什么指针类型它所占内存空间的大小都是 (4个字节),无论真么类型,切记!但是很多同学就会有疑问了,为什么呢?那如果都是4个字节,我为什么要去区分指针的类型呢?这不是没事找事吗?(所以这边就在第二点给你答案)

2.虽然任何类型指针的大小都是4个字节,但是当我们在对指针进行解引用时,指针的类型此时就会起关键作用(意思就是指针进行解引用时,能够访问的空间是不同的,例如:int类型在进行解引用操作时可以访问 4个字节大小的空间,而char却只能够访问 1个字节的内存)举一个形象一点是例子:假如现在我有一个数组(char arr[ 6 ] = “abcdef”),此时我使用int类型的指针去指向数组arr(int pa = arr,但是这边要进行一个强制类型的转化),所以当此时的int*指针向后走一步的时候就会直接跳过4个字节的内存,而(char * ch = arr),这样的话指针向后走一步,就只能跳过一个字节的内容,所以这边就涉及到了另一个概念叫:指针的步长;但是这边还有一个要注意的点(就是我的指针使用来指向某一个变量此时的地址的,所以在我们使用指针的时候都一般是将我所需要的变量的地址取出来,然后再放到我的指针变量中,例:int *p = &a ,然而为什么我们上面可以写成 int * pa = arr这个形式呢?原因就是上上述的那个必考点,因为数组名就是首元素的地址,所以此时的arr其实就是表明我arr数组的首元素的地址,这点一定要特别注意!)

3.这边就讲一讲什么是指针的步长,指针的步长就是当指针指向下一个位置时,可以跨过的距离,就好比一个男生和一个女生,它们跨一步的距离肯定是不同的,男神更远而女生更近,(对于指针来说就像是char指针类型,一步跨1个字节,int,一步跨4个字节,double*,一步跨8个字节,所以这就是不同指针类型的重大区别,虽然它们的所占内存空间的大小都是一样的,都是4个字节,重要的事情多说几遍!),下面用这个代码给大家展示一下什么是步长的具体用法:

//这题的关键就在于如何理解指针的步长
#include<stdio.h>
int main()
{
    int a[4] = { 1,2,3,4 };//这个数组在内存中的储存方式是  |01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00| 这个就是代表1 2 3 4 的内存储存方式
    int* ptr1 = (int *)(&a + 1);//这边的意思就是(跳过了整个数组a,不敢给吓到)(然后前面加一个int*的意思就是,将数组类型强制转换为 整形指针)(然后把值赋给了ptr1*这个指针,所以此时 ptr1指针 指向的就是跳过整个数组后的那个位置的地址)(然后此时为什么一定要进行强制类型转换呢?答案就是:在进行赋值操作时一定要是想同类型的数据,所以你想赋值给一个指针变量是,你赋值的内容也最好是指针类型的)
    int* ptr2 = (int *)((int)a + 1);//这边的a是首元素地址的意思(不敢给下到连这个都不会看),然后对首元素地址进行强制类型转,转换为整形(所以此时就是传化为一个整形的元素地址)(所以此时加1,就是使这个整数的地址往后移动了1位)
    printf("%x,%x",ptr1[-1],*ptr2);//这边虽然打印不出来,但是答案是:4   02000000(重要的是这里的ptr1[-1]是什么东西:其实就是*(ptr+(-1)),也就是*(ptr-1)),所以此时的答案就是4(因为指针现在指向的是跳过a这整个数组后面的未知数组的地址,所以从那个位置进行减1,得到的就是4这个元素的位置了)
   //表示%x就是用来打印16进制的    //上面那个太长,把*ptr2写到这里:意思就是a的地址加1后的地址(因为此时的a是一个整数),也就是01往后一个字节的那个位置(要注意的是这边并没有到2的地址(因为从1到2它的中间是有4个字节)),如果如上图的话就是在 01 后面那个 00 的位置(这点要明白),然后因为我最后赋值是赋给了一个int*类型的指针,所以此时对ptr2进行解引用的话(此时它就是直接往后走4个字节的位置,来到2的位置)(原因就是:ptr2,是一个int*类型的指针,步长是4个字节)(这个一定要懂)
    return 0;
}

(二、)上述就是指针的大致情况和几个考点(必考),接下来我们再讲一讲有关指针的小进阶。

1.刚刚忘记详细讲一讲什么是指针的解引用操作了,所以这里进行一点的解引用操作的讲解,还是以 int * pa = arr 这个作为例子,假如我现在需要使用或者说打印这个arr数组,此时我就要进行解引用( * arr),这个就叫做解引用,在数组的前面加一个 * 号,但是要区别, int * pa = arr 中的这个 * 号,这两个的意思是完全不同的,前者表示的是(解引用的意思),后者表示的是(pa这个变量是一个指针的意思),所以要注意,所以我的( * arr)就是解引用操作,只有这样此时我在可以拿到arr数组中的首元素,是真正的元素,而不是像一串数字的东西(地址 )

2.进阶第一点,我们讲一讲什么是野指针问题;首先野指针指的是:指针指向的位置是不可知 的,是随机的,例:int*p;这个就是一个野指针(没有初始化的指针,会导致越界,空间释放度等问题,就会使程序出现问题)所以为了规避这种问题一下有几个方法:

(1.)只要定义指针变量,我们就直接初始化,当不知道初始化为什么好的时候,我们就初始化为空指针(NULL)或 ( 0 )

(2.)使用内存时,时刻注意指针是否会越界访问别的内存(意思就是不能让指针一直向后走,如果一直向后走就会导致你的变量中的内存不够使用,此时如果指针还在向后走的话,就会导致越界访问了)

(3.)指针指向空间释放的位置为空指针(NULL)

(4.)使用时检查指针的有效性

3.进阶第二点,我们讲一讲什么是关于指针的计算(午觉时间到,晚上咱继续,就怪昨天某个钟姓人士叫我打游戏,然后下午还要军训,不容易啊!);指针的计算:(指针的加减计算,无论是加完还是减完,最后得到的答案不是相加等于多少,也不是相减等于多少而是指两个指针之间的元素个数是多少,元素个数才是我最终要的答案(并且千万要记住是元素个数,不是字节数)),下面让我们用一个代码来演示一下:

#include<stdio.h>
int main()
{ 
    int a[5][5];
    int(*p)[4];//这个p是一个数组指针(不敢不会认)
    p = a;//首先毫无疑问,这个a就是 一个首元素地址(且要注意这边的类型是不一样的,如果直接这样写就会报警告)(详情请见上面那个鬼)(但是意思还是表示:我把首元素的地址赋给了p,只是类型不同而已)
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//重点呢还是在步长上,且p[4][2]的意思呢是:*(p+4)=p[4];  *(*(p+4)+2)=p[4][2],只是同样的意思执行两次而已,看住很吓人(其实也就是soso啦)
    return 0;//这个地方还要注意一个点(就是使用%p去打印-4)(这边打印就会涉及到源码反码补码,所以要进行转换,因为这个是%p)%p打印的是内存中的值(所以它并不关心有无符号)(所以它打印的就是-4这个值的补码)(因为%p是用来打印内存地址的,而我现在内存中存的也就是-4的补码,所以我根本不关心,他到底是源码反码还是补码,我关心的只是它内存中存的是什么而已,内存中存什么我就打印什么)(就是相当于把内存中的补码当成了这个值的地址)
}

上述这个题目就是一个典型与指针计算相关的题目:(虽然注释已经讲的很清晰了,但是我还是再总结一下)

(1.)首先这边引入一个新概念叫(数组指针),就是 int(*p)[4],这边有一个小知识点(因为[ ] 的优先级比 * 高,所以必须加上一个()写成 (*p)只有这样才代表的是指针而不是数组)(因为有一个叫指针数组的东西与这个的差别很小,就在于有没有这个()),且指针数组的形式是(int * arr[10]),所以可以 看出数组指针和指针数组的区别就在于这个(),这边我们先再插入一个代码来展示一下,什么是指针数组:

//数组指针的使用
#include<stdio.h>
int main()
{
  int i;
  int a = 10;
  int b = 20;
  int c = 30;
  //int* pa = &a;
  //int* pb = &b;
  //int* pc = &c;
  int* arr[3] = { &a,&b,&c };
  int** parr = arr;//这边的int**parr应该理解为 int*(*parr),括号外的*表示(*parr)的类型是int*类型,(*parr)这个中的*表示parr是一个指针
    //这边还有很关键的一点就是 int* arr[3] = { pa,pb,pc }这个东西中的arr表示的数组名(所以此时代表的是首元素的地址(重点就是地址二字)),所以我接收arr是用一个指针就行(*parr)
    //但是这边为什么要写成int** parr = arr这个来接收arr呢?(原因就是arr此时的类型是一个int*的数组,所以为了保持类型的一样,我也要用一个int*的类型的指针去接收这个int*类型的数组),所以写成int**parr;
  for (i = 0; i <= 3; i++)
  {
    printf("%d\n", *(arr[i]));
    printf("%d\n", **(arr + i));
    printf("%d\n",(*parr[i]));//这个的意思就是对(*parr)进行解引用,才能拿到具体的元素,而不是地址,因为此时*parr代表的就是数组arr(也就是首元素的地址)
    printf("%d\n", **(parr + i));
  }
  return 0;
}
上述代码就能很好的解释什么是指针数组,什么是数组指针

2.总结:军训很累,下次再写

相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
52 0
|
14天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
67 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
14天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
42 9
|
14天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
39 7
|
25天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
87 13
|
18天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
18天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
54 3
|
19天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
25天前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
47 11
|
18天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
32 1