【新手入门必看】从零开始学指针

news2024/9/20 17:44:53

我使用VS CODE+MSYS2的编译环境进行学习,想使用VS CODE进行C/C++代码编写的小伙伴参考这篇文章进行环境配置VS Code 配置 C/C++ 编程运行环境(保姆级教程)

一、指针的引入

指针==地址

#include <stdio.h>

int main() {
    int a = 10;
    
    printf("The value of a is: %d\n", a);
    printf("The address of a is: %p\n", &a);
    printf("The value of a is: %d\n", *(&a));//取值运算符,把后面内存地址内的数据“取出来”
    return 0;
}

变量:int a=10;  (四要素:类型int、变量名a、内存地址、值10)

变量访问的两种方式:
1、通过变量名访问:

printf("The value of a is: %d\n", a);

2、通过地址访问:

&取地址运算符   *将地址内的值读出运算符printf("The value of a is: %d\n", *(&a));

二、指针变量的引入

指针变量==存放地址的变量

#include <stdio.h>

int main() {
    //什么是整型变量,存放整型数的变量
    //什么是字符型变量,存放字符型数据的变量
    //什么是指针变量,存放指针的变量
    //指针==地址:指针变量就是存放地址的变量
    int a = 10;
    int *p;//这里*是一个标识符,告诉系统我是一个指针变量,用来保存别人的地址,和14行运算符不同
    p=&a;//p=a的地址
    printf("The value of a is: %d\n", a);
    printf("The address of a is: %p\n", &a);
    printf("The value of a is: %d\n", *(&a));//取值运算符,把后面内存地址内的数据“取出来”
    printf("pointer-The value of a is: %d\n", *p);
    return 0;
}

如何定义一个指针变量—— * 的标识作用——只用于指针变量定义或者声明的时候

如何使用一个指针变量—— * 的运算作用——把地址内的数据取出

#include <stdio.h>

int main() {
    int a = 0x1234;
    int *i = &a;
    char *c = &a;

    printf("*i:a=%x\n", *i);
    printf("*c:a=%x\n", *c);

    printf("*i:addr=%p\n", i);
    printf("*c:addr=%p\n", c);

    printf("++i=%p\n", ++i);
    printf("++c=%p\n", ++c);
}

int类型占用4个字节(byte),一个字节占用8位(bit)————32bit

char类型占用1个字节,一个字节占用8位————8bit

三、为什么要使用指针

1、场景一之变量值交换

#include <stdio.h>

int main(){
	int a=10;
	int b=20;
	int temp;	

	printf("before:");
	printf("a=%d\t",a);
	printf("b=%d\n",b);

	temp=a;
	a=b;
	b=temp;

	printf("after:");
	printf("a=%d\t",a);
	printf("b=%d\n",b);

	return 0;
}

当我们想把main函数里面的值交换封装到函数里面却发现交换后的a和b的值并没有改变

#include <stdio.h>

void changeData(int a,int b){
	int temp;
	temp=a;
	a=b;
	b=temp;
}

int main(){
	int a=10;
	int b=20;
	
	printf("before:");
	printf("a=%d\t",a);
	printf("b=%d\n",b);

	changeData(a,b);

	printf("after:");
	printf("a=%d\t",a);
	printf("b=%d\n",b);

	return 0;
}

 

原因是应为数据发生交换的内存空间和main函数里变量的内存空间不在同一内存空间 ,解决办法将changeData函数的参数变为指针,调用changeData函数时将变量a和b的地址传入

#include <stdio.h>

void changeData(int *a,int *b){
	int temp;
	temp=*a;
	*a=*b;
	*b=temp;
}

int main(){
	int a=10;
	int b=20;
	
	printf("before:");
	printf("a=%d\t",a);
	printf("b=%d\n",b);

	changeData(&a,&b);

	printf("after:");
	printf("a=%d\t",a);
	printf("b=%d\n",b);

	return 0;
}

2、场景二之指针指向固定的内存地址

#include <stdio.h>

int main(){
	unsigned int *p=(unsigned int *)0x0019FF11;
	printf("p = 0x%p\n",p);
	return 0;
}

四、通过指针引用数组

1、定义一个指针变量指向数组

在C语言中,数组名(不包括形参的数组名,形参数组并不占用实际的内存单元)和数组中的首元素都可以代表数组的首地址

2、指针偏移遍历数组

我们之前访问数组元素用的都是下标法printf("arr[0]=%d\n",arr[0]); 系统在使用数组下标对数组成员进行访问时开销比较大,指针的访问效率远远大于数组名的访问效率但普通工程代码量小影响微乎其微。

在数组中所有元素的地址是连续的,第一个元素是a,那么第二个就是a+1,第三个就是a+2......但这个1并不是数组一而是字节数,比如int类型的数组第二个元素就比第一个元素的地址多了四个字节(32位)参考本文 二、指针变量的引入理解

