指针的基本知识

指针的基本知识点(1)


观前提示:本人才疏学浅,理解能力有限,本文仅供参考,若有那里出错,欢迎指正!!

指针的基础概念

1.了解内存和指针

在了解指针前,我们因该搞清楚什么是内存。内存就好比一块块的区域,里面存放着需要用到数据,在计算机中的内存一般是以bit划分,每一bit就好比一块区域,这一块区域能存某些数据,而每一字节分配一个地址,也就是说分配内存的最小单位是字节(byte),最小传输单位是(bit)。如图1.1

图.1中0x00000001是这一字节地址,一字节中有8个bit位。

在C语言中地址被叫做指针,所以***内存单元的编码 == 地址 == 指针***。

指针的使用

接下来我们就直接来使用指针试试吧

#include<stdio.h>
int main()
{
	int a = 0;
	int* p = &a;
	printf("%p\n", p);
	return 0;
}

在上述代码,我们把a取地址存放到p指针变量中,再以地址的形式打印出来,我们就会打印出a的地址(如果打印出的地址太长不方便观察,可以该为x86环境,地址会短,便于观察)。
我们继续来观察下内存中存放的情况如图.2

图.2中a开辟了4个字节,每一个字节有一个地址,&a得到的是地址中较小的那个。

指针变量和解引用操作符( * )

1.指针变量

在上述代码中我们提到把&a存放到p指针变量中,所以指针变量就是一种变量,这种变量是用来存放地址的,存放在指针变量中的值都会理解为地址

2.解引用操作符( * )

我们已经在前面的代码中见过这一操作符( * ),如:int * ,这里的是表示p的类型是整形指针变量和我们接下来要讲的解引用操作符并不相同,下来看一段代码

int main()
{
	int a = 10;
	char b = 'W';
	int* p1 = &a;
	char* p2 = &b;
	printf("%d \n", *p1);
	printf("%c \n", *p2);
	return 0;
}

该代码的输出结果是10和w,所以我们就能想到 ***** 解引用操作符的作用或许就是把指针变量中的值给取出。但是int和char类型不同,在内存中占的内存也不同,为什么 *** *就能整好取出一个int类型或char类型呢?我们把目光再回到int,p1的类型上我们就能理解原因:指针的类型决定了,对指针解引用的时候有多大权限(一次能够操作多少字节)

*** **解引用操作符不仅可以取出也可以,同时也能够修改该地址处的数据例如接下来的代码

int main()
{
	int a = 10;
	int* p = &a;
	*p = 20;
	printf("%d ", *p);
	return 0;
}

从代码中能看出通过解引用操作符,我们能够修改该地址的数据。

3.指针的大小

结论如下:

  • 32为平台下地址是32个bit位,指针变量的大小是4个字节。
  • 64位平台下地址是64个bit位,指针变量的大小是8个字节。
  • 指针变量的大小与类型无关只于环境有关。

指针大小的原因本文不深入解释。

指针 + - 整数

指针变量的加减会根据指针变量的类型,跳过类型大小的字节数,例如以下代码

int main()
{ 
	int arr[4] = { 1,2,3,4 };
	int* p = &arr;
	printf("%d\n", *p);
	printf("%d\n", *(p+1));
	printf("%d\n", *(p+2));
	printf("%d\n", *(p+3));
	return 0;
}

该代码输出结果为1,2,3,4 从结果来看p+1跳过了1个int类型,以此类推,面对char类型的+1,也就是跳过1个字节。

void* 指针

void* 指针,在往后的未知类型传参中会大量使用例如

print(void* p)
{
	printf("%d ", *(int*)p);
}
int main()
{
	int a = 10;
	int* p = &a;
	print(p);
	return 0;
}

上述代码中我们给print函数传了指针变量p而在print函数中用void类型接收,这种情况适用于,比如这个print函数要接收不止一种类型的参数,而我们在使用的时候把这些参数都以void的类型接收这样就能以一个函数来处理多种类型。

const修饰指针

const有两种

  1. const int p*

  2. int const p*

两种的const的位置不同造成的效果也不同,我们先看代码

void test1()
{
	int a = 10;
	int b = 0;
	const int* p = &a;
	*p = 20;//编译器报错
	p = &b;
}
void test2()
{
	int a = 10;
	int b = 0;
	int* const p = &a;
	*p = 20;
	p = &b;//编译器报错
}
void test3()
{
	int a = 10;
	int b = 0;
	const int* const p = &a;
	*p = 20;//编译器报错
	p = &b;//编译器报错
}
int main()
{
	test1();
	test2();
	test3(); 
		return 0;
}

在这里插入图片描述

