C语言学习——指针(定义、变量的指针和指向变量的指针变量)

news2024/11/15 12:23:51

目录

十、指针

10.1地址和指针的概念

10.2变量的指针和指向变量的指针变量

定义一个指针变量

指针变量的引用

指针变量作为函数参数


十、指针

10.1地址和指针的概念

我们要想了解什么是指针,就必须弄清楚数据在内存中是如何存储的,又是如何读取的。

如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。编译系统根据程序中定义的变量类型,分配一定长度的空间。例如,一般为整型变量分配2个字节,对单精度浮点型变量分配4个字节,对字符型变量分配1个字节。内存区的每一个字节都有一个编号,这就是“地址”,它相当于旅馆中的房间号。在地址所标志的内存单元中存放数据,这相当于旅馆房间中居住的旅客一样。

区别:内存单元的地址和内存单元的内容。

内存单元的地址是用来定位内存中的特定位置的,而内存单元的内容则是存储在那个位置上的实际数据。通过地址可以访问和修改内容,而内容则是程序和操作系统实际处理的数据。

在程序中,一般是通过变量名来对内存单元进行存取操作的。但其实程序经过编译后已经将变量名转换为变量的地址,对变量值的存区都是通过地址进行的

直接访问和间接访问的区别:

直接访问:指程序或硬件通过内存地址直接访问内存中的数据。访问过程简单快捷,因为地址直接指向数据所在的位置。例如:

int arr[10]; // 数组声明
arr[2] = 5;  // 直接访问数组第3个元素

间接访问:通过一个中间的指针或索引来访问内存中的数据。实际的数据位置由指针或索引间接确定。例如:

int arr[10];
int *ptr = arr;  // 指针指向数组
*(ptr + 2) = 5;  // 通过指针间接访问数组第3个元素

指针:一个变量的地址称为该变量的“指针”。

指针变量:用一个变量专门存放另一个变量的地址(即指针)。

指针变量的值(即指针变量中存放的值)是地址(即指针)。

指针是一个地址,而指针变量是存放地址的变量。

10.2变量的指针和指向变量的指针变量

变量的指针就是变量的地址。

存放变量地址的变量就是指针变量,它用来指向另一个变量。

为了表示指针变量和它所指向的变量之间的关系,在程序中用“ * ”符号表示“指向”的对象,如果已定义i_pointer为指针变量,则(*i_pointer)是i_pointer所指向的变量。如图:

可以看到,*i_pointer也代表一个变量,它和变量i是同一回事。

下面两个语句作用相同:

//1、
i = 3;
//2、
*i_pointer = 3;  //将3赋给指针变量i_pointer所指向的变量
定义一个指针变量

C语言规定所有变量在使用前必须定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是用来专门存放地址的,必须将它定义为“指针类型”。我们看看下面的这个例子:

int i, j;
int *pointer_1, *pointer_2;  //定义两个指针变量,指向整型变量的指针变量。

定义指针变量的一般形式为:

基本类型 *指针变量名;

下面都是合法的定义:

float *pointer_3;
char *pointer_4;

那么,怎样使一个指针变量指向另一个变量呢?可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向该变量。例如:

pointer_1 = &i;  //将变量i的地址存放到该指针中,因此pointer_1就“指向”了变量i
pointer_2 = &j;

在定义指针变量时要注意的两点:

  • 指针变量前面的“ * ”表示该变量的类型为指针型变量。指针变量名是pointer_1,pointer_2,而不是 *pointer_1, *pointer_2。这与定义整型或浮点型变量的形式不同的。

  • 在定义指针变量时必须指定其基本类型。因为不同类型的数据在内存中所占的字节数是不相同的,因此必须指定指针变量所指向的变量的类型,即基类型。一个指针变量只能指向同一个类型的变量,不能忽而指向一个整型变量,忽而指向一个实型变量。

需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。下面的赋值是错误的:

float a;  //定义a为float类型的变量
int *pointer_1;  //定义pointer_1为基类型为int的指针变量
pointer_1 = &a;  //将float型变量的地址放到指向整型变量的指针变量中,是错误的
指针变量的引用

