c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组

目录
一、本章目标
二、指针
?2.1指针是什么?
?2.2指针有什么用?
?2.3取地址取的是哪个地址?
?2.4指针类型
?2.5指针类型的意义
?2.6指针-指针
?2.7野指针
?2.8指针和指针变量的区别
?2.9一级到N级指针
?2.10数组指针
?2.11函数指针
三、数组
?3.1数组的概念
?3.2一维数组的初始化
?3.3数组名的意义
?3.4一维数组与sizeof的那些事
?3.5二维数组
3.5.1二维数组的创建
【c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组】3.5.2二维数组的初始化
3.5.3二维数组与sizeof的那些事
?3.6 从三维到N维数组
?3.7数组传参
四、指针和数组笔试题解析
?4.1指针与数组关于sizeof和strlen的事
?4.2指针笔试题
一、本章目标

  1. 极速解决一级至n级指针
  2. 极速解决一维至n维数组
  3. 极速解决数组与指针联系
  4. 指针和数组笔试题解析
二、指针 ?2.1指针是什么?
谈到指针,不得不谈内存,内存是一块大的用来存储数据的空间,它被分为了一小块一小块的内存单元,每一块内存单元的大小为1字节,为方便使用和区分内存单元,每一块内存单元有一个独立的编号,以32位机器为例,这个独立的编号由32位二进制组成(1字节等于8个二进制位),即4字节。如果是64位机器的话,这个独立的编号是64位二进制组成(1字节等于8个二进制位),即8字节。而这个编号就是我们说的地址,也被称作指针。(这一点要牢记)。
图解:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

内存单元大小为一个字节,这里的11 22 33 44 55 66 77均为16进制数。(一位16进制数等于4位2进制数)
以32位机器为例,由于地址是32位二进制组成的,那么就有2^32种不同的地址,那么就有2^32个内存单元,因为内存单元的大小是一个字节,那么内存的大小也可以算出来,即2^32Byte,也等于4GB。
总结:
  1. 指针是地址,是内存单元的编号。(这一点非常重要)
  2. 32位机器下,指针大小为4字节
  3. 64位机器下,指针大小为8字节
?2.2指针有什么用? 可以取出某个变量的地址,方便以后操作那块地址空间,为什么不用变量名直接操作那块空间呢?
因为有时我们拿不到变量名,比如在函数传参时,我们在函数体内部是无法拿到变量名的,函数传参只能传数据,地址是数据,变量名传的是变量名中的数据,而不是变量名本身。
?2.3取地址取的是哪个地址? 例如:int a;
我们知道int在内存开辟4个字节,占用4个内存单元,每个内存都有一个编号,即都有一个地址,那&a,取的是4个地址中的哪个地址呢?
c语言规定取变量的地址,取的是众多地址中最小的地址。
图解:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片



?2.4指针类型 我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的
指针的类型常见的有char*、double* 、int*、int (*)[10]等。
介绍解引用操作符 *(间接访问操作)
*(地址)就能找到那块地址空间,为什么不说拿到那块空间的数据呢?因为当*(地址)做左值时,它代表的是那块空间,当*(地址)做右值时,代表的是那块空间的数据。(理解左值和右值这一点非常重要)
简单运用一下指针
#define _CRT_SECURE_NO_WARNINGS 1 #include int main() { char a = 'B'; char* pa = &a; printf("%c\n", *pa); return; }


解读代码:
pa拿到变量a的地址,通过解引用操作拿到变量a管理的那块空间。之后*pa的作用与a相同,可以认为*pa等价于a,使用a和使用(*pa)没任何区别。
?2.5指针类型的意义
第一点牢记:
  • char*类型的指针,对其接引用访问1个字节。
  • int*类型的指针,接引用访问4个字节。
  • double* 类型的指针,接引用访问8个字节。
以此类推。
详细点:
以int*为例如果指针的值是0x 00 1F EA 88,那么解引用访问的就是从该地址处向高地址处访问4个字节。
图解:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

第二点牢记:
  1. char*类型的指针,对其+1跳过1个字节。
  2. int*类型的指针,对其+1跳过4个字节。
  3. double* 类型的指针,对其+1跳过8个字节。
