IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 10讲学会C语言之第七讲:指针 -> 正文阅读

[C++知识库]10讲学会C语言之第七讲:指针


前言

大家好,我是卷卷,本节课的主题是指针,这同时也是C语言中最重要的部分,希望大家更加认真对待。本节课主要有以下六个部分,指针与地址,角色互换,冒泡排序,电码加密,动态内存分配,作业。(文末附课程资源和讨论q群号)

一、指针与地址

保存数据地址的变量,称作指针变量。严格意义上指针就是数据地址。但一般把指针变量叫作指针。指针的定义形式为
数据类型* 变量名;
比如 int* p;定义了一个指向,或者保存int变量的地址的指针p。设被指向的变量为a,则指针的示意图如下:
???????????????????指针示意图
在这里插入图片描述
可以看到,指针和其它变量的本质区别是:指针存放的是地址,其它变量存放的是数据值。所以修改p,其实修改的是p存放的变量地址。p存放的变量地址也叫作p的指向。所以修改p也叫作改变p的指向。所以如果写p=1,只是把p存放的地址改为1。那么如何修改a呢?这就要用到*,*表示取地址,即访问地址所在的单元。比如*p就是a的地址001所在的单元。如果要将a改为一个数,比如1。则要这样写:*p=1;这样就把a的值改为了1。在C/C++语言中,NULL等同于数字0,代表空指针,不指向任何变量。特别注意:指针的值,除了改为0之外,一般不手动修改。因为其它数字不一定是实际内存地址,如果程序的其它语句使用了这个指针,则可能使程序出错。将变量的地址赋值给指针的操作:
取变量b的地址:&b
定义一个指针p并将变量b的地址赋值p:b的数据类型* p=&b;
因为指针也是一个变量,所以也可以用const修饰,但是会有两种情况:
第一种是const int* p;表示常量指针,p的值固定了,也就是p的指向固定了,但是指向的内容却可以改变。
第二种是int* const p;表示指针常量,p指向的内容固定了,但是p本身,即p的指向可以改变。这就是指针与地址的大致介绍了。

二、角色互换

例1:有两个角色分别用变量a和b表示。为了实现角色互换,现制定了三套方案,通过函数调用来交换变量a和b的值,即swap1(),swap2()和swap3()。请分析在这三个函数中,哪个函数可以实现a和b的交换?三个函数如下:

void swap1(int x,int y){
	int t;
	t=x;
	x=y;
	y=t;
}
void swap2(int *x,int *y){
	int t;
	t=*x;
	*x=*y;
	*y=t;
}
void swap3(int *x,int *y){
	int* t;
	t=x;
	x=y;
	y=t;
}

本题的重点是指针作为函数参数。分析:第1个函数传的是形参,所以无法实现a和b的交换。第2个函数传的都是指针。t保存的是x所指向的变量,即实参。然后x所指向的变量被y所指向的变量替代,最后y所指向的变量被t保存的变量替代。所以能实现实参a和b的交换。第3个函数虽然传了指针,但是t保存的是指针x,而不是x指向的变量。所以后两句也都错了,该函数不能实现a和b的交换。综上,只有swap2()可以实现a和b的交换。

例2:输入年份和天数,输出对应的年、月、日。要求定义和调用函数
void month_day_year(int year,int yearday,int* pmonth,int* pday),
其中year是年,yearday是天数,*pmonth和*pday是计算得出的月和日。例如,输入2000和61,输出2000-3-1,即2000年的第61天是3月1日。分析:本题是上一节课例10的改版,大部分内容都一样。区别在于那题是输入日期,输出当年第几天。本题则是输入当年第几天,输出日期。由于那题的核心思想是迭代累加每月的天数,所以本题只需迭代累减每月的天数。具体是yearday每次减去当前月份的天数,直到小于当前月份的天数。比如闰年第50天,显然要减去1月的31天,剩余19天,也就是1月过后,再过19天。又19小于29,所以无需再减,第50天就是2月19日。这是算法,用C语言描述就要用到指针了,因为返回类型是void。改变实参只需对指针变量取地址即可,前面已讲,这里不作赘述。本题的重点显然是在函数内改变实参,我们来看一下代码:

