C 程序设计教程(05)—— C 语言的数据类型(三):指针类型

news2024/11/18 11:46:15

C 程序设计教程(05)—— C 语言的数据类型(三):指针类型

该专栏主要介绍 C 语言的基本语法,作为《程序设计语言》课程的课件与参考资料,用于《程序设计语言》课程的教学,供入门级用户阅读。

目录

  • C 程序设计教程(05)—— C 语言的数据类型(三):指针类型
    • 一、指针与指针变量
        • 1、指针与指针变量的概念
        • 2、指针变量的定义
        • 3、指针变量的引用
        • 4、运算符 & 和 \*
    • 二、指针的算术运算
    • 三、指针与数组
    • 四、指针与结构体类型

指针是一个特殊的变量,它里面存储的数值被解释成为内存中的一个地址。有了指针技术,可以描述复杂的数据结构,对字符串的处理可以更灵活,对数组的处理更方便。

一、指针与指针变量

1、指针与指针变量的概念

在程序中定义的变量,编译系统为其分配相应的内存单元。也就是说,每个变量在内存中都会有固定的位置,有具体的地址。由于变量的数据类型不同,它所占用的内存单元数也不相同。可以使用 sizeof() 函数返回某种类型或某个变量占用的内存大小(单位为字节)。例如:

#include <stdio.h>
#include <string.h>
int main()
{
	int a=1,b=2;
	float x=3.2,y=4.7;
	double m=3.1415926;
	char ch1='a',ch2='b';
	printf("int类型的大小:%d ,%d\n",sizeof(a),sizeof(b));
	printf("float类型的大小:%d ,%d\n",sizeof(x),sizeof(y));
	printf("double类型的大小:%d\n",sizeof(m));
	printf("字符型的大小:%d ,%d\n",sizeof(ch1),sizeof(ch2));
	return 0;	
}

以上程序的运行结果如下:

在这里插入图片描述

访问变量时,首先找到该变量在内存的地址,这个地址称为指针。而用来保存地址的变量就是指针变量,通过指针变量对所指向内存的访问,就是“间接访问”。

例如:

#include <stdio.h>
#include <string.h>
int main()
{
	int a=1,*pa=&a;
	float x=4.7,*px=&x;
	double m=3.1415926,*pm=&m;
	char ch='a',*pch=&ch;
	printf("变量a的地址:%p, %p\n",pa,&a);
	printf("变量x的地址:%p, %p\n",px,&x);
	printf("变量m的地址:%p, %p\n",pm,&m);
	printf("变量ch的地址:%p, %p\n",pch,&ch);
	return 0;	
}

以上程序的运行结果如下:

在这里插入图片描述

2、指针变量的定义

定义指针变量的语法格式如下:

类型* 变量名;

以下代码表示定义三个指针变量 ptr1、ptr2、ptr3。其中 ptr1 可以指向一个整型变量,ptr2 可以指向一个实型变量,ptr3 可以指向一个字符变量。换句话说,ptr1、ptr2、ptr3 可以分别保存整型变量的地址、实型变量的地址、字符型变量的地址。

int* ptr1;
float* ptr2;
char* ptr3;
int m = 3;
float f = 4.5;
char ch = 'a';
ptr1 = &m;  //将变量m的地址赋给指针变量ptr1
ptr2 = &f;  //将变量f的地址赋给指针变量ptr2
ptr3 = &ch; //将变量ch的地址赋给指针变量ptr3

3、指针变量的引用

指针变量提供了对变量的一种间接访问形式。对指针变量的引用格式为:

*指针变量

例如:

#include <stdio.h>
#include <string.h>
int main()
{
	int* p;
	int m;
	printf("请输入变量m的值:");
	scanf("%d",&m);
	p=&m;   //指针变量p指向变量m 
	printf("指针所指向的变量m的值为:%d\n",*p); //对指针所指的变量的引用形式,与m意义相同 
	return 0;	
}

以上程序的运行结果如下:

在这里插入图片描述

4、运算符 & 和 *

(1)& 是取地址运算符。&a 的运算结果是一个指针,指针的类型是 a 的类型加个 *,指针所指向的类型是 a 的类型,指针所指向的地址就是 a 的地址。

(2)* 是间接运算符。*p 的运算结果就是 p 所指向的东西,这个东西有以下特点:它的类型是 p 指向的类型,它所占用的地址是 p 所指向的地址。

例如:

#include <stdio.h>
#include <string.h>
int main()
{
	int a=12; 
	int* p; 
	p = &a;   //&a的结果是一个指针,类型是 int*,指向的类型是 int,指向的地址是 a 的地址。
	*p = 24;  //*p 的结果类型是 int,它所占用的地址是p所指向的地址,显然,*p 就是变量 a。
	printf("%d,%d\n",*p,a);
	printf("%p,%p",p,&a);
	return 0;	
}