每次重新编译运行后数组的首地址都会改变,因为局部数组是放在栈区的,由编译器自动进行分配和释放

#include <stdio.h>

int main(){
    int arr[3]={1,2,3};
    printf("arr address:%p\n",arr);
    printf("arr[0] address:%p\n",&arr[0]);
    int *p,*q;
    p=arr;
    q=&arr[0];

    printf("a[0]=%d\t",*(p));//指针变量p存放了数组的首地址,通过取值符号*将地址内的值取出
    printf("a[0] address:%p\n",p);
    printf("a[1]=%d\t",*(p+1));
    printf("a[1] address:%p\n",p+1);
    printf("a[2]=%d\t",*(p+2));
    printf("a[2] address:%p\n",p+2);

    printf("a[0]=%d\t",*(q));    
    printf("a[1]=%d\t",*(q+1));   
    printf("a[2]=%d",*(q+2));   
    return 0;
}

通过for循环遍历可节省代码量

#include <stdio.h>

int main(){
    int arr[3]={1,2,3};
    int len=sizeof(arr)/sizeof(int);//或者int len=sizeof(arr)/sizeof(arr[0])
    printf("arr address:%p\n",arr);
    printf("arr[0] address:%p\n",&arr[0]);
    for(int i=0;i<len;i++){
        printf("arr[%d]=%d\t",i,arr[i]);
    }
    printf("\n\n");

    int *p,*q;
    p=arr;
    q=&arr[0];

    for(int i=0;i<len;i++){
        printf("arr[%d]=%d\t",i,*(p+i));
        printf("address=%p\n",p+i);
    }
     printf("\n");
    for(int i=0;i<len;i++){
        printf("arr[%d]=%d\t",i,*(q+i));
        printf("address=%p\n",q+i);
    }
    return 0;
}

3、指针当做数组名来用

4、指针常量和指针变量的区别

int *p是指针变量,它所保存的地址是可变的

数组名arr是指针常量,它的地址是确定的

#include <stdio.h>

int main(){
    int arr[3]={1,2,3};
    int *p=arr;

    for(int i=0;i<3;i++){
        printf("%d ",*p++);
    }
/*
    for(int i=0;i<3;i++){
        printf("%d ",*arr++);//编译不通过,指针常量
    }
*/
    for(int i=0;i<3;i++){
        printf("%d ",*(arr+i));
    }
    printf("\n\n");
    return 0;
}

 5、sizeof计算大小的区别

在32位操作系统中指针占4字节,64位操作系统中指针占8字节

6、小练习

(1)通过指针的方式实现数组的初始化和遍历

#include <stdio.h>

void initArray(int *p,int len);
void printArray(int *p,int len);

int main(){
    int arr[3];
    int len=sizeof(arr)/sizeof(int);
    initArray(arr,len);
    printArray(arr,len);
    return 0;
}

void initArray(int *p,int len){
    
    for(int i=0;i<len;i++){
        printf("please enter %d number:",i+1);
        scanf("%d\n",p++);
    }
}

void printArray(int *p,int len){
    for(int i=0;i<len;i++){
        printf("arr[%d]=%d\t",i,*(p+i));
        //printf("arr[%d]=%d\t",i,*p++)
    }
}

(2)实现数组翻转

#include <stdio.h>

void Array1(int *p,int len);
void Array2(int *p,int len);
void printArray(int *p,int len);

int main(){
    int arr1[5]={5,4,3,2,1};
    int arr2[6]={6,5,4,3,2,1};
    int len1=sizeof(arr1)/sizeof(int);
    int len2=sizeof(arr2)/sizeof(int);
    printf("before:\n");
    printArray(arr1,len1);
    printf("\n");
    printArray(arr2,len2);
    printf("\nafter:\n");
    Array1(arr1,len1);
    printArray(arr1,len1);
    printf("\n");
    Array2(arr2,len2);
    printArray(arr2,len2);
    return 0;
}

void Array1(int *p,int len){
    int temp;
    for(int i=0;i<(len/2);i++){//前2个数,中间那个数不变
        for(int j=len-i-1;j<=len-i-1;j++){//后2个数
            /*形式1*/
            temp=p[i];
            p[i]=p[j];
            p[j]=temp;

            /*形式2*/
            // temp=*(p+i);
            // *(p+i)=*(p+j);
            // *(p+j)=temp;    
        }
    }
}

void Array2(int *p,int len){
    int temp;
    for(int i=0;i<=(len/2);i++){//前3个数
        for(int j=len-i-1;j<=(len-i-1);j++){//后3个数
            /*形式1*/
            // temp=p[i];
            // p[i]=p[j];
            // p[j]=temp;    

            /*形式2*/
            temp=*(p+i);
            *(p+i)=*(p+j);
            *(p+j)=temp;    
        }
    }
}