牢记:指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量。下面的赋值是不合法的:

*pointer_1 = 100;  //pointer为指针变量 100为整数

两个相关的运算符:

  • &:取地址运算符。

  • *: 指针运算符。(或称“间接访问”运算符),取指针所指向的对象的内容。

例如,&a为变量a的地址,*p为指针变量p所指向的存储单元的内容(即p所指向的变量的值)。

通过指针变量访问整数变量。

#include<stdio.h>
​
int main()
{
    int a,b;
    int *pointer_1, *pointer_2;
    a = 100, b = 10;
    pointer_1 = &a;  //把变量a的地址赋给pointer_1 
    pointer_2 = &b;  // 把变量b的地址赋给pointer_2
    printf("%d,%d\n",a,b);
    printf("%d,%d\n",*pointer_1,*pointer_2);
    return 0;
 } 
 
//输出:
100,10
100,10 

对“&”和“*”运算符,再作说明:

如果已经执行了语句

pointer_1 = &a;

(1)&*pointer_1的含义是什么?

“&”和“ * ”两个运算符的优先级相同,但按自右而左方向结合,因此先进行pointer_1运算,它的变量就是a,再执行&运算。因此,&pointer_1与&a相同,即变量a的地址。

(2)*&a的含义是什么?

先进行&a运算,得到a的地址,再进行*运算,即&a所指向的变量,也就是变量a。 *&a和 *pointer_1的作用是一样的,它们都等价于变量a,即 *&a 与 a等价。

(3)(*pointer_1)++相当于a++。

注意括号是必要的,如果没有括号,就成为了*pointer_1++,我们知道,++和 * 为同一优先级别,而结合方向为自右而左,因此它相当于 *(pointer_1)++。由于++在pointer_1的右侧,是“后加”,因此先对pointer_1的原值进行 * 运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

输入a和b两个整数,按先大后小的顺序输出a和b。

#include<stdio.h>
​
int main()
{
    int *p1, *p2, *p, a, b;
    scanf("%d%d",&a,&b);  //输入 5 9
    p1 = &a; 
    p2 = &b;
    if(a<b)
    {
        p1 = &b;  //p1指向值大的b的地址 
        p2 = &a;
    }
    printf("a=%d,b=%d\n",a,b);          //输出:a=5,b=9
    printf("max=%d,min=%d\n",*p1,*p2);  //输出:max=9,min=5  
    return 0;
 } 
指针变量作为函数参数

函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

下面通过一个例子来说明:

题目要求同上一题,即对输入的两个整数按大小顺序输出。

现用函数处理,而且用指针类型的数据作函数的参数。

#include<stdio.h>
​
int main()
{
    void swap(int *p1, int *p2); 
    int a, b;
    int *pointer_1, *pointer_2;
    scanf("%d%d",&a,&b);  //输入 5 9
    pointer_1 = &a;  //将a的地址赋给指针变量pointer_1 ,使pointer_1指向a 
    pointer_2 = &b;  //将b的地址赋给指针变量pointer_2 ,使pointer_2指向b  
    if(a<b) swap(pointer_1,pointer_2);  //注意传递是指针变量,在函数调用时,将实参变量的值传送给形参变量  “值传递” 
    printf("max=%d,min=%d\n",a,b);          //输出:max=9,min=5
    return 0;
 } 
// 到这里 有 
//p1 和 pointer_1都指向变量a 
//p2 和 pointer_2都指向变量b
//接着执行swap函数的函数体 ,使 *p1 和 *p2 的值交换 
void swap(int *p1, int *p2)  //用户自定义函数 作用:交换两个变量a和b的值 
{
    int temp;
    temp = *p1;   // *p1就是a, 是整型变量 所以赋给的temp必须是整型变量 
    *p1 = *p2;
    *p2 = temp;
}
//函数调用后,形参p1和p2不复存在(已释放) 

注意,本例题采取的方法是交换a和b的值,而p1和p2的值不变。这与前一个例子刚好相反。

