C语言基础篇5:指针(一)

        指针是C语言的核心、精髓所在,用好了指针可以在C语言编程中起到事半功倍的效果。指针一方面可以提高程序的编译效率和执行速度,而且还可以通过指针实现动态的存储分配,另一方面使用指针可使程序更灵活,便于表示各种数据结构,编写高质量的程序。同时,其抽象概念,学习过程中要多看多练,使用时应多注意,否则操作不当会导致整个程序收到破坏。

1 指针相关概念

        指针是C语言最显著的优点之一,指针使用起来十分灵活而且能提高某些程序的效率,但是指针使用不当,会很容易造成系统错误,往往许多程序“挂死”的大部分原因都是错误地使用指针所造成的。

1.1 地址与指针

        系统的内存就像带有编号的房间,如果想使用内存就需要得到房间编号。例如,定义一个整型变量i,整型变量需要4个字节,所以编译器为变量i分配编号从1000~1003。

        什么是地址?地址就是内存区中对每个字节的编号,例如上面的1000~1003就是地址。

        什么是指针?这里仅把指针看作是内存的一个地址,多数情况下,这个地址是内存中另一个变量的位置,如下图:

        上图中定义了一个变量,在进行编译时就会给这个变量在内存中分配一个地址,通过访问这个地址就可以找到所需的变量,该变量的地址称为该变量的指针。上图中的地址1000就是变量i的指针。

【说明】在C语言中,存取变量值的方法有两种。按变量地址存取变量的方式称为“直接访问”;将变量地址存放在另一个变量中,先找到存放“变量地址”的另一个变量,通过另一个变量找到变量的地址,这种方法称为“间接访问”。

1.2 指针变量

1.2.1 变量与指针

        变量的地址是变量和指针两者之间连接的纽带,如果一个变量包含了另一个变量的地址,那么,第一个变量可以说成是指向第二个变量。所谓“指向”就是通过地址来体现的,在程序中用“*”表示指向。因为指针变量是指向一个变量的地址,所以将一个变量的地址值赋给这个指针变量后,这个指针变量就“指向”了该变量。例如,将变量i地址存放到指针变量p中,p就指向i。在程序代码中是通过变量名来对内存单元进行存取操作的,但是代码经过编译后已经将变量名转换成该变量在内存的存放地址,对变量值的存取都是通过地址进行的。例如,对变量i和j进行如下操作:

    int i,j;
    i + j;

        其含义根据变量名与地址的对应关系,找到变量i的地址1000,然后从1000开始读取4个字节数据放到CPU寄存器中,在找到变量j的地址1004,从1004开始读取4个字节的数据放到CPU的另一个寄存器中,通过CPU计算出结果。     

1.2.2 使用指针变量

        由于通过地址能访问指定的内存存储单元,可以说是地址“指向”该内存单元。地址可以称之为指针,意思是通过指针就能找到内存单元。一个变量的地址称为该变量的指针。如果有一个变量专门用来存放另一个变量的地址,它就是指针变量。在C语言中有专门用来存放内存单元地址的变量类型,就是指针类型。

        指针变量的一般形式:类型说明 * 变量名。其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明本指针变量所指向的变量的数据类型。

        指针变量的赋值,指针变量同普通变量一样,使用之前不仅要定义,而且必须赋予具体的值。未经赋值的指针变量不能使用。给指针变量所赋的值与给其他变量所赋的值不同,给指针变量的赋值只能赋予地址,而不能赋予任何其他数据,否则将引起错误。C语言提供了地址运算符“&”来表示变量的地址。一般形式为:& 变量名。例如,&a表示变量a的地址,&b表示变量b的地址。给一个指针变量赋值可以有两种方法。

        1、定义指针变量的同时进行赋值:int a;int *p = &a;

        2、先定义指针变量,之后在赋值:int a;int *p ;p = &a;

        【注意】两者的区别,如果在定义完指针变量之后再赋值,注意不要加*。

        【示例1】利用指针输出数据。

        

#include <stdio.h>
int main(){
    int a,b;
    //声明两个指针变量
    int *ip1,*ip2;
    printf("请输入苹果和香蕉的价格 \n");
    scanf("%d,%d",&a,&b);
    ip1 = &a;
    ip2 = &b;
    printf("苹果的价格是: %d/一斤 \n",*ip1);
    printf("香蕉的价格是:%d/一斤 \n",*ip2);
    return 0;
}

    

        指针变量的引用

        引用指针变量是对变量进行间接访问的一种形式。对指针变量的引用形式如下:

        * 指针变量。其含义是引用指针变量所指向的值。

        【示例2】利用指针变量实现数据的输入、输出。