void printArray(int *p,int len){
    for(int i=0;i<len;i++){
        printf("arr[%d]=%d\t",i,*(p+i));
        //printf("arr[%d]=%d\t",i,*p++)
    }
}

五、二维数组

有一个二维数组a,它有3行4列:int a[3][4]={{1,3,5,7},{2,4,6,8},{1,2,3,4}};

a是二维数组名,a数组包含3行即3个行元素:a[0], a[1], a[2]。而每个行元素又是一个一位数组,它包含4个元素(即4个列元素)。例如a[0]所代表的一位数组包含4个元素:a[0][0], a[0][1], a[0][2],a[0][3];可以认为二维数组是数组的数组,即二维数组a是由3个一维数组所组成的。

a[0], a[1], a[2]既然是一维数组名,而数组名又代表数组首元素地址,因此a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0]。也就是说a[1]的值就是&a[1][0],a[2]d的值就是&a[2][0]。

a==&a[0]==&a[0][0]

a+1==&a[1]==&a[1][0]

虽然说 a, a[0]都是代表第一个一维数组的首地址&a[0][0],但是在去值的时候我们发现*a是无效的。在C语言中,二维数组名实际上是一个指向数组首行首元素的一维数组指针。所以,对于数组 int a[3][4]

  • a 表示整个二维数组,等价于 *(a+0) 或者 &a[0][0],即指向第一个一维数组(行)的首地址。
  • *a 解引用 a,得到的是 a 所指向的内容,也就是 a[0],它是一个包含四个整数的一维数组。

因此,*a 实际上等于 a[0],它们都是指向同一块内存区域,即数组的第一行 [1, 3, 5, 7] 的起始地址。但在表达式中直接写 *a 并不能获得具体某个元素的值,因为 *a 还是一个数组,不是单个整数。如果你想访问第一个元素,可以写作 (*a)[0],这等同于 a[0][0],结果就是 1。

 

 

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

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

相关文章

编写函数fun,函数的功能是:根据以下公式计算s,计算结果作为函数值返回;n通过形参传入。

本文收录于专栏:算法之翼 https://blog.csdn.net/weixin_52908342/category_10943144.html 订阅后本专栏全部文章可见。 本文含有题目的题干、解题思路、解题思路、解题代码、代码解析。本文分别包含C语言、C++、Java、Python四种语言的解法完整代码和详细的解析。 题干 编写…

Java:二叉树(1)

从现在开始&#xff0c;我们进入二叉树的学习&#xff0c;二叉树是数据结构的重点部分&#xff0c;在了解这个结构之前&#xff0c;我们先来了解一下什么是树型结构吧&#xff01; 一、树型结构 1、树型结构简介 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>…

深度学习系列64:数字人openHeygen详解

1. 主流程分析 从inference.py函数进入&#xff0c;主要流程包括&#xff1a; 1&#xff09; 使用cv2获取视频中所有帧的列表&#xff0c;如下&#xff1a; 2&#xff09;定义Croper。核心代码为69行&#xff1a;full_frames_RGB, crop, quad croper.crop(full_frames_RGB)。…

openobserve-filebeat配置

优势 rustgolang开发的日志工具组合&#xff0c;自带日志数据存储&#xff0c;简化部署和管理。日志数据可配置保留x天。从日志文件中采集&#xff0c;做到非侵入式日志集中管理。 可从日志内容中提取信息进行报警等二次开发。 下载 openobserve-v0.10.1-windows-amd64 fil…

VL02N交货单清除字段:VLSTK(分配状态)

VL02N交货单清除字段&#xff1a;VLSTK(分配状态) 通过查找增强对应的BADI&#xff1a;LE_SHP_DELIVERY_PROC 修改方法&#xff1a;IF_EX_LE_SHP_DELIVERY_PROC~CHANGE_DELIVERY_HEADER&#xff0c;代码如下&#xff1a;

JSS作业

JSS作业&#xff1a; 1: <script>var cnt parseInt(window.prompt("请输入打印的行数&#xff1a;"));for (var i 1; i < cnt; i){for (var j 1; j < i; j){document.write("*")}document.write("<br>")} </script>…

XTuner 微调 LLM:1.8B、多模态、Agent

两种微调范式&#xff1a;增量预训练和指令微调 在大语言模型下游应用中&#xff0c;主要有两种微调范式&#xff1a;增量预训练和指令微调。增量预训练旨在让模型学习特定领域的常识&#xff0c;而不需要有监督标注的数据&#xff1b;指令微调则是通过高质量的对话数据训练模型…

C语言中, 文件包含处理,#include< > 与 #include ““的区别

文件包含处理 指一个源文件可以将另外一个文件的全部内容包含进来 &#xff23;语言提供了#include命令用来实现文件包含的操作 #include< > 与 #include ""的区别 <> 表示系统直接按系统指定的目录检索 "" 表示系统先在 "" 指定…