#include<stdio.h>
void month_day_year(int year,int yearday,int *pmonth,int *pday);
int main(){
	int day,month,year,yearday;
	printf("输入年和天数:");
	scanf("%d %d",&year,&yearday);
	month_day_year(year,yearday,&month,&day);
	printf("%d-%d-%d\n",year,month,day);
	return 0;
}
void month_day_year(int year,int yearday,int *pmonth,int *pday){
	int k,leap;
	int tab[2][13]={
		{0,31,28,31,30,31,30,31,31,30,31,30,31},
		{0,31,29,31,30,31,30,31,31,30,31,30,31}
	};
	leap=((year%4==0&&year%100!=0)||year%400==0);
	for(k=1;yearday>tab[leap][k];k++)
		yearday-=tab[leap][k];
	*pmonth=k;
	*pday=yearday;
}

这里声明了一个函数month_day_year,需要传入四个参数。函数的大部分代码和那题一样,区别在于for循环的内容和后两句。这里传入的yearday,如果大于每月的天数,就减去每月的天数,最后利用指针获取月份和天数。最后的这个天数就是当月的第几天,获得的月份就是第几个月,我们来试一下:在这里插入图片描述
这就是本题的结果了。本题的关键还是改变实参,如果传入一个指针类型的变量,需要取地址,取到真正的变量,然后对真正的变量进行操作。注意,必须要用取地址才能取到真正的变量,如果没有星号,操作的只是一个参数副本,而且操作的只是地址本身,不是地址所指向的变量。所以需要用星号,根据地址取到真正的变量。这就是取地址的具体含义了。

三、冒泡排序

冒泡排序和选择排序一样,都是比较经典,比较重要的排序算法,希望大家好好掌握。例3:冒泡排序的思想是:遍历数组,每次遍历拿开头的元素和后面所有元素比较大小,若不符合递增或递减的顺序,则交换两个元素。输入n个整数,将它们以从小到大的方式冒泡排序后输出。分析:显然需要用二重循环,不妨用for循环,设待排序数组为a,外层循环变量为i,内层为j。由于a[j]要和后一个位置的元素比较,所以j的上限只能是n-2。由于排序算法也有两个可优化的地方。第一点是冒泡排序每一趟排序后,都会有一个最小元素,像水中的气泡那样冒泡到数组顶部,所以n-1趟排序后,最后一个元素自然排好,无需再排序。所以i从0到n-2。第二点是冒泡排序有个规律:每趟排序都可以少比较i次。所以j从0到n-i-2。交换想必大家已经非常熟悉了,不再赘述。排序完后再遍历一次数组,输出即可。本题的重点显然是冒泡排序,代码:

#include<stdio.h>
void bubbleSort(int a[],int n);
int main(){
	int n;
	printf("输入n:");
	scanf("%d",&n);
	int a[n],i;
	printf("输入%d个数:\n",n);
	for(i=0;i<n;i++)
		scanf("%d",&a[i]);
	bubbleSort(a,n);
	printf("排序后:\n");
	for(i=0;i<n;i++)
		printf("%d ",a[i]);
	return 0;
}
void bubbleSort(int *a,int n){//数组名=第一个单元的地址 
	int i,j;
	for(i=0;i<n-1;i++)
	for(j=0;j<n-1-i;j++)
	if(a[j]>a[j+1]){
		int temp=a[j];
		a[j]=a[j+1];
		a[j+1]=temp;
	}
}

这里把冒泡排序的代码封装成一个函数,便于调用。我们先来看冒泡排序的代码,首先如果要访问j+1,那么j最多到n-2,然后根据规律,每轮循环都可以少比较i次,所以j的上限可以是n-2-i,所以这里j就写成小于n-1-i。然后,如果当前元素比后一个元素大的话,就交换这两个元素,关于交换两个元素,在之前的课时中已经讲过。至于主函数的代码,想必大家都比较熟悉了。我们来试一下:在这里插入图片描述
这就是冒泡排序的结果了。这里要补充一个知识点,数组与指针,我在讲解数组的时候提到过,数组名也是一个地址,只不过是第一个单元的地址,所以传入参数有两种形式,一种是一个数组名再加上一对方括号,比如int a[]。还有种形式是传入一个指针,然后再加上数组名,比如int* a。这两种形式,都是传入第一个单元的地址,我们来试一下:在这里插入图片描述
它们的效果显然是一样的。