#include <stdio.h>
int main(){
    int *p,q;
    printf("请输入香蕉的价格: \n");
    scanf("%d",&q);
    p = &q;
    printf("香蕉的价格是:%d 元一斤\n",*p);
    return 0;
}

 

1.3 & 和  * 运算符

        上面介绍指针变量的过程中用到了两个运算符& 和 * ,& 是一个返回操作数地址的单目运算符,叫做取地址运算符,例如:p = &a。就是把变量a的内存地址赋给p,这个地址是该变量在计算机内部存储的地址。

        * 是单目运算符,叫做指针运算符,作用是返回指定地址内变量的值,如上面提到的p中装有变量a的内存地址,则 q = *p。就是把变量a的值赋给q,假如a = 5,那么q = 5.

        指针运算符和取地址运算符可以组合使用,那么&*和*&有什么区别呢?有如下语句:

int a;
p = &a;

        通过以上两个语句来发分析 &* 和 *& 之间的区别,&和*的运算符优先级别相同,按自右而左的方向结合。因此&*先计算*运算,*p相当于变量a;再进行&运算,&*p就相当于取变量a的地址。*&a先计算&运算,&a就是取变量a的地址,再进行*计算,*&a相当于取变量a所在地址的值,实际就是a变量。

【示例1】 &* 应用。

#include <stdio.h>

int main(int argc, const char * argv[]) {
    long i;
    long *p;
    printf("请输入一个数值:\n");
    scanf("%ld",&i);
    p = &i;
    printf("输出&*p结果是:%ld\n",&*p);
    printf("输出&i结果是:%ld\n",&i);
    return 0;
}

   

 【示例2】*& 应用

#include <stdio.h>

int main(int argc, const char * argv[]) {
    long i, *p;
    printf("请输入一个数值:\n");
    scanf("%ld",&i);
    p = &i;
    printf("输出*&i的结果是:%ld\n",*&i);
    printf("输出i的结果是:%ld\n",i);
    printf("输出*p的结果是:%ld\n",*p);
    return 0;
}

        其中,示例1:&*p和&i的作用相同,都是取变量i的地址 。

                示例2:*&p和*p相同,取变量的值。

1.4 指针的算术运算

        指针有加减两种运算,即指针的自加和自减,这不同于普通变量的自加自减运算,并不是简单的加1或减1。

【示例1】整型变量地址输出

#include <stdio.h>

int main(int argc, const char * argv[]) {
    int i;
    int *p;
    printf("请输入一个数值:\n");
    scanf("%d",&i);
    p = &i;
    printf("p的结果是:%d\n",p);
    p++;
    printf("p的结果是:%d\n",p);
    return 0;
}

        把上面代码的变量修改为short变量,再次执行。

#include <stdio.h>

int main(int argc, const char * argv[]) {
    short i;
    short *p;
    printf("请输入一个数值:\n");
    scanf("%d",&i);
    p = &i;
    printf("p的结果是:%d\n",p);
    p++;
    printf("p的结果是:%d\n",p);
    return 0;
}

 

        从两个示例可以看出,这里的指针自加不是简单的在地址上加1,而是指向下一个存放基本整型数的地址,第一个示例的变量是基本整型,所以p++后,p的值增加4(基本整型占4个字节);第二个示例的变量定义为短整型,所以p++后p的值加2(短整型占2个字节)。

        【结论】 指针都是按照它所指向的数据类型的直接长度进行加减的。

【范例1】转向的指针:通过交换两个指针变量的值取改变指针的方向。

#include <stdio.h>

int main(void) {
    int *p1,*p2,a,b,*t;
    printf("请输入a,b的值\n");
    scanf("%d,%d",&a,&b);
    p1 = &a;
    p2 = &b;
    if(*p1 < *p2){
        t = p1;
        p1 = p2;
        p2 = t;
    }
    printf("%d > %d\n",*p1,*p2);
    return 0;
}

2 一维数组与指针

        数组在内存中存放也同样具有地址。 对于数组来说,数据名就是数组在内存中存储的首地址。指针变量用于存放变量的地址,自然也可以存放数组的首地址或数组元素的地址,这样就给数组和指针之间建立了一个联系。

2.1 指向数组元素的指针

        当定义了一个一维数组时,系统会在内存中为该数组分配一个存储空间。其数组的名字就是数组在内存的首地址。如果在定义一个指针变量,并将数组的首地址传给指针变量,则该指针就指向了这个一维数组。如:int *p,a[10]; p = a;

        这里a是数组名,也就是数组的首地址,将它赋给指针变量p,也就是将数组a的首地址赋给p,也可以写成:int *p,a[10]; p = &a[0]。