我们通过上面这个例子,可以知道在执行swap函数后,变量a和b的值都改变了。请仔细分析一下,这个改变是怎么实现的。这个改变不是通过将形参值传回实参来实现的。我们可以思考一下能否通过下面的函数来实现a和b的互换。

void swap(int x,int y)
{
    int temp;
    temp = x;
    x = y;
    y = temp;
}

如果在main函数中调用swap函数:

swap(a,b);

会有什么结果呢?

在函数调用时,a的值传给x,b的值传给y。执行完swap函数后,x和y的值是互换了,但未影响到a和b的值。

在函数结束时,变量x和y释放了,main函数中的a和b并未互换。

也就是说,由于“单向传递”的“值传递”方式,形参值的改变不能使实参值随之改变。

为了使在函数中改变了的变量值能被main函数所用,不能采取上述把要改变值的变量作为参数的方法,而应该用指针变量作为函数的参数,在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来,这样就实现了“通过调用函数使变量值发生变化,在主调函数(如main函数)中可以使用这些改变了的值”的目的。

如果想通过函数调用得到n个要改变的值,可以:

(1)在主调函数中设n个变量,用n个指针变量指向它们;

(2)然后将指针变量作实参,将这n个变量的地址传给所调用的函数的形参;

(3)通过形参指针变量,改变该n个变量的值;

(4)主调函数中就可以使用这些改变了值的变量。

可以按此思路,再看一遍上述的代码。

请注意,不能企图改变指针形参的值而使指针实参的值改变。可以看一下下面的程序:

#include<stdio.h>
​
int main()
{
    void swap(int *p1, int *p2); 
    int a, b;
    int *pointer_1, *pointer_2;
    scanf("%d%d",&a,&b);  //输入 5 9
    pointer_1 = &a;  
    pointer_2 = &b;  
    if(a<b) swap(pointer_1,pointer_2);  
    printf("max=%d,min=%d\n",*pointer_1,*pointer_2); //输出:max=5,min=9
    return 0;
 } 
​
void swap(int *p1, int *p2)  
{
    int *p;
    p = p1;   
    p1 = p2;
    p2 = p;
}

编写此程序的意图是:交换pointer_1和pointer_2的值,使pointer_1指向值大的变量,其设想是:

(1)先使用pointer_1指向a,pointer_2指向b;

(2)调用swap函数,将pointer_1的值传给p1,pointer_2的值传给p2;

(3)在swap函数中使p1与p2的值交换

(4)形参p1、p2将地址传回实参pointer_1和pointer_2,使pointer_1指向b,pointer_2指向a。然后输出 *pointer_1 和 *pointer_2,想实现两个值的交换。

但是,这是办不到的,假设我们输入“5 9”后,程序实际输出为“max=5,min=9”。

问题出现在第(4)步。

C语言中实参变量和形参变量之间的数据传递是单向的“值传递”的方式。指针变量作函数参数也要遵循这一规则。不可能通过调用函数来改变实参指针变量的值,但可以改变实参指针变量所指变量的值。

我们知道,函数的调用可以(而且只可以)得到一个返回值(即函数值),而运用指针作为参数,可以得到多个变化了的值。如果不用指针是难以做到这一点的。

最后,我们再通过这一道题的代码来检验一下我们对这一节知识点的掌握程度吧!

输入a、b、c这3个整数,按大小顺序输出。

#include<stdio.h>
​
int main()
{
    void exchange(int *q1,int *q2,int *q3);
    int a,b,c,*p1,*p2,*p3;
    scanf("%d%d%d",&a,&b,&c);  //5 2 9
    p1=&a;
    p2=&b;
    p3=&c;
    exchange(p1,p2,p3);
    printf("%d %d %d\n",a,b,c); //9 5 2
    return 0;
}
​
void exchange(int *q1,int *q2,int *q3)
{
    void swap(int *pt1,int *pt2);
    if(*q1<*q2) swap(q1,q2);
    if(*q1<*q3) swap(q1,q3);
    if(*q2<*q3) swap(q2,q3);
}
​
void swap(int *pt1,int *pt2)
{
    int temp;
    temp=*pt1;
    *pt1=*pt2;
    *pt2=temp;
}