我们看下一题,例4:已知数组a[2],使用指针计算a的元素个数和a的存储单元数。分析:定义两个指针p,q,p指向数组首元素,q利用指针偏移指向数组尾元素。具体做法是,p=a;或者p=&a[0],q=p+1;。两个相同类型的指针相减,表示它们之间相隔的数组元素数目。所以p-q+1就是数组元素个数。数组元素个数乘上元素的数据类型占的字节数就是a的存储单元数。数据类型占的字节数用sizeof关键字获取。比如int类型用sizeof(int),就是int的字节数。本题的重点是相同类型的指针相减,代码:

#include<stdio.h>
int main(){
	double a[2],*p,*q;
	p=&a[0];
	q=p+1;
	printf("%d\n",q-p+1);
	printf("%d\n",(q-p+1)*sizeof(double));
	return 0;
}

首先取下第一个单元的地址,然后就在p的基础之上偏移一个单元,这样q减p就是1,即相差的元素个数,再加上1就是数组元素的个数。最后数组元素的个数乘上double的长度即可。我们来看一下结果:在这里插入图片描述
这就是本题的讲解了。

例5:输入10个整数作为数组元素,使用指针来计算并输出它们的和。分析:利用for循环,循环变量为指针p。因为a表示数组首地址,所以p从a开始。因为p+n相当于偏移n个元素。所以p的上限是p+10。显然,每次p只需自动增长1,即p偏移一个元素。题目要求一批整数的和,所以要定义sum来累加元素。sum每次累加*p,即p指向的元素,最后输出sum即可。我们来看一下代码:

#include<stdio.h>
int main(){
	int i,a[10],*p;
	int sum=0;
	printf("输入10个整数:\n");
	for(i=0;i<10;i++)
		scanf("%d",&a[i]);
	for(p=a;p<a+10;p++)
		sum+=*p;
	printf("它们的和是:%d\n",sum);
	return 0;
}

第二个循环的循环变量是指针p,从第一个单元的地址开始,直到第十个单元,这里其实可以发现,a+i其实就是a[i]的地址,a+i取地址就是a[i],如下:

	a+i=&a[i];
	*(a+i)=a[i];

因为a在前面说过,它是第一个单元的地址,所以如果左边是地址,那右边也必须是地址。如果右边是变量,则左边也得是变量,所以需要用星号取到实际的变量。循环体内累加元素,注意要用星号取地址。我们看一下结果:在这里插入图片描述
这就是本题的讲解了。

四、电码加密

例6:为了防止信息被别人轻易窃取,需要把电码明文通过加密方式变换成为密文。变换规则如下:小写字母z变换成a,其它字母变换成该字母ASCII码顺序后一位的字母,比如o变换成p。输入一个字符串,输出加密后的信息。分析:定义字符数组s[100],输入,这里用gets来接收。用*s来作处理。具体地,用while循环,结束条件是*s为结束符,每轮循环s自增1,表示往后偏移一个元素。然后根据题目,每轮循环用*s去判定即可,注意字母后移是*s+1,不是s+1。这里s +1是地址,而我们要操作的是数值,所以要用星号s取到真正的数值。本题的重点是gets函数,代码:

#include<stdio.h>
#include<string.h>
void encrypt(char*);
int main(){
	char line[100];
	printf("输入一个字符串:\n");
	gets(line);
	encrypt(line);
	printf("加密后:%s\n",line);
	return 0;
}
void encrypt(char s[]){
	while(*s!='\0'){
		if(*s=='z')
			*s='a';
		else
			*s+=1;
		s++;
	}
}