【注意】在将数组名赋给指针变量时不需要写“&”,但是将数组首地址赋给指针变量时,需要加上“&”。

【示例2.1.1】输出数组的元素。

#include <stdio.h>

int main(void) {
    int *p,*q,a[5],b[5],i;
    p = &a[0];
    q = b;
    printf("请输入数组a中的元素:\n");
    for(i = 0;i<5;i++){
        scanf("%d",&a[i]);
    }
    printf("请输入数组b中的元素:\n");
    for(i = 0;i<5;i++){
        scanf("%d",&b[i]);
    }
    printf("数组a的元素是:\n");
    for(i = 0;i<5;i++){
        printf("%5d",*(p+i));
    }
    printf("\n");
    printf("数组b的元素是“\n");
    for( i =0;i<5;i++){
        printf("%5d",*(q+i));
    }
    printf("\n");
}

2.2 使用指针访问数组

        对于一维数组的引用有两种方法:一种是下标法,另一种是指针法。下标法是采用a[i]的形式引用数组中的元素,而指针法时本篇主要介绍的,首先看下面两条语句:int *p,a[10]; p = &a;

        上面的语句将作以下几方面介绍:

        1、p + n 与 a + n表示数组元素a[n]的地址,即&a[n]。对整个a数组来说,共有10个元素,n的取值是0~9,则数组元素的地址就可以表示为p+0~p+9或者a+0~a+9,如下图 

        2、如何来表示数组中的元素用到了前面介绍的数组元素的地址,用*(p+n)和*(a+n)来表示数组中的各个元素。例如下面的语句:printf("%10d",*(p+i)) ;printf("%10d",*(q+i));分别表示输出数组a和数组b中对应的元素。

        上面提到可以用a+n表示数组元素的地址,*(a+n)表示数组元素,那么就可以把上面的程序进行改造:

#include <stdio.h>

int main(void) {
    int *p,*q,a[5],b[5],i;
    p = &a[0];
    q = b;
    printf("请输入数组a中的元素:\n");
    for(i = 0;i<5;i++){
        scanf("%d",&a[i]);
    }
    printf("请输入数组b中的元素:\n");
    for(i = 0;i<5;i++){
        scanf("%d",&b[i]);
    }
    printf("数组a的元素是:\n");
    for(i = 0;i<5;i++){
        printf("%5d",*(a+i));
    }
    printf("\n");
    printf("数组b的元素是“\n");
    for( i =0;i<5;i++){
        printf("%5d",*(b+i));
    }
    printf("\n");
}

        3、表示指针的移动可以使用”++“和”--“运算符,利用”++“运算符可以把上面的程序再次改造

#include <stdio.h>

int main(void) {
    int *p,*q,a[5],b[5],i;
    p = &a[0];
    q = b;
    printf("请输入数组a中的元素:\n");
    for(i = 0;i<5;i++){
        scanf("%d",p++);
    }
    printf("请输入数组b中的元素:\n");
    for(i = 0;i<5;i++){
        scanf("%d",q++);
    }
    p = a;
    q = b;
    printf("数组a的元素是:\n");
    for(i = 0;i<5;i++){
        printf("%5d",*p++);
    }
    printf("\n");
    printf("数组b的元素是“\n");
    for( i =0;i<5;i++){
        printf("%5d",*q++);
    }
    printf("\n");
}

【示例2.1】查找数列中的最值。输入10个整型数字,自动查找这些数中的最大值和最小值。

#include <stdio.h>
void max_min(int a[],int n,int *max,int *min){
    int *p;
    *max = *min = *a;
    for(p = a + 1;p < a + n; p ++){
        if(*p > *max){
            *max = *p;
        }else if(*p< *min){
            *min = *p;
        }
    }
}

int main(void) {
    
    int i,a[10];
    int max,min;
    printf("请输入10个整数:\n");
    for(i = 0;i<10;i++){
        scanf("%d",&a[i]);
    }
    max_min(a,10,&max,&min);
    printf("最大值是:%d\n",max);
    printf("最小值是:%d\n",min);
    getchar();
    
}

 

3 字符串与指针

        字符串常量是由双引号组成的字符序列,表示字符串可以用字符数组,即通过数组名来表示字符串,数组名就是数组的首地址,是字符串的起始地址。现在将字符串数组的名赋予一个指向字符类型的指针变量,让字符类型指针指向字符串在内存的首地址,对字符串的表示就可以用指针实现。

3.1 字符型指针

        字符型指针就是指向字符型内存空间的指针变量,一般形式为:char *p ,使用字符型指针可以访问字符串。