以此类推
以int* p为例,其他以此类推。
图解:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

总结:
1:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
2:指针的类型决定了指针向前或者向后走一步有多大(距离)。
这两点就是语法规则,要牢记与心
?2.6指针-指针 牢记:指针-指针(得相同类型的指针)的值为它们之间相差的元素个数。
以int* 为例:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

此时p2-p1等于2(经过两个整形的大小)
其他以此类推(篇幅有限)
?2.7野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因

用户只能对以申请的内存空间进行访问
1.指针未初始化:未初始化的指针指向是随机的,不能访问。
2.指针越界访问:访问不属于用户申请的空间,可能引起错误,很危险。
3指针指向的空间释放:释放了的空间不属于用户,不能访问。
如何规避野指针?
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
?2.8指针和指针变量的区别
指针是地址,是编号,是一串二进制数。
指针变量是存放指针的变量,就如同整形变量是存放整形的变量。也可叫做地址变量,不过一般不这么叫。
一般我们口头说的指针指的是指针变量,这一点我们心里要明白。
?2.9一级到N级指针
一维指针变量存的是如int 、double、float、char、long等变量的地址。
二级指针变量存的是一维指针变量的地址。
三维指针变量存的是二维指针变量的地址。
一般使用较多的是一、二级指针(熟练掌握),三维及以上的指针很少碰到。
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

N级指针以此类推
?2.10数组指针
类比于整形指针,整形指针是指向整形的指针,结构体指针是指针结构体的指针。
数组指针是指向数组的指针。
数组指针的表示:
如int arr[10]={0};
指向该数组的指针可写为:int (*p)[10]=&a; (只需把(*p)代替掉arr即可)
解释:
首先()的优先级大于[ ],*和p先结合成*p,说明它是指针,去掉变量名就是它的类型。
它的类型是int()[10]。类型代表着该指针p指向10个元素的数组,每个元素是int类型的变量。
其他以此类推
这里写个数组指针类型的数组。(赏析)
int (*p[10])[5];
解释
*的优先级低于[ ],p先与[ ]结合,p[10]表面这是一个数组,它有10个元素,
它的类型是int(*)[5]。这个我们在上面见过,它是一个数组指针类型,该数组指针指向有5个元素的数组,每个元素是int类型。
该指针+1跳过一个数组的长度。(需记忆)
对数组指针解引用等价于该数组的变量名。(需记忆)
如int arr[10]={1,2,3,4,5,6,7,8,9,10}
&a等于取的是整个数组。
指向它的数组指针为int (*p1)[10]=&a;
这个变量的变量名是p1,变量的类型是int(*)[10].
p1+1跳过整个数组。
//这里需要对数组有一定的了解。
图解(p1+1)跳过整个数组
这是这个指针类型赋予数组指针的意义。
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

?2.11函数指针 函数指针顾名思义:指向函数的指针
这里必须要说明的是函数也是有地址的
函数名或者&函数名都是该函数的地址
函数指针的类型:
以 void print(int n)为例
类似于数组指针,将(*p)替换函数名即可:void (*p) (int)=&print; (这里&print和print都可以)。
调用函数时,我们可以(*p)(3),也可直接p(3)。
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

由于篇幅原因,这里只讲解了函数指针的语法,并没有讲解它的使用环境。(函数指针通常用于回调函数,有兴趣的话博主可以下次更新这部分的内容)
三、数组 ?3.1数组的概念 首先声明:指针和数组完全是两个不同的东西。但它们之间在使用时又密不可分。
数组概念:一组相同类型元素的集合。可以是整形,浮点型,也可以是结构体。
而指针就是地址,是编号。
?3.2一维数组的初始化 数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
数组的初始化有多种情况,以下情况都是正确的初始化。
(1)int arr1[10] = {1,2,3}; (2)int arr2[] = {1,2,3,4}; (3)int arr3[5] = {1,2,3,4,5}; (4)char arr4[3] = {'a',98, 'c'}; (5)char arr5[] = {'a','b','c'}; (6)char arr6[] = "abcdef";