以上程序的输出结果如下:

在这里插入图片描述

从以上结果可以看出:指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为 sizeof(指针所指向的类型) 的一片内存区。

我们说一个指针的值是 XX,就相当于说该指针指向了以 XX 为首地址的一片内存区域。我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。

当我们遇到一个指针,都应该思考一下:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?

二、指针的算术运算

一个指针 pold 加(减)一个整数 n 后,结果是一个新的指针 pnew,pnew 的类型和 pold 类型相同,pnew 所指向的类型和 pold 所指向的类型也相同。pnew 的值将比 pold 的值增加(减少)n 乘 sizeof(pold 所指向的类型) 个字节。就是说,pnew 所指向的内存区将比 pold 所指向的内存区向高(低)地址方向移动了 n 乘 sizeof(ptrold 所指向的类型) 个字节。

指针和指针进行加减:两个指针不能进行加法运算,这是非法操作。两个指针可以进行减法操作,但必须类型相同,一般用于数组。

下面的例子中,指针 ptr 的类型是 int*,它指向的类型是 int,它被初始化为指向整型变量 a。当指针 ptr 加 1 以后,指针ptr 的值加上了 sizeof(int),即加了 4。ptr 所指向的地址由原来的变量 a 的地址向高地址方向增加了4 个字节。

#include <stdio.h>
#include <string.h>
int main()
{
	int a[5]={1,2,3,4,5}; 
    int* ptr = a;
    printf("指针的地址:%p,  指针指向的数组元素的值:%d\n",ptr,*ptr);
    ptr++; 
    printf("指针的地址:%p,  指针指向的数组元素的值:%d\n",ptr,*ptr);
	return 0;	
}

以上程序的输出结果如下:

在这里插入图片描述

可以使用指针和一个循环来遍历数组,例如:

#include <stdio.h>
#include <string.h>
int main()
{
	int arr[10]={0,1,2,3,4,5,6,7,8,9},i; 
    int* ptr = &arr[0];
    for (i=0;i<10;i++)
    {
    	(*ptr)++;
		printf("指针的地址:%p,  指针指向的数组元素的值:%d\n",ptr,*ptr);
    	printf("数组元素arr[%d]的值:%d\n",i,arr[i]);
    	ptr++;	
	}
	return 0;	
}

以上程序的输出结果如下:

在这里插入图片描述

三、指针与数组

数组的数组名可以看作一个指针。例如:

#include <stdio.h>
#include <string.h>
int main()
{
	int array[10]={0,1,2,3,4,5,6,7,8,9},value,pvalue; 
	value=array[0]; 
	pvalue=*array; 
	printf("array[0]的值:%d, *array的值:%d\n",array[0],*array);
	value=array[3]; 
	pvalue=*(array+3); 
	printf("array[3]的值:%d, *(array+3)的值:%d\n",array[3],*(array+3));
	value=array[4]; 
	pvalue=*(array+4);
	printf("array[4]的值:%d, *(array+4)的值:%d\n",array[4],*(array+4));
	return 0;	
}

以上程序的输出结果如下:

在这里插入图片描述

说明:上例中的数组名 array 代表数组本身,类型是int[10]。如果把 array 看做指针的话,它指向数组的第 0 个单元,类型是 int*,所指向的类型是数组单元的类型即 int。因此 *array 等于0。同理,array+3 是一个指向数组第3 个单元的指针,所以 *(array+3) 等于 3。

关于数组的数组名的总结:以数组 int array[10] 为例,则数组名称 array 有两层含义:

(1)它代表整个数组,它的类型是 int[10];

(2)它是一个【常量指针】,该指针的类型是 int*,该指针指向的类型是 int,该指针指向的内存区就是数组第 0 号单元,该指针自己占有单独的内存区,注意它和数组第 0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似 array++ 的表达式是错误的。

(3)在不同的表达式中数组名 array 可以扮演不同的角色。

在表达式 sizeof(array) 中,数组名 array 代表数组本身,这时sizeof 函数测出的是整个数组的大小。

在表达式 *array 中,array 扮演的是指针,因此这个表达式的结果就是数组第 0 号单元的值。sizeof(*array) 测出的是数组单元的大小。

表达式 array+n(n=0,1,2,…)中,array 扮演的是指针,故 array+n 的结果是一个指针,它的类型是 int*,它指向的类型是 int,它指向数组第 n 号单元。故 sizeof(array+n) 测出的是指针类型的大小。

例如,有如下代码:

#include <stdio.h>
#include <string.h>
int main()
{
	int array[5]={1,2,3,4,5},a=1,*p=&a; 
	printf("指针p的大小:%d\n",sizeof(p));//指针类型的大小 
	printf("*array的值:%d\n",*array); //第一个数组元素的值 
	printf("array的大小:%d\n",sizeof(array));//整个数组的大小 
	printf("指针array+1的大小:%d\n",sizeof(array+1));//指针类型的大小
	printf("指针array+2的大小:%d\n",sizeof(array+2));//指针类型的大小
	printf("array的地址:%p\n",array); //第一个数组元素的地址 
	printf("array的地址:%p\n",&array[0]);//第一个数组元素的地址
	printf("*array的大小:%d\n",sizeof(*array));//第一个数组元素的大小 
	return 0;	
}

以上程序的输出结果如下:

在这里插入图片描述

四、指针与结构体类型

如果需要访问结构体对象的成员,分两种情况:

(1)如果访问的是结构体变量的成员,采用【变量名.成员名】的格式。

(2)如果访问的是结构体指针指向的结构体成员,采用【变量名->成员名】的格式。

例如:

#include <stdio.h>
#include <string.h>
int main()
{
	struct MyStruct 
 	{ 
 		int a; 
 		int b; 
 		int c; 
 	}; 
	struct MyStruct s = {20,30,40}; 
	struct MyStruct* ptr = &s; //声明一个指向结构对象s的指针。类型是MyStruct*,它指向的类型是MyStruct。
	printf("访问结构体变量的成员:%d,%d,%d\n",s.a,s.b,s.c); 
	printf("访问结构体指针的成员:%d,%d,%d\n",ptr->a,ptr->b,ptr->c); 
} 

以上程序的输出结果如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/148277.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySql中json类型数据的查询以及在MyBatis-Plus中的使用

表结构和初始数据 新建表结构 CREATE TABLE json_test (id int NOT NULL AUTO_INCREMENT,roles json DEFAULT NULL COMMENT 角色,project json DEFAULT NULL COMMENT 项目,PRIMARY KEY (id) ) ENGINEInnoDB;初始数据 INSERT INTO ctts_dev.json_test(id, roles, project) VALU…

SpringBoot 整合 xxl-job

文章目录部署 xxl-jobSpringBoot 配置maven 配置application.yaml配置 XxlJobConfigXxlJobSpringExecutor新建执行任务配置 xxl-job-admin执行器管理任务管理部署 xxl-job K8S 部署 xxl-job 参考文档&#xff1a;https://blog.csdn.net/weixin_42555971/article/details/12489…

【Web开发】Python实现Web服务器(Docker下部署Flask)

&#x1f37a;基于Python的Web服务器系列相关文章编写如下&#x1f37a;&#xff1a; &#x1f388;【Web开发】Python实现Web服务器&#xff08;Flask快速入门&#xff09;&#x1f388;&#x1f388;【Web开发】Python实现Web服务器&#xff08;Flask案例测试&#xff09;&a…

分享112个PHP源码,总有一款适合您

PHP源码 分享112个PHP源码&#xff0c;总有一款适合您 链接&#xff1a;https://pan.baidu.com/s/1MaBtjYZk08o0eJT5_E79aQ?pwduldm 提取码&#xff1a;uldm 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c;大家下载…

实战丨从0到1搭建结算平台

一、概述我们最开始分享了O2O电商支付清结算体系&#xff0c;接着分享了如何从0-1搭建计费体系&#xff0c;接下来我们分享&#xff1a;各方的钱算完之后怎么付出去&#xff0c;也即结算平台建设的实操与设计思路。1.什么是结算&#xff1f;说结算平台之前&#xff0c;先说一下…

22年 | 年前总结 | 主业谋生存,副业谋发展

22年关键词 复盘 | 极简 | 长期主义 | 阅读 | 斜杠青年 | 一事无成 | … 当然了&#xff0c;2023也会继续延续某些关键词。 一壶清酒&#xff0c;敬这红尘也敬我 很多人都在说&#xff0c;疫情存在的时间比任何一段恋情还要长。 而我想说&#xff0c;我失败的次数还超过了做…

Java设计模式中策略模式是怎么回事/怎么替代繁琐if-else语句/如何优化条件选择语句

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 6.3 策略模式 6.3.1 概述 定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替代&#xff0c;且算法的变化不会影响使用算法的用户属…

【自学Python】Python注释

文章来源嗨客网&#xff08;www.haicoder.net&#xff09; Python注释 Python注释教程 用于注解说明解释程序的文字就是注释&#xff0c;注释提高了代码的阅读性。一旦程序中某部分内容被注释掉&#xff0c;则该内容将会被 Python 解释器忽略&#xff0c;换句话说&#xff0c…

一般颜色直方图