上述代码中编译器会报错,提示表达式必须是可修改的值。这就表明
(1)const int * p 情况下p的值不能被修改。 (2)int * const p情况下p的值不能被修改。
test3中左右都有const所以
p和p的值都不能修改。

野指针

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

1.造成野指针的原因

1.指针未初始化
int main()
{
	int* p;//指针没有初始化,默认随机值
	*p = 20;
	return 0;
}
2.指针越界访问
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*(p++) = i;//超出数组指向数组外的指针,p就是野指针
	}
	return 0;
}
3.指向已经释放的空间
int* test1()
{
	int a = 10;
	return &a;
}
int main()
{
	int* p = test1();//p指向&a而a已经释放,所以p野指针
	return 0;
}

在使用指针的时候我们要避免野指针的出现,若果不用该指针要及时至空(p=NULL)

arrsrt断言

assert主要时用来判断指针是否为空

assert(p != NULL);

使用assert时要包含头文件<assert.h>
assert如果表达式为真不会产生任何效果,程序继续进行,当表达式为假的时候,assert会报错。

strlen函数的模拟实现

接下来我们用strlen函数模拟实现来熟悉下指针的使用

size_t my_strlen(char str[])
{
	char* p = str;
	assert(p);
	int count = 0;
	while (*p)
	{
		count++;
		p++;
	}
	return count;
}
int main()
{
	char str[] = { "hello world" };
	size_t ret = my_strlen(str);
	printf("%zd", ret);
	return 0;
}

指针的基本知识点(2)


一维数组传参的本质

首先我们得了解数组名的本质,一般情况下数组名代表的是首元素的地址,也有两个例外

  • sizeof(数组名):sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

还有一点我们得搞清楚,&数组名取出的地址和首元素地址在数值上并没有区别,只有在进行加减的时候才会有区别,例如:

int main()
{
	int arr[10] = { 0 };
	&arr + 1;//表示跳过整个数组
	arr + 1;//表示跳过一个int类型
	return 0;
}

接下来我们来了解下一维数组传参的本质,在之前的strlen函数模拟实现代码中我们能看到在把数组传递过去的时候我们用数组来接收,但是我们知到数组名代表首元素的地址,所以我们传参的时候,本质上就是传递了地址,因此,在形参部分我们也可以用指针来接收。

void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[] = { 1,2,3,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

以上代码就是用指针来接收。所以一维数组传参的本质就是传递了地址。

二级指针

在之前的代码中我们所用到的指针都是一级指针,而一级指针中存放的是地址,指向某个数。而二级指针中存放的也是地址–>指针的地址,所以二级指针指向的是一个指针。

int main()
{
	int a = 10;
	int* p = &a;  	//int*  是p  的类型
	int** pp = &p;	//int** 是pp 的类型
	return 0;
}

上述代码中pp就是二级指针。
在这里插入图片描述

相对的,还会有多级指针与二级指针同理。

指针数组

1.定义

指针数组顾名思义就是存放指针的数组

int main()
{
	char* str[] = {"hello","world"};
	printf("%p\n", str);
	printf("%p\n", str+1);
	return 0;
}

从上述代码中我们能够打印出来两个地址,一个是“hello”的首元素地址,一个是“world”的首元素地址,这两个地址寸放大str数组中,所以str就被称为指针数组。

2.利用指针数组来模拟二维数组

int main()
{
	int arr1[3] = { 1,2,3 };
	int arr2[3] = { 4,5,6 };
	int arr3[3] = { 7,8,9 };
	int* arr[3] = { arr1,arr2,arr3 };
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ", arr[i][j]);
		}
	}
	return 0;
}

上述代码中能否成功打印1-9的数组呢?答案是能!我们首先定义了3给整形数组arr1,arr2,arr3,再把它们的首元素地址存放到指针数组arr中,这好像确实能够模拟二维数组,但是在两个循环里面为什么能够直接用arr[ i ] [ j ]来打印呢,明明不是二维数组。这就涉及到arr [ x ]计算机中会变成什么。

我们要知到[ ] 一个操作符,计算机在理解arr [ x ]的时候会把它看作*( arr + x ),所以arr [ x ] == (arr+x),那么arr [i] [j]== ( *(arr+i)+j )。这样就能够解释上述代码中的arr [ i ] [ j ]。


指针的基本知识点(3)


字符指针变量

我们首先看一段代码

int main()
{
	char a = 'a';
	char str[] = "hello";
	char* p1 = &a;
	char* p2 = str;
	printf("%c ", *p1);
	printf("%s ", p2);
	return 0;
}

上述代码中p1是字符指针变量,p2也是字符指针变量。
我们再来看一道有趣的题目出自《剑指offer》一书中