声明:本文章为个人学习笔记,资料整理参考谭浩强《C程序设计(第三版)》如有错误,欢迎大家指正!

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

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

相关文章

利用EditPlus进行Json数据格式化

利用EditPlus进行Json数据格式化 git下载地址&#xff1a;https://github.com/michael-deve/CommonData-EditPlusTools.git (安装过editplus的直接将里面的json.js文件复制走就行) 命令&#xff1a;Cscript.exe /nologo “D:\Program Files (x86)\EditPlus 3\json.js” D:\P…

代码随想录算法训练营第三十天| 01背包问题 二维, 01背包问题 一维 , 416. 分割等和子集

今天是动态规划学习的第三天&#xff0c;主要的学习内容包括&#xff1a;01背包问题二维数组解法和一维数组解法&#xff0c;以及01背包问题的应用。 01背包问题 二维 题目链接&#xff1a;46. 携带研究材料&#xff08;第六期模拟笔试&#xff09; (kamacoder.com) 首先我们…

胡姓名人伟人有哪些?胡姓最厉害三个名人是谁

胡姓名人伟人有哪些?胡姓最厉害三个名人是谁? 在中国悠久的历史长河中,胡姓不仅承载着丰富的文化遗产,更是孕育出无数杰出的历史人物。这些人物以其独特的贡献和影响力,成为中华文明的重要组成部分。以下是根据历史影响力和文化贡献精心挑选的十大胡姓名人,他们的故事和成就展…

GNU/Linux - systemd介绍

systemd官网&#xff1a; System and Service Manager systemd systemd Github地址&#xff1a; https://github.com/systemd/systemd 首次发布 2010年3月30日 System and Service Manager systemd 是一套 Linux 系统的基本构件。它提供了一个系统和服务管理器&#xff0c;作为…

USB 2.0 协议专栏之 USB 配置描述符(四)

前言&#xff1a;本篇博客为手把手教学的 USB 2.0 协议栈类精品博客&#xff0c;该专栏博客侧重针对 USB 2.0 协议进行讲解。第 4 篇重点为 USB 2.0 协议中的配置描述符 Configuration Descriptors 进行讲解&#xff0c;并结合 CH32V307 与 STM32 代码进行 Configuration Descr…

【工业机器人】工业异常检测大模型AnomalyGPT

AnomalyGPT 工业异常检测视觉大模型AnomalyGPT AnomalyGPT: Detecting Industrial Anomalies using Large Vision-Language Models AnomalyGPT是一种基于大视觉语言模型&#xff08;LVLM&#xff09;的新型工业异常检测&#xff08;IAD&#xff09;方法。它利用LVLM的能力来理…

Oracle VM VirtualBox虚拟机内存不够用的解决方案

一、 前言 在使用Oracle VM VirtualBox虚拟机的过程中&#xff0c;随着时间的推移&#xff0c;我们会感觉我们的内存越来越不够用&#xff0c;今天就来给大家分享一下我们如何解决虚拟机内存不够用的问题。 二、解决方法 1.虚拟机碎片化整理 我们第一步要做的是碎片整理&…

【protobuf】ProtoBuf——proto3语法详解、enum类型、enum类型的使用和注意事项、Any类型、通讯录录入号码类型和地址的功能实现

文章目录 ProtoBuf5. proto3语法详解5.3 enum类型5.4 Any类型 ProtoBuf 5. proto3语法详解 5.3 enum类型 定义规则&#xff1a; proto3支持我们定义枚举类型并使用&#xff1a; 枚举类型的名称采用驼峰命名法且首字母大写&#xff0c;如 MyEnum &#xff0c;这样的命名方式符合…

重启人生计划-且随风行

&#x1f973;&#x1f973;&#x1f973; 茫茫人海千千万万&#xff0c;感谢这一刻你看到了我的文章&#xff0c;感谢观赏&#xff0c;大家好呀&#xff0c;我是最爱吃鱼罐头&#xff0c;大家可以叫鱼罐头呦~&#x1f973;&#x1f973;&#x1f973; 如果你觉得这个【重启人生…

Element UI详解