颜色直方图是一种用于图像处理和分析的图表&#xff0c;它可以显示图像中不同颜色的数量。通常&#xff0c;颜色直方图会将颜色分成几个色调区间&#xff0c;每个区间对应一个条形图&#xff0c;其中条形图的高度表示该色调区间中的像素数量。通过颜色直方图&#xff0c;你可以…

Mask RCNN网络源码解读(Ⅰ) --- 语义分割前言与转置卷积

目录 1.什么是语义分割 2.语义分割常见的数据集格式 3.常见的语义分割评价指标 4.转置卷积 1.什么是语义分割 常见分割任务&#xff1a;语义分割、实例分割、全景分割 图一 原始图片图二 语义分割图三 实例分割语义分割&#xff08;例如FCN网络&#xff09;可以理解为一个…

开发与项目经理之间的打情骂俏——数据库篇

&#x1f466;&#x1f466;一个帅气的boy&#xff0c;你可以叫我Love And Program &#x1f5b1; ⌨个人主页&#xff1a;Love And Program的个人主页 &#x1f496;&#x1f496;如果对你有帮助的话希望三连&#x1f4a8;&#x1f4a8;支持一下博主 由数据库引发的一系列探…

小论文写作指南(AI类)

参考b站沃恩智慧课程 论文结构 标题:不要太长或太短,抓住重点,简明扼要。 作者:你的作品一定力争一作,通讯作者是导师/大老板/出资人。 摘要Abstract:点明大背景(如为什么研究微表情识别,对社会有什么价值,拔高立意层次),阐述目标(我们提出模型为了在什么问题上达…

9个时间序列交叉验证方法的介绍和对比

评估性能对预测模型的开发至关重要。交叉验证是一种流行的技术。但是在处理时间序列时&#xff0c;应该确保交叉验证处理了数据的时间依赖性质。在之前的文章中&#xff0c;我们也做过相应的介绍。 在本文中&#xff0c;我们收集了时间序列的常用的9种交叉验证方法。这些包括样…

【博客578】LVS NAT配合MASQUERADE实现FULLNAT的场景,及此场景下net.ipv4.vs.conntrack参数的重要作用

LVS NAT配合MASQUERADE实现FULLNAT的场景&#xff0c;及此场景下net.ipv4.vs.conntrack参数的重要作用 1、LVS基本原理&#xff1a; 流程&#xff1a; 当用户向负载均衡调度器&#xff08;Director Server&#xff09;发起请求&#xff0c;调度器将请求发往至内核空间 PREROU…

第6章 线程通信

6.2.1 管道 管道是一个线性字节数组,类似文件,使用文件读写进行访问&#xff1b;在程序里面,创建管道需要使用popen()或者pipe(); 管道的一个重要特点是使用管道的两个线程之间必须存在某种关系, 例如,使用popen需要提供另一端进程的文件名,使用pipe的两个线程分别隶属于父子进…

Linux常用命令——fgrep命令

在线Linux命令查询工具 fgrep 为文件搜索文字字符串 补充说明 fgrep命令是用来搜索 file 参数指定的输入文件&#xff08;缺省为标准输入&#xff09;中的匹配模式的行。fgrep 命令特别搜索 Pattern 参数&#xff0c;它们是固定的字符串。如果在 File 参数中指定一个以上的…

dp刷题(三)编辑距离(Hard)

编辑距离_牛客题霸_牛客网 描述 给定两个单词word1和word2&#xff0c;请计算将word1转换为word2至少需要多少步操作。 你可以对一个单词执行以下3种操作&#xff1a; a&#xff09;在单词中插入一个字符 b&#xff09;删除单词中的一个字符 c&#xff09;替换单词中的一个字…

C#,图像二值化(14)——全局阈值的最佳迭代算法(Iterate Thresholding)及源代码

1、图像二值化 图像二值化是将彩色图像转换为黑白图像。大多数计算机视觉应用程序将图片转换为二进制表示。图像越是未经处理&#xff0c;计算机就越容易解释其基本特征。 二值化过程 在计算机存储器中&#xff0c;所有文件通常以灰度级的形式存储&#xff0c;灰度级具有从0…

Vue基础入门小demo——记事本

文章目录 &#x1f4cb;前言 &#x1f3af;demo介绍 &#x1f3af;完整代码 &#x1f3af;最终效果 &#x1f3af;案例分析 &#x1f4cb;前言 记事本&#xff08;不是操作系统的那个记事本&#xff0c;是一个简单的网页版本记事本&#xff09;是一个较全面的Vue指令集合案…

迁移学习简要

什么是迁移学习 迁移学习是一种机器学习方法&#xff0c;就是把任务为A的开发模型作为其的初始点&#xff0c;重新使用在任务为B的开发模型的过程中。迁移学习是通过从已学习的相关任务中转移知识来改进学习的新任务。虽然大多数机器学习的新 算法都是为了解决单个任务而设计的…