这里把加密的代码封装成了一个函数,函数接口我想大家应该没有问题,这在之前讲过。这里while循环的写法是比较简洁的,它其实就相当于这段代码:

	int i=0;
	while(s[i]!='\0'){
		if(s[i]=='z')
			s[i]='a';
		else
			s[i]+=1;
		i++;
	}

然后按照题目的要求赋值,操作即可,我们来验证一下:在这里插入图片描述
这就是本题的运行结果了。本题的重点是gets函数,gets函数用于获得一个整个字符串,所以如果定义了一个字符数组,用gets函数接收是最方便的。关于gets函数的其他具体内容,稍后会讲解。当然使用gets函数之前,还要声明一下string头文件。接下来是常用字符串函数:
??????????????????常用字符串函数
在这里插入图片描述

例7:输入5个字符串,利用字符串函数,得到最小的字符串并输出。分析:定义字符数组sx[80],smin[80]。其中sx用于输入,smin用于求最小串。在整数运算中,存储最小值的变量往往用赋值操作。而字符串是用strcpy函数来复制。在整数运算中,比较变量大小只需一个不等号即可。而字符串要用strcmp来比较。所以本题只需在整数运算算法的基础上,把赋值和比较操作改为字符串的相应操作即可。本题的重点是string copy函数,string compare函数,代码:

#include<stdio.h>
#include<string.h>
int main(){
	int i;
	char sx[80],smin[80];
	scanf("%s",sx);
	strcpy(smin,sx);
	for(i=1;i<5;i++){
		scanf("%s",sx);
		if(strcmp(sx,smin)<0)
			strcpy(smin,sx);
	}
	printf("min is %s\n",smin);
	return 0;
}

首先初始化最小值,smin代表最小字符串,用sx来初始化最小字符串。循环内,如果遇到比最小字符串smin还小的字符串,就更新smin,最后输出最小字符串即可。运行一下:在这里插入图片描述
好了,这就是全部例题的讲解了。

五、动态内存分配

首先要声明一个头文件stdlib,然后动态分配函数如下,注意unsigned类型是无符号整数,也就是非负数。
(1)动态分配函数malloc()
语法:void* malloc(unsigned size)
功能:申请在内存的堆区中连续分配一块size字节的空间。若申请成功,则返回指向所分配内存空间的起始地址的指针。若失败,则返回NULL。该函数具体使用时,需要强制转换为特定类型的指针。
例子:int* p=(int*)malloc(5*sizeof(int));
该语句定义了一个int类型的指针,指向一块存放int变量的空间,该空间大小是5个int大小,即20字节。
(2)动态分配函数calloc()
语法:void* calloc(unsigned n,unsigned size)
功能:和malloc差不多,唯一区别是分配空间的数据会全部初始化为0。
(3)动态释放函数free()
语法:void free(void* ptr)
功能:释放ptr指向的动态分配空间。若ptr为NULL,则什么都不做。
备注:为保证堆区(动态存储区)的有效利用,在知道某个动态分配块不再用时,应及时将它释放。
接下来讲一下C++动态内存分配,表格如下:
??????????????????C++动态内存分配
在这里插入图片描述

注意new和delete都是关键字,直接使用即可。

六、作业

接下来是作业,作业是6道例题加上3道练习题,总共9道题。练习1:在这里插入图片描述
第一道比较简单,用循环解决即可。练习2:在这里插入图片描述
这题有很多种做法,最常规的做法是暴力求解,即用二重循环来删除字符。还有种做法是开辟另一个数组来过滤字符。如果你要开辟另一个数组,就要让它的长度足够长,因为我在之前测试过,长度至少为65以后才能通过测试。当然还有其他几种高级的方法,比如说直接使用传入的字符串来处理,也就是s[k++]=s[i++]的形式。练习3:在这里插入图片描述
这题也比较简单,因为我们已经学习了string compare函数,我们也学习了两个元素交换的算法。在字符串交换中,是要用到string copy函数的,然后用冒泡排序或者选择排序都可以,好了,这就是本讲的全部内容了,我们下讲见!
课后作业和参考答案 提取码:l2du
讨论群号:1028887052

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-15 11:29:21  更:2022-05-15 11:29:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 18:31:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码