《伏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二维数组的创建

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字节。而这个编号就是我们说的地址,也被称作指针。(这一点要牢记)。

图解:

 内存单元大小为一个字节,这里的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语言规定取变量的地址,取的是众多地址中最小的地址。

图解:

✨2.4指针类型

我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的

指针的类型常见的有char*、double* 、int*、int (*)[10]等。

介绍解引用操作符 *(间接访问操作)

*(地址)就能找到那块地址空间,为什么不说拿到那块空间的数据呢?因为当*(地址)做左值时,它代表的是那块空间,当*(地址)做右值时,代表的是那块空间的数据。(理解左值和右值这一点非常重要)

简单运用一下指针

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	char a = 'B';
	char* pa = &a;
	printf("%cn", *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个字节。

图解:

 第二点牢记:

  1. ?char*类型的指针,对其+1跳过1个字节。
  2. ?int*类型的指针,对其+1跳过4个字节。
  3. ?double* 类型的指针,对其+1跳过8个字节。

以此类推

以int* p为例,其他以此类推。

图解:

 总结:

1:?指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

2:?指针的类型决定了指针向前或者向后走一步有多大(距离)。

这两点就是语法规则,要牢记与心

✨2.6指针-指针

牢记:指针-指针(得相同类型的指针)的值为它们之间相差的元素个数。

以int* 为例:

此时p2-p1等于2(经过两个整形的大小)

其他以此类推(篇幅有限)

✨2.7野指针

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

用户只能对以申请的内存空间进行访问

1.?指针未初始化:未初始化的指针指向是随机的,不能访问。

2.?指针越界访问:访问不属于用户申请的空间,可能引起错误,很危险。

3?指针指向的空间释放:释放了的空间不属于用户,不能访问。

如何规避野指针?

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

✨2.8指针和指针变量的区别

?指针是地址,是编号,是一串二进制数

?指针变量是存放指针的变量,就如同整形变量是存放整形的变量。也可叫做地址变量,不过一般不这么叫。

?一般我们口头说的指针指的是指针变量,这一点我们心里要明白。

✨2.9一级到N级指针

?一维指针变量存的是如int 、double、float、char、long等变量的地址。

?二级指针变量存的是一维指针变量的地址。

?三维指针变量存的是二维指针变量的地址。

?一般使用较多的是一、二级指针(熟练掌握),三维及以上的指针很少碰到。

 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)跳过整个数组

这是这个指针类型赋予数组指针的意义。

✨2.11函数指针

函数指针顾名思义:指向函数的指针

这里必须要说明的是函数也是有地址的

函数名或者&函数名都是该函数的地址

函数指针的类型:

以 void print(int n)为例

类似于数组指针,将(*p)替换函数名即可:void (*p) (int)=&print;(这里&print和print都可以)。

调用函数时,我们可以(*p)(3),也可直接p(3)。


 由于篇幅原因,这里只讲解了函数指针的语法,并没有讲解它的使用环境。(函数指针通常用于回调函数,有兴趣的话博主可以下次更新这部分的内容)


?三、数组

✨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'、''。

总结:

1.?如果数组在创建的时候指定了大小,就开辟该大小的空间,如果赋予的值少于空间数目,则其他剩余空间的值都会被置于0.

2.?数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。

3.?直接使用字符串赋值,字符串自带''这一结束标志,开辟数组时也要为''开辟空间。

以上几种情况要熟练掌握。

✨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}};

我们可以通过监视观察初始化的结果,以下是监视的结果。

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]的数组

 图解:

✨3.6 从三维到N维数组

我们知道二维数组可用二维坐标表示。三维数组可用三维坐标,四维该怎么表示呢?

其实都可以看作一维的数组。

如int arr[ 3 ][  2 ][  4 ] 三维数组。

看作3个元素的一维数组,每个元素是二维数组。

图解:

✨3.7数组传参

数组传参分两种:

1.?传数组,用数组接收。

2.?传数组,用指针接收。

种:传数组,用数组接收

一维数组:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
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<stdio.h>
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<stdio.h>
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("%dn",sizeof(a));
printf("%dn",sizeof(a+0));
printf("%dn",sizeof(*a));
printf("%dn",sizeof(a+1));
printf("%dn",sizeof(a[1]));
printf("%dn",sizeof(&a));
printf("%dn",sizeof(*&a));
printf("%dn",sizeof(&a+1));
printf("%dn",sizeof(&a[0]));
printf("%dn",sizeof(&a[0]+1));

//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%dn", sizeof(arr));
printf("%dn", sizeof(arr+0));
printf("%dn", sizeof(*arr));
printf("%dn", sizeof(arr[1]));
printf("%dn", sizeof(&arr));
printf("%dn", sizeof(&arr+1));
printf("%dn", sizeof(&arr[0]+1));
printf("%dn", strlen(arr));
printf("%dn", strlen(arr+0));
printf("%dn", strlen(*arr));
printf("%dn", strlen(arr[1]));
printf("%dn", strlen(&arr));
printf("%dn", strlen(&arr+1));
printf("%dn", strlen(&arr[0]+1));

//二维数组
int a[3][4] = {0};
printf("%dn",sizeof(a));
printf("%dn",sizeof(a[0][0]));
printf("%dn",sizeof(a[0]));
printf("%dn",sizeof(a[0]+1));
printf("%dn",sizeof(*(a[0]+1)));
printf("%dn",sizeof(a+1));
printf("%dn",sizeof(*(a+1)));
printf("%dn",sizeof(&a[0]+1));
printf("%dn",sizeof(*(&a[0]+1)));
printf("%dn",sizeof(*a));
printf("%dn",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;
}
//程序的结果是什么?

笔试题二
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%sn", *pa);
return 0;
}

解析:

char** pa=a,pa++,该指针指向的是char* 类型,+1跳过一个char*,即一个数组元素,因此pa++指向数组第二个元素,*pa即拿到数组第二个元素,(“at”存储在数组的第二个元素的是'a'的地址),所以*pa就是'a'的地址,即字符串“at”的首地址,因此将打印出at。

由于篇幅原因,还有很多细节没有交代,之后会出更多c语言的系列,感谢关注。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>