(1)实际开辟了10个空间,前三个存的是1、2、3,其他七个都是0。
(2)开辟了4个空间,分别存的是1、2、3、4。
(3)开辟了5个空间,分别存的是1、2、3、4、5。
(4)开辟了3个空间,分别存的是'a'、98、'c'。
(5)开辟了3个空间,分别存的是'a'、'b'、'c'。
(6)开辟了7个空间,分别存的是'a'、'b'、'c'、'd'、'e'、'f'、'\0'。
总结:
1.如果数组在创建的时候指定了大小,就开辟该大小的空间,如果赋予的值少于空间数目,则其他剩余空间的值都会被置于0.
2.数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
3.直接使用字符串赋值,字符串自带'\0'这一结束标志,开辟数组时也要为'\0'开辟空间。
以上几种情况要熟练掌握。
?3.3数组名的意义
数组名:就是首元素的地址。
注意:数组名可以看作一个常量(首元素的地址),它不能作为左值,只能作为右值。
左值:能给作为赋值运算符(=)左边的值即为左值。
右值:能给作为赋值运算符(=)右边的值即为右值。
但两个例外
1.sizeof(arr)中arr代表整个数组。
2.&arr代表整个数组的地址。
?3.4一维数组与sizeof的那些事 例:int arr[10]={1,2,3,4,5,6,7,8,9,10};
计算整个一维数组的大小:sizeof(arr);
计算一维数组元素的大小:sizeof(arr[0]);
计算一维数组的元素个数:sizof(arr)/sizeof(arr[0]);
?3.5二维数组 3.5.1二维数组的创建
//数组创建 int arr1[3][4]; char arr2[3][5]; double arr3[2][4];

3.5.2二维数组的初始化
//数组初始化 int arr1[3][4] = {1,2,3,4}; int arr2[3][4] = {{1,2},{4,5}}; int arr3[][4] = {{2,3},{4,5}};

我们可以通过监视观察初始化的结果,以下是监视的结果。
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片


3.5.3二维数组与sizeof的那些事
(1)计算整个二维数组的大小:sizeof(arr);
(2)计算二维数组的行数:sizeof(arr)/sizeof(arr[0]);
(3)计算二维数组的列数:sizeof(arr[0])/sizeof(arr[0][0]);
(4)计算二维数组元素的大小:sizeof(arr[0][0]);
需了解以下知识
1.数组名就是首元素地址
此时二维数组的首元素需要特别指出:
以arr[ 3 ][ 4 ]为例
它是有3个元素的数组,每个元素是一个一维数组int arr[ 4 ]。
此时二维数组的首元素的地址是int arr[4]的地址,即数组的地址,类型为int (*) [ 4 ]。
(本文章下面的标题:深度剖析有详解)
2.变形公式:arr[ i ] <==> *(arr + i)arr[ i ][ j ]<==> *(*(arr+i)+j)
详解:
(1)sizeof(数组名)就是计算的整个数组的大小(需要记住)。
(2)sizeof(arr[0]):根据变形公式arr[0]等价于*(arr+0),即(*arr)。
arr是数组指针类型(int (*) [ 4 ]),对数组指针解引用后就相当于该数组的变量名,(这点要牢记)。
而sizeof(数组名)就是该数组的大小,即4*4=16Byte。
而sizeof(arr)/sizeof(arr[ 0 ])=(4*3*4)/16=3。
(3)sizeof(arr[0])/sizeof(arr[0][0])=16/4=4。
(4)sizeof(arr[0][0])=4。

深度解剖:
二维数组本质是线性的一维数组,如int arr[3][4]; 相当于int arr[3],它的每个元素是一个int arr[4]的数组
图解:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

?3.6 从三维到N维数组 我们知道二维数组可用二维坐标表示。三维数组可用三维坐标,四维该怎么表示呢?
其实都可以看作一维的数组。
如int arr[ 3 ][2 ][4 ] 三维数组。
看作3个元素的一维数组,每个元素是二维数组。
图解:
c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片


?3.7数组传参
数组传参分两种:
1.传数组,用数组接收。
2.传数组,用指针接收。
第一种:传数组,用数组接收
一维数组:
#define _CRT_SECURE_NO_WARNINGS 1 #include void print(int arr[],int n)//该arr[]中的[]中填大于0的值或者不填都可以。 { int i = 0; for (i = 0; i < n; i++) { printf("%d ", arr[i]); } } int main() { int arr[10] = { 1,2,3,4,5,6 }; int len = sizeof(arr) / sizeof(arr[0]); print(arr, len); return 0; }