int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else

		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else

		printf("str3 and str4 are not same\n");

	return 0;
}

该题的答案是:在这里插入图片描述

str1和str2不相同很好理解:因为创建str1和str2一定是两个内存块,所以str1和str2的地址一定不会相同。而str3和str2为什们是同一块内存块呢?原因就是:*str3和 *str4,直接指向了"hello bit",而"hello bit"是常量字符串C\C++会把常量字符串存储到内存中的静态区,而静态区的内容是不能修改的,当有多个指针指向同一块字符串时,就没有必要再创建一块新的空间来存放相同的字符串,大家都用这一个就可以,因为也不能修改所以公用就行。

数组指针变量

1.数组指针的基本形式

我们前面讲了指针数组,是存放指针的数组,那么数组指针自然就是指向数组的指针
基本形式如下

int(*p)[x] = &arr;

去掉数组名就能表示类型 int (*) [5],一个指针指向数组长度为5的整形数组。

2.数组指针和一般指针的比较

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int(*p1)[5] = &arr;
	int* p2 = arr;
	printf("%p\n", p1);
	printf("%p\n", p2);
	p1++;
	p2++;
	printf("%p\n", p1);
	printf("%p", p2);
	return 0;
}

上述代码中第4行就是数组指针的基本类型,p1是数组指针,p2是整形指针,当两个指针各自增的时候就能看出p1跳过整个数组,而p2跳过一个整形。

3.二维数组传参的本质

我们知到一维数组的数组名代表首元素的地址,二维数组的数组名也代表首元素的地址,只不过首元素的地址是首行数组的地址例如

int main()
{
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	int(*p1)[4] = arr;
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *(*p1 + i));
	}
}

上述代码中用数组指针p1接收二维数组arr的数组名,*p1取出的是第一行数组,+ i 再解引用( * )就是遍历第一行数组的所有元素。同样我们可以用数组指针将二维数组所有值打印出来。

int main()
{
	int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	int(*p1)[4] = arr;
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%4d ", *(*(p1 + i)+j));
		}
		printf("\n");
	}
}

在这里插入图片描述

函数指针变量

1.函数指针的基本形式

函数指针:是个指针,指向函数,那么我们能够通过这一指针来调用这一函数。我们首先来看一看函数是否有地址。

void test()
{
	printf("%d\n", 10);
}
int main()
{
	test();
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

结果是:

10
003813D4
003813D4

由此我们能看出函数不仅有地址而且函数名和&函数名表示的都是函数的地址。且函数名和&函数名并没有区别。
函数指针的基本形式如下:

int (*p)(int x,int y)

表示函数指针p指向的函数有x,y两个参数,并且返回类型是int型。

2.函数指针的使用

int add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 6;
	int (*p)(int, int) = add;
	int ret = (*p)(a, b);//  (p)(a, b)   *号可以不写
	printf("%d ", ret);
	return 0;
}

上述代码中我们将函数地址放到函数指针p中,利用函数指针p调用add函数。

以上就是指针的基本知识点,当然指针中一定会有更加复杂的知识点,本文只讨论基础。若想看更多有关指针的知识,请尽情期待!

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。
### 内容概要 这份《计算机试卷1》包含多个部分,主要覆盖了计算机基础知识、操作系统应用、文字处理、电子表格、演示文稿制作、互联网应用以及计算机多媒体技术。试卷以单选题开始,涉及计算机历史、基本概念、硬件组成、软件系统、网络协议等。接着是操作应用部分,要求考生在给定的软件环境中完成一系列具体的计算机操作任务。 ### 适用人群 本试卷适用于计算机科学与技术、信息技术相关专业的学生,以及准备计算机水平考试或职业资格认证的人士。它适合那些希望检验和提升自己计算机操作能力的学习者,也适用于教育工作者作为教学评估工具。 ### 使用场景及目标 1. **学习评估**:作为教育机构的课程评估工具,帮助教师了解学生对计算机基础知识的掌握程度。 2. **自学检验**:供个人自学者检验自己的计算机操作技能和理论知识,为进一步学习提供方向。 3. **职业发展**:为职场人士提供计算机技能的自我提升途径,增强其在信息时代的竞争力。 4. **考试准备**:为准备计算机相关考试的考生提供实战演练的机会,加强考试自信。 5. **教学资源**:教师可以将其作为教学资源,设计课程和实验,提高教学效果。 试卷的目标是通过理论知识的测试和实践技能的操作,全面提升考生的计算机应用能力。考生应掌握从基础的计算机组成原理到复杂的数据处理、演示文稿制作、网络应用以及多媒体技术处理等多方面技能。通过本试卷的学习与练习,考生将能够更加熟练地使用计算机解决实际问题,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值