vulfocus靶场couchdb 权限绕过 (CVE-2017-12635)

Apache CouchDB是一个开源数据库&#xff0c;专注于易用性和成为"完全拥抱web的数据库"。它是一个使用JSON作为存储格式&#xff0c;JavaScript作为查询语言&#xff0c;MapReduce和HTTP作为API的NoSQL数据库。应用广泛&#xff0c;如BBC用在其动态内容展示平台&…

动态内存管理 柔性数组

文章目录 动态内存函数 malloc freecallocrealloc 重新开辟空间realloc 也可以第一个参数为NULL&#xff0c;则是直接开辟内存&#xff0c;类似于malloc用法 常见的动态内存错误对空指针进行解引用操作对开辟的内存越界访问对非动态开辟的内存使用free释放使用free释放动态开辟…

《Linux运维总结:Kylin V10+ARM架构CPU基于docker-compose一键离线部署redis6.2.8之容器版哨兵集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;《Linux运维篇&#xff1a;Linux系统运维指南》 一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&…

停更这大半年-我去考了个研

解释一下2023年我几乎没有更新文章 还有私信也没有回&#xff0c;因为我几乎没有登陆过csdn了 我从2023年3月份开学就开始准备考研了 其实3月28号就出结果了&#xff0c;后面有事情就给忘了&#xff0c;给我的粉丝解释一下&#xff08;虽然粉丝也不多&#xff09;。后面应该会…

【GPTs分享】GPTs分享之 AskYourPDF Research Assistant​

一、简介 AskYourPDF Research Assistant 是一款高级人工智能研究助手&#xff0c;专门设计用于帮助用户高效从PDF文件和文章中提取信息。它结合了深度学习技术和自然语言处理能力&#xff0c;以便用户能够快速地查询、总结及处理文档内容&#xff0c;并能够生成与文章内容相关…

JAVA的jBPM工作流框架

一. jBPM是什么&#xff1f; 现实生活中有很多需要走一些流程的过程&#xff0c;比如请假流程&#xff0c;报销流程等&#xff0c;使用工作流框架&#xff0c;即可写一个流程即可&#xff0c;添加流程时不在繁琐的建立新的各种配置。jBPM&#xff08;Java Business Process M…

力扣:104. 二叉树的最大深度(Java,DFS,BFS)

目录 题目描述&#xff1a;输入&#xff1a;输出&#xff1a;代码实现&#xff1a;1.深度优先搜索&#xff08;递归&#xff09;2.广度优先搜索&#xff08;队列&#xff09; 题目描述&#xff1a; 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从…

【CVPR2023】《A2J-Transformer:用于从单个RGB图像估计3D交互手部姿态的锚点到关节变换网络

这篇论文的标题是《A2J-Transformer: Anchor-to-Joint Transformer Network for 3D Interacting Hand Pose Estimation from a Single RGB Image》&#xff0c;作者是Changlong Jiang, Yang Xiao, Cunlin Wu, Mingyang Zhang, Jinghong Zheng, Zhiguo Cao, 和 Joey Tianyi Zhou…

MyBatisPlus详解(一)项目搭建、@TableName、@TableId、@TableField注解与常见配置

文章目录 前言1 快速入门1.1 环境准备1.1.1 新建数据库1.1.2 创建项目 1.2 快速开始1.2.1 引入MyBatisPlus依赖1.2.2 定义Mapper1.2.3 配置实体类和表的关系1.2.4 简单测试1.2.4.1 测试插入操作1.2.4.2 测试查询操作1.2.4.3 测试更新操作1.2.4.4 测试删除操作 1.3 常见注解1.3.…

Jenkins服务器IP更换,Jenkins URL地址更换

服务器的网络地址发生变动&#xff0c;修改jenkins服务器IP地址后&#xff0c;jenkins网页能够打开&#xff0c;但是job中的配置钩子没有自动改变&#xff0c;如图所示&#xff1a; 经过查询资料了解&#xff0c;需要修改jenkins本地化配置地址才可以显示正确&#xff1a; 1、…

独立样本t检验——python完整代码(直接运行就行)

#!/usr/bin/env python # -*- coding: utf-8 -*- # Author : 三十二画生JH # Contact : fjhstudent163.com # Software: PyCharm # Time : 2024/4/21 21:49 # Site : 网址 # File : t_test.py # Version : # ---功能描述 """ 对实验数据做独立样本&am…

【GoWeb框架初探————XORM篇】

1. XORM xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。 1.1 特性 支持 Struct 和数据库表之间的灵活映射&#xff0c;并支持自动同步事务支持同时支持原始SQL语句和ORM操作的混合执行使用连写来简化调用支持使用ID, In, Where, Limit, Join, Havi…