//该arr[]中的[]中填大于0的值或者不填都可以。
二维数组:
#define _CRT_SECURE_NO_WARNINGS 1 #include void print(int arr[2][3],int r,int c)//行可以省略,列不能省略。 { int i = 0; int j = 0; for (i = 0; i < r; i++) { for (j = 0; j < c; j++) { printf("%d ", arr[i][j]); } printf("\n"); } printf("\n"); } int main() { int arr[2][3] = { 1,2,3,4,5,6 }; int r = sizeof(arr) / sizeof(arr[0]); int c = sizeof(arr[0]) / sizeof(arr[0][0]); print(arr, r, c); return 0; }

//行可以省略,列不能省略。
第二种:传数组,用指针接收
一维数组:
void print(int* p, int len) { int i = 0; for (i = 0; i < len; i++) { printf("%d ", p[i]); } } int main() { int arr[5] = { 1,2,3,4,5 }; int len = sizeof(arr) / sizeof(arr[0]); print(arr, len); return 0; }

二维数组:
#define _CRT_SECURE_NO_WARNINGS 1 #include void print(int(*arr)[3], int r, int c) { int i = 0; int j = 0; for (i = 0; i < r; i++) { for (j = 0; j < c; j++) { printf("%d ", arr[i][j]); } printf("\n"); } printf("\n"); } int main() { int arr[2][3] = { 1,2,3,4,5,6 }; int r = sizeof(arr) / sizeof(arr[0]); int c = sizeof(arr[0]) / sizeof(arr[0][0]); print(arr, r, c); return 0; }

总结:
1.传数组名时,可以用数组接收,传什么数组,就用什么数组接收(不建议省略形参中[ ]的内容 )。
2.一维数组名,要看该数组元素的类型,如果该数组的元素类型是int*,数组名又是数组首元素的地址,即int*的地址,那么接收该数组名就得用int**。
3.二维数组名:和一维数组名一样,需把二维数组看成一维数组,如int arr[3][4]。它有3个元素,每个元素是int arr[4]。那么数组名代表int arr[4]的地址,即数组的地址,那么需要用数组指针来接收。
四、指针和数组笔试题解析 ?4.1指针与数组关于sizeof和strlen的事
//一维数组 int a[] = {1,2,3,4}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a+0)); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(a[1])); printf("%d\n",sizeof(&a)); printf("%d\n",sizeof(*&a)); printf("%d\n",sizeof(&a+1)); printf("%d\n",sizeof(&a[0])); printf("%d\n",sizeof(&a[0]+1)); //字符数组 char arr[] = {'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", sizeof(*arr)); printf("%d\n", sizeof(arr[1])); printf("%d\n", sizeof(&arr)); printf("%d\n", sizeof(&arr+1)); printf("%d\n", sizeof(&arr[0]+1)); printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr+1)); printf("%d\n", strlen(&arr[0]+1)); //二维数组 int a[3][4] = {0}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a[0][0])); printf("%d\n",sizeof(a[0])); printf("%d\n",sizeof(a[0]+1)); printf("%d\n",sizeof(*(a[0]+1))); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(*(a+1))); printf("%d\n",sizeof(&a[0]+1)); printf("%d\n",sizeof(*(&a[0]+1))); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a[3]));

编译器运行立马出答案,就不赘述了。
?4.2指针笔试题
笔试题一: int main() { int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d", *(a + 1), *(ptr - 1)); return 0; } //程序的结果是什么?

c语言|《伏C录》破劫篇-零基础无境界限制极速领悟指针与数组
文章图片

笔试题二 #include int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }

解析:
char** pa=a,pa++,该指针指向的是char* 类型,+1跳过一个char*,即一个数组元素,因此pa++指向数组第二个元素,*pa即拿到数组第二个元素,(“at”存储在数组的第二个元素的是'a'的地址),所以*pa就是'a'的地址,即字符串“at”的首地址,因此将打印出at。
由于篇幅原因,还有很多细节没有交代,之后会出更多c语言的系列,感谢关注。

    推荐阅读