【示例3.1】字符型指针

#include <stdio.h>

int main(int argc, const char * argv[]) {
    char *string = "生当作人杰,死亦为鬼雄\n";
    printf("%s\n",string);
    return 0;
}

【示例3.1.1】声明两个字符数组,将str1中的字符串复制到str2中

#include <stdio.h>

int main(int argc, const char * argv[]) {
    char str1[]="生当作人杰,死亦为鬼雄",str2[15],*p1,*p2;
    p1 = str1;
    p2 = str2;
    while(*p1 != '\0'){
        *p2 = *p1;
        p1 ++;
        p2 ++;
    }
    *p2 = '\0';
    printf("第二个字符串的内容是:\n");
    puts(str2);
    return 0;
}

 

3.2 字符串数组

        这里提到的字符串数组与前面提到的字符数组不同,字符数组是一个一维数组,而字符串数组是以字符串作为数组元素的数组,可以将其看成一个二维字符数组。例如下面一个简单的字符串数组定义:

char country[5][20] =
    {
        "中国",
        "美国",
        "俄罗斯",
        "英国",
        "法国"
    }

        字符型数组变量country被定义为含有5个字符串的数组,每个字符串的长度要小于20(要考虑字符串最后的'\0')。通过观察上面定义的字符串数组会发现,元素的长度远远小于其定义的20个字节的空间,这样会造成空间浪费。为了解决这个问题,可以使用指针数组,每个指针指向所需要的字符常量,这种方法虽然需要再数组中保存字符指针,同样也占用空间,但要远少于字符串数组所需要的空间。 

        那么什么是指针数组呢?一个数组,其元素均为指针类型数据,成为指针数组。也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式:

类型名   数组名[数组长度]

        【例3.2.1】输出一周7天

#include <stdio.h>