目录 Element UIElement UI 简介开发使用开发指南概述总结 设计原则组件使用特性使用场景优势不足 Element UI Element UI 简介 Element UI 是由饿了么前端团队开发的一套基于 Vue.js 的桌面端组件库。它提供了一系列丰富的 UI 组件&#xff0c;用于快速搭建企业级的 Web 应用…

RCE编码绕过--php://filter妙用

目录 代码 如何绕过 payload构造 代码 <?php $content <?php exit; ?>; $content . $_POST[txt]; file_put_contents($_POST[filename],$content); 当你想要输入代码的时候前面会有<?php exit;?>;&#xff0c;代码没有办法执行下去&#xff0c;所以…

day32+学习记录

一.算法练习 509.斐波那契数 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xf…

(待会删)分享9款一键生成原创论文在线使用软件

在当前的学术研究和写作环境中&#xff0c;AI技术的应用已经变得越来越普遍。其中&#xff0c;一键生成原创论文的在线软件更是为学者们提供了极大的便利。本文将重点介绍一款备受推荐的AI原创论文写作平台——千笔-AIPassPaPer&#xff0c;并分享其他几款优秀的同类软件。 千…

政务大数据解决方案(五)

政务大数据解决方案旨在通过建立统一的数据平台&#xff0c;将各政府部门的数据资源进行有效整合与智能分析&#xff0c;利用先进的数据处理和人工智能技术实现对社会动态的实时监测和精准预测&#xff0c;从而优化政府决策、提升公共服务效率和透明度。该方案涵盖数据的采集、…

每日OJ_牛客HJ75 公共子串计算

目录 牛客HJ75 公共子串计算 解析代码 牛客HJ75 公共子串计算 公共子串计算_牛客题霸_牛客网 解析代码 求最大公共子串&#xff0c;使用递推实现 假设 x(i)&#xff1a;字符串第i个字符 y(j)&#xff1a;字符串第j个字符 dp[i][j]&#xff1a;以x(i)&#xff0c;y(j)结尾的最…

XSS-games

XSS 1.XSS 漏洞简介2.XSS的原理3.XSS的攻击方式4.XSS-GAMESMa SpaghetJefffUgandan KnucklesRicardo MilosAh Thats HawtLigmaMafiaOk, BoomerWW3svg 1.XSS 漏洞简介 ​ XSS又叫CSS&#xff08;Cross Site Script&#xff09;跨站脚本攻击是指恶意攻击者往Web页面里插入恶意Sc…

XSS反射实战

目录 1.XSS向量编码 2.xss靶场训练&#xff08;easy&#xff09; 2.1第一关 2.2第二关 方法一 方法二 2.3第三关 2.4第四关 2.5第五关 2.6第六关 2.7第七关 第一种方法&#xff1a; 第二种方法&#xff1a; 第三个方法&#xff1a; 2.8第八关 1.XSS向量编码 &…

二叉树进阶之二叉搜索树:一切的根源

前言&#xff1a; 在学完了简单的容器与C面向对象的三大特性之后&#xff0c;我们首先接触的就是map与set两大容器&#xff0c;但是这两个容器底层实现的原理是什么呢&#xff1f;我们不而知&#xff0c;今天&#xff0c;主要来为学习map与set的底层原理而打好基础&#xff0c…

【精选】学生考勤管理系统设计与实现(源码+辅导+设计)

目录&#xff1a; 系统介绍&#xff1a; 第2章 开发技术介绍 2.1 B/S结构 2.2 Java语言 2.3 springboot框架 2.4 MySQL数据库 系统设计 系统的总体功能设计 系统实现界面&#xff1a; 3.视频演示 系统测试 测试概述 测试结果 参考代码&#xff1a; 为什么选择我&am…

【数据结构】PTA 单链表逆转 C语言

本题要求实现一个函数&#xff0c;将给定的单链表逆转。 函数接口定义&#xff1a; List Reverse( List L ); 其中List结构定义如下&#xff1a; typedef struct Node *PtrToNode; struct Node {ElementType Data; /* 存储结点数据 */PtrToNode Next; /* 指向下一个结点的…