int main() {
    int i;
    char *month[] = {
        "星期一",
        "星期二",
        "星期三",
        "星期四",
        "星期五",
        "星期六",
        "星期日"
    };
    for(i = 0;i<7;i++){
        printf("%s \n",month[i]);
    }
    return 0;
    
   
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习.NET验证模块FluentValidation的基本用法(续3:ASP.NET Core中的调用方式)

FluentValidation模块支持在ASP.NET Core项目中进行手工或自动验证&#xff0c;主要验证方式包括以下三种&#xff1a;   1&#xff09;手工注册验证类&#xff0c;并在控制器或其它模块中调用验证&#xff1b;   2&#xff09;基于ASP.NET验证管道&#xff08;validation …

CountDownLatch实战应用——批量数据多线程协调异步处理(主线程执行事务回滚)

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; CountDownLatch实战应用——批量数据多线程协调异步处理(主线程执行事务…

基于SpringBoot的超市信息管理系

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着我国经济的不断发…

送PDF书 | 豆瓣9.2分,超250万Python新手的选择!蟒蛇书入门到实践

在此疾速成长的科技元年&#xff0c;编程就像是许多人通往无限可能世界的门票。而在编程语言的明星阵容中&#xff0c;Python就像是那位独领风 骚的超级巨星&#xff0c; 以其简洁易懂的语法和强大的功能&#xff0c;脱颖而出&#xff0c;成为全球最炙手可热的编程语言之一。 …

【第二节:微信小程序 app.json配置】微信小程序入门,以思维导图的方式展开2

以思维导图的方式呈现出来&#xff0c;是不是会更加直观一些呢 如果看不清楚&#xff0c;私信给单发 &#xff1a; 第二节&#xff1a;微信小程序 app.json配置&#xff1a; 包括&#xff1a; window pages tabBar networkTimeout debug 如下图所示&#xff1a; 2、ap…

使用 OpenCV 发现圆角矩形的轮廓

OpenCV - 如何找到圆角矩形的矩形轮廓? 问题: 在图像中,我试图找到矩形对象的圆角轮廓。然而,我对两者的尝试 HoughLinesP 并 findContours 没有产生预期的结果。 我的目标是找到一个类似于以下形状的矩形: 。 代码: import cv2 import matplotlib.pyplot as plt…

亚信科技AntDB数据库与库瀚存储方案完成兼容性互认证

近日&#xff0c;亚信科技AntDB数据库与苏州库瀚信息科技有限公司自主研发的RISC-V数据库存储解决方案进行了产品兼容测试。经过双方团队的严格测试&#xff0c;亚信科技AntDB数据库与库瀚数据库存储解决方案完全兼容、运行稳定。除高可用性测试外&#xff0c;双方进一步开展TP…

[C/C++]数据结构 堆排序(详细图解)

一:前言 在[C/C]数据结构 堆的详解中,介绍了什么是堆,并且完成了堆的实现和一系列接口,包括向上调整法和向下调整法等,接下来小编介绍一个有点量级的排序方法------堆排序,时间复杂度为O(n*lgn) 二:堆排序详解 2.1 方法介绍 1.首先将待排序数组建为大堆,此时堆顶元素就为数组…

Linux操作系统 1.初识Linux

一、Linux学习大致内容 二、操作系统概述 操作系统的作用&#xff1a; 常见操作系统&#xff1a; 1、pc&#xff08;电脑端&#xff09;&#xff1a;windows、Linux、MacOS 2、移动端&#xff1a;Android、ios、鸿蒙系统 总结 1.计算机由哪两个部分组成&#xff1f;、 硬件…

04:2440---内存控制器

目录 一:介绍 1:引入 2:概念 3:通信 A:片选信号 B:片选信号的地址空间范围 ​​​​ 4:地址线 A:不同位数的接法 B:访问原理 C:访问地址 5:时序 1:NOR FLASH A:2440NOR FLASH时序 B:原理/时序图 C:寄存器 6:SDARM A:访问方式 B:原理图 C:BWSCON D:BANKCON…

【Linux系统编程】操作系统详解(什么是操作系统?为什么会存在操作系统?设计操作系统的目的是什么?)

目录 一、前言 二、 什么是操作系统 &#x1f4a6;操作系统的引入 &#x1f4a6;操作系统的概念理解 &#x1f4a6;操作系统设计的目的与定位 &#x1f4a6;总结 二、操作系统之上之下分别有什么 三、深度理解操作系统的“管理” &#x1f4a6;场景理解 &#x1f4a6;操…

视频文件+EasyDarwin做摄像机模拟器模拟RTSP流很方便,还能做成系统服务,方法与流程

之前我看到过一家人工智能做算法的企业&#xff0c;用EasyDarwinFFMPEG做了一个摄像机的模拟器&#xff0c;方法大概是&#xff1a; 用ffmpeg读取mp4等类型的视频文件&#xff08;当然ffmpeg啥都能读取&#xff09;&#xff0c;再以RTSP协议的形式推送给EasyDarwin&#xff1b…

不会提问不打紧,不敢提问才要命

最近在星球里回答了球友提出来的一些问题&#xff0c;我都给了回复&#xff0c;不经过在明确问题、探索问题的过程&#xff0c;对我启发挺大&#xff0c;特此来记录下感受和感悟。 缘起 最近新加入球友提的问题&#xff0c;有几次&#xff0c;我第一时间没看懂&#xff0c;甚…

【沐风老师】3DMAX快速地板屋顶墙面铺设插件使用方法详解

3DMAX快速地板屋顶墙面铺设插件使用教程 3DMAX快速地板屋顶墙面铺设插件&#xff0c;一键生成各种地板、墙面纹理模型&#xff0c;是一款非常实用的室内设计和建筑建模插件。 【适用版本】 3dMax7或更新版本 【安装方法】 该插件无需安装&#xff0c;直接在建模过程中使用&a…

【leetcode】62. 不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; …

如何减少40%的Docker构建时间

随着Docker的普及&#xff0c;许多公司的产品会将组件构建为Docker镜像。但随着时间的推移&#xff0c;一些镜像变得越来越大&#xff0c;对应的CI构建也变得越来越慢。 如果能在喝完一杯咖啡的时间&#xff08;不超过5分钟&#xff09;内完成构建&#xff0c;将是一个理想状态…

关于高斯核是实现尺度空间变换的唯一性思考

受到自己的启发&#xff0c;唯一性证明有了思路&#xff1a; 谁的一阶导数是自己&#xff0c;exp&#xff08;x&#xff09;&#xff0c;只有是自己&#xff0c;才能保持自己在其中。 为什么不能是exp&#xff08;x&#xff09;呢&#xff1f;不变导致图像不会模糊&#xff0…

二叉树:leetcode1457. 二叉树中的伪回文路径

给你一棵二叉树&#xff0c;每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的&#xff0c;当它满足&#xff1a;路径经过的所有节点值的排列中&#xff0c;存在一个回文序列。 请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。 给定二叉树的节点数目…

【LeetCode 热题 HOT 100】题解笔记 —— Day02

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

「媒体邀约」三农,农业类媒体资源有哪些?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 农业在我国国民经济中的地位是基础&#xff0c;农业是国民经济建设和发展的基础产业&#xff0c;因此围绕三农发展有很多的公司和企业&#xff0c;每年全国都有大大小小关于农业的展览&a…
最新文章