C语言中的基础指针操作

news2024/12/23 23:10:14

在C语言中,指针是一个非常重要的概念,它提供了直接访问内存地址的能力。指针变量用于存储内存地址,而不是数据值,在某种意义上和门牌号具有相似含义:指针是一个变量,其存储的是另一个变量的内存地址,这个内存地址唯一的标识,用于指向特定的内存位置。门牌号也是用来唯一标识一个具体的房屋或地址的,但指针的使用要复杂得多,涉及到内存的管理、指针的运算、野指针的避免等多个方面。在处理数组、字符串、动态内存分配以及函数参数传递等方面使得程序员们能够编写出更灵活、更高效的代码。

指针的概念

指针是一个变量,其值为另一个变量的地址,即直接指向内存中的某个位置,指针的声明需要在变量类型前加上星号*,像int *ptr;就声明了一个指向整数的指针变量ptr

指针的用途和功能

  1. 动态内存管理:C语言允许程序员在运行时动态地分配和释放内存,通过指针来实现,如使用malloccallocrealloc等函数分配内存,使用free函数释放内存。

  2. 数组操作:指针可以用来遍历数组,因为数组名本质上是一个指向数组首元素的指针。使用指针进行数组操作比使用数组索引更加高效。

  3. 字符串处理:在C语言中字符串是通过字符数组实现的。因此,字符串操作(如复制、拼接等)可以通过指针操作来实现。

  4. 函数参数传递:通过使用指针作为函数参数,可以在函数内部修改外部变量的值,实现数据的双向传递。

  5. 指向函数的指针:指针也可以指向函数,这使得可以将函数作为参数传递给其他函数,或者通过指针调用函数。

  6. 指向指针的指针:C语言允许创建指向指针的指针,这在进行复杂数据结构(如链表、树等)的操作时非常有用。

指针操作的大概流程如下
在这里插入图片描述

指针的基础操作

声明并初始化指针

在定义指针前需要先声明一个整数变量(我定义的value)并初始化,随后声明一个指向整数的指针变量(我声明的是pointer,很多人习惯使用p)并初始化为value的地址

    int value = 10; 
    int * pointer = &value;

在C语言中这里的三种写法都是可以的:

int* pointer = &value;
int * pointer = &value;
int *pointer = &value;

随后即可通过访问指针来查询这个整型变量(value)的值:

 printf("通过指针访问的值: %d \n", *pointer); 

完整代码如下:

#include <stdio.h>  
  
int main() {  
    int value = 10; 
    int *pointer = &value; 
    printf("通过指针访问的值: %d\n", *pointer);  
    
    return 0;
}

输出内容:通过指针访问的值: 10

修改指针指向的值

在C语言中,如果已经声明并初始化了一个指针,可以做到只修改指针所指向的值,原变量的数值不会改变。就好比结婚一样,老实的Java程序员大锤和小美去民政局登记结婚(为一个变量声明并初始化了一个指针),但大锤满足不了小美了(原数值因为各种原因在某项功能中需要进行改动),小美又不想离婚(修改原变量值),于是小美就找到了老王来满足她(修改指针所指向的值),以后小美还可以找老陈、老宋、老李(多次修改指针所指向的值),但是这样会造成:1.大锤没法活了(程序崩溃),2.孩子不是大锤亲生(野指针),3.家丑外扬(内存泄漏),4.小美被玩坏了(数据损坏)。根据刚才的代码继续编写:

#include <stdio.h>  
  
int main() {  
    int value = 10; 
    int *pointer = &value; 
    printf("通过指针访问的值: %d\n", *pointer);  
  
    // 修改指针指向的值  
    *pointer = 20;  
    printf("修改后通过指针访问的值: %d\n", *pointer);  
    printf("直接访问变量value的值: %d\n", value);  
  
    return 0;  
}

输出内容可以看出,指针所指向的值发生了更改,而原变量的值未发生任何变化:
在这里插入图片描述

取地址和解引用操作

在C语言中取地址操作是将变量的地址赋值给指针,而解引用操作则是通过指针访问它所指向的变量的值。这两个操作在C语言的指针使用中非常重要,它们允许我们通过指针间接地访问和操作内存中的数据。

修改一下之前的代码,通过&运算符将value的地址赋给了pointer,进行了取地址操作

#include <stdio.h>  
  
int main() {  
    int value = 20;    
    int *pointer;        
    // 在指针变量中存储value的地址,即取地址操作  
    pointer = &value;      
  
    printf("value变量的地址: %p\n", &value);  
    printf("通过pointer指针访问value变量的值: %d\n", *pointer);  
  
    return 0;  
}

输出中的0x7ffdee0e6ddc是变量value在内存中的地址,内存地址是操作系统分配给程序用于存储数据的物理或虚拟内存位置,每个程序运行时,操作系统都会为其分配一块内存空间,程序中的变量就存储在这块空间的特定地址上,同时每次程序运行时,操作系统可能会分配不同的内存地址给程序中的变量,0x7ffdee0e6ddc这个地址只是在这次运行程序时有效,下次运行时可能会有所不同。
在这里插入图片描述

指针的算术运算

指针的算术运算分为指针加减运算和指针相减运算
指针加减运算:指针可以进行加减运算,其结果是指针向前或向后移动若干个元素的距离(不是字节),移动的字节数取决于指针指向的数据类型。
指针相减运算:两个指针相减的结果是两个指针之间相隔的元素个数,要求两个指针指向同一块内存区域。
以下代码定义了两个数组:一个short类型的dataset数组和一个double类型的bills数组,每个数组都有SIZE 4个元素。然后,它定义了两个指针变量ptiptf,分别指向这两个数组的起始位置,随后代码进入一个循环,遍历这两个数组。在每次迭代中,它都会计算并打印出ptiptf指针在加上index值后的地址。这里pti + indexptf + index分别表示ptiptf指针向前移动indexshortdouble元素的位置。由于指针的加减运算是以它指向的数据类型的大小为单位进行的,所以pti每次增加2个字节(因为short类型通常占2个字节),而ptf每次增加8个字节(因为double类型通常占8个字节)。在打印指针地址时,代码将指针转换为void*类型。这是因为printf函数使用%p格式说明符来打印指针,而%p期望一个void*类型的参数。将指针转换为void*类型可以确保无论指针指向什么类型的数据,都能以统一的方式打印其地址。

#include <stdio.h>  
  
#define SIZE 4  
  
int main()  
{  
    short dataset[SIZE];  
    short *pti;  
    short index;  
    double bills[SIZE];  
    double *ptf;  
    pti = dataset;  
    ptf = bills;  
  
    printf("%23s %15s\n", "short pointers", "double pointers");  
    for (index = 0; index < SIZE; index++)  
    {  
        printf("pointers + %d: %10p %10p\n", index, (void*)(pti + index), (void*)(ptf + index));    
    }  
  
    return 0;  
}

代码会在终端输出以下内容:

                  short          double
pointers + 0: 0x7ffc8b926ef8 0x7ffc8b926f00 
pointers + 1: 0x7ffc8b926efc 0x7ffc8b926f10 
pointers + 2: 0x7ffc8b926f00 0x7ffc8b926f20 
pointers + 3: 0x7ffc8b926f04 0x7ffc8b926f30 
指针的比较

在C语言中可以使用关系运算符(如==、<、>等)来比较两个指针,比较的是它们所指向的地址的大小。
这里定义了一个长度为10的数组array,随后声明了三个指针ptr1、ptr2、ptr3,ptr1指向array的第3个元素 ,ptr2指向array的第6个元素 ,ptr3ptr1指向相同的地址:

#include <stdio.h>  
  
int main() {  
    int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};  
    int *ptr1, *ptr2, *ptr3;  
  
    ptr1 = &array[2]; // ptr1指向array的第3个元素  
    ptr2 = &array[5]; // ptr2指向array的第6个元素  
    ptr3 = ptr1;      // ptr3与ptr1指向相同的地址  
  
    // 比较ptr1和ptr2  
    if (ptr1 < ptr2) {  
        printf("ptr1 < ptr2\n");  
    } else {  
        printf("ptr1 >= ptr2\n");  
    }  
  
    // 比较ptr1和ptr3  
    if (ptr1 == ptr3) {  
        printf("ptr1 == ptr3\n");  
    } else {  
        printf("ptr1 != ptr3\n");  
    }  
  
    // 比较ptr2和ptr1  
    if (ptr2 > ptr1) {  
        printf("ptr2 > ptr1\n");  
    } else {  
        printf("ptr2 <= ptr1\n");  
    }  
  
    return 0;  
}

代码运行后再终端输出:

ptr1 < ptr2
ptr1 == ptr3
ptr2 > ptr1
指针与数组

在C语言中,数组名可以视为指向数组首元素的指针,因此可以使用指针来遍历数组元素,可以使用指针算术运算来访问数组中的元素。
以下代码中的dqys不仅代表了一个包含12个整数的数组,同时也可以被看作是一个指向int类型的指针,它指向dqys数组的第一个元素,在随后的for循环中使用了指针算术运算来遍历数组dqys。表达式dqys + index表示指针dqys向前移动indexint类型元素的位置。因为dqys是一个指向int的指针,所以每次递增都会使指针地址增加sizeof(int)个字节。通过解引用操作符*可以获取该地址处的值,即数组dqys中索引为index的元素的值。当index为0时,*(dqys + 0)就相当于dqys[0],它表示数组的第一个元素,其值为31。同理,*(dqys + 1)则相当于dqys[1],它表示数组的第二个元素,其值为28:

#include <stdio.h>

#define MONTHS 12

int main()
{
    int dqys[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int index;
    
    for (index = 0; index < MONTHS; index++)
    {
        printf("%2d 月有 %d 天. \n", index + 1, *(dqys + index));
    }
    return 0;
}

代码输出内容如下:

 1 月有 31 天. 
 2 月有 28 天. 
 3 月有 31 天. 
 4 月有 30 天. 
 5 月有 31 天. 
 6 月有 30 天. 
 7 月有 31 天. 
 8 月有 31 天. 
 9 月有 30 天. 
10 月有 31 天. 
11 月有 30 天. 
12 月有 31 天. 
指针与函数

C语言中允许指针指向函数,这使得可以将函数作为参数传递给其他函数,或者通过指针调用函数,同时也可以通过将指针作为函数参数传递,可以在函数内部修改外部变量的值。
这里的modifyValue函数接收一个int类型的指针作为参数,并通过该指针修改外部变量的值。add函数用来返回两个整数的和。executeFunction函数接收一个函数指针作为参数,并通过该函数指针调用函数。随后在main函数中,首先使用modifyValue函数通过指针修改外部变量的值。然后声明了一个函数指针functionPointer,并将其指向add函数。最后将该函数指针作为参数传递给executeFunction函数,并通过该函数指针调用add函数。

#include <stdio.h>  
  
// 定义一个函数,该函数接收一个int类型的指针作为参数  
void modifyValue(int *value) {  //函数接收一个int类型的指针作为参数
    *value = 10; // 通过指针修改外部变量的值  
}  
  
int add(int a, int b) {  // 函数用来返回两个整数的和 
    return a + b;  
}  
  
void executeFunction(int (*func)(int, int), int a, int b) {  // 接收一个函数指针作为参数,并调用该函数
    int result = func(a, b); // 通过函数指针调用函数  
    printf("结果为: %d\n", result);  
}  
  
int main() {  
    int variable = 0;  
    printf("数值修改后: %d\n", variable);  
    modifyValue(&variable); // 将变量的地址传递给函数以修改其值  
    printf("数值修改前: %d\n", variable);  
    // 使用函数指针调用函数  
    int (*functionPointer)(int, int) = add; // 声明一个函数指针,并将其指向add函数  
    executeFunction(functionPointer, 5, 3); // 将函数指针作为参数传递给executeFunction函数  
  
    return 0;  
}
动态内存分配

在C语言中提供了mallocfree函数用于动态内存分配和释放。malloc函数用于分配指定大小的内存空间,并返回一个指向该空间的指针;free函数用于释放已分配的内存空间。
下面代码中先声明了一个int类型的指针ptr,并将其初始化为NULL。然后使用malloc函数动态分配了足够存储5个整数的内存空间,并将返回的指针赋值给ptr。随后使用指针算术运算访问和修改动态分配的内存空间中的内容。之后使用for循环打印出动态分配的内存空间中的内容。最后使用free函数释放了已分配的内存空间,并将ptr重新设置为NULL避免空指针。

#include <stdio.h>  
#include <stdlib.h>  
  
int main() {  
    int *ptr = NULL; // 声明一个int类型的指针,并初始化为NULL  
    int n = 5; // 存储5个整数  
    ptr = (int*)malloc(n * sizeof(int)); // 使用malloc函数动态分配内存空间,分配了n个int大小的内存空间,并将返回的指针转换为int类型的指针  
    // 检查malloc函数是否成功分配了内存,如果内存分配失败就退出程序 
    if (ptr == NULL) {  
        printf("内存分配GG了\n");  
        return 1; 
    }  
    // 使用指针访问和修改动态分配的内存空间中的内容,通过指针算术运算访问数组元素,并赋值    
    for (int i = 0; i < n; i++) {  
        *(ptr + i) = i + 1; 
    }  
    // 通过指针算术运算访问数组元素,并打印动态分配的内存空间中的内容  
    for (int i = 0; i < n; i++) {  
        printf("%d ", *(ptr + i));   
    }  
    printf("\n");  
    // 使用free函数释放ptr指向的内存空间   
    free(ptr); 
    ptr = NULL; // 释放内存后,ptr变成了悬空指针,建议将ptr重新设置为NULL以避免悬空指针的问题  
  
    return 0;  
}
多级指针、指针数组、const指针、void指针

在C语言中出了基础的指针,以下的几种指针方式也很常见
多级指针:指向指针的指针,用于实现更复杂的数据结构和操作,如动态内存分配中的二维数组。
指针数组:数组中的元素是指针类型,常用于存储多个字符串或指向函数的指针。
const指针:指向常量的指针或指针常量,用于限制指针的指向或指针所指向的值不可修改。
void指针:通用指针类型,可以指向任意类型的数据,但在使用前通常需要类型转换。

#include <stdio.h>  
#include <stdlib.h>  
  
int main() {  
    // 多级指针,指向指针的指针  
    int value = 10;  
    int *ptr1 = &value;  
    int **ptr2 = &ptr1; 
    int ***ptr3 = &ptr2; 
    int ****ptr4 = &ptr3;
    int *****ptr5 = &ptr4;
    int ******ptr6 = &ptr5;
    int *******ptr7 = &ptr6;
    printf("通过多级指针访问值:%d\n", *******ptr7);  
  
    // 存储字符串  
    char *strings[] = {"Hello", "Gayboy", "GGBond"};  
    int i;  
    for (i = 0; i < 3; i++) {  
        printf("指针数组中的字符串:%s\n", strings[i]);  
    }  
  
    const int constValue = 20;  
    const int *constPtr = &constValue;  
    // *constPtr = 30; 不能修改指向常量的指针所指向的值  
    printf("指向常量的指针:%d\n", *constPtr);  
  
    // 指针常量
    int anotherValue = 30;  
    int *const constPtr2 = &anotherValue;  
    // constPtr2 = &value; 这样就是错误的,指针常量的值不可修改  
    *constPtr2 = 40; 
    printf("指针常量的指向值:%d\n", *constPtr2);  
  
    // 最后设一个通用指针  
    int intValue = 50;  
    float floatValue = 3.14f;  
    void *voidPtr;  
  
    voidPtr = &intValue;  
    printf("通过void指针访问int值:%d\n", *(int *)voidPtr);  
  
    voidPtr = &floatValue;  
    printf("通过void指针访问float值:%f\n", *(float *)voidPtr);  
  
    return 0;  
}

运行结果如下:
在这里插入图片描述

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

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

相关文章

成品视频素材下载网站有哪些?剪辑好可以用的视频素材网站分享

对于初学者在制作短视频时&#xff0c;常常希望能够快速获取高质量的素材。如果你正计划从事短视频创作&#xff0c;这里推荐几个优秀的成品素材网站&#xff0c;希望能对你有所帮助。 首先推荐的是蛙学网 作为国内用户首选的成品视频素材平台之一。这里提供丰富的视频素材库&…

大数据面试题之Spark(1)

目录 Spark的任务执行流程 Spark的运行流程 Spark的作业运行流程是怎么样的? Spark的特点 Spark源码中的任务调度 Spark作业调度 Spark的架构 Spark的使用场景 Spark on standalone模型、YARN架构模型(画架构图) Spark的yarn-cluster涉及的参数有哪些? Spark提交jo…

2.ROS串口安装和调试

首先安装串口依赖 sudo apt-get install ros-melodic-serial 其次安装串口调试助手 sudo apt-get install minicom 再赋予串口权限 sudo chmod 777 /dev/ttyTHS1 打开调试助手 sudo cutecom 硬件引脚图&#xff1a;

LangChain真的好用吗?谈一下LangChain封装FAISS的一些坑

最近在做一个知识库问答项目&#xff0c;就是现在大模型浪潮下比较火的 RAG 应用。LangChain 可以说是 RAG 最受欢迎的工具&#xff0c;因此我首选 LangChain 来快速构建我的应用。坦白来讲 LangChain 本身一套对于组件的定义已经让我感觉很复杂&#xff0c;为什么采用 f-strin…

Emp.dll文件丢失?理解Emp.dll重要性与处理常见问题

在繁多的动态链接库&#xff08;DLL&#xff09;文件中&#xff0c;emp.dll 可能不是最广为人知的&#xff0c;但在特定软件或环境中&#xff0c;它扮演着关键角色。本文旨在深入探讨 emp.dll 的功能、重要性以及面对常见问题时的解决策略。 什么是 emp.dll&#xff1f; Emp.d…

【Cpolar】如何实现外部网络对内部网络服务的访问

希望文章能给到你启发和灵感&#xff5e; 如果觉得文章对你有帮助的话&#xff0c;点赞 关注 收藏 支持一下博主吧&#xff5e; 阅读指南 开篇说明一、基础环境说明1.1 硬件环境1.2 软件环境 二、什么是Cpolar&#xff1f;三、如何安装Cpolar?3.1 Mac系统安装 四、最后 开篇说…

synchronized 锁优化原理

目录 一、轻量级锁 二、锁膨胀 三、自旋优化 四、偏向锁 五、锁消除 一、轻量级锁 1. 会创建一个锁记录 Lock Record&#xff08;保存在线程栈中&#xff09;&#xff0c;尝试 CAS 修改 Mark Word 中的对象头&#xff0c;是一种乐观锁的思想&#xff0c;而不是将 Java 对…

昇思MindSpore学习笔记4--数据集 Dataset

昇思MindSpore学习笔记4--数据集 Dataset 摘要&#xff1a; 昇思MindSpore数据集Dataset的加载、数据集常见操作和自定义数据集方法。 一、数据集 Dataset概念 MindSpore数据引擎基于Pipeline 数据预处理相关模块&#xff1a; 数据集Dataset加载原始数据&#xff0c;支持文本…

RAG一文读懂!概念、场景、优势、对比微调与项目代码示例

本文结合“基于 ERNIE SDKLangChain 搭建个人知识库”的代码示例&#xff0c;为您讲解 RAG 的相关概念。 01 概念 在2020年 Facebook AI Research(FAIR)团队发表一篇名为《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》的论文。这篇论文首次提出了 RA…

C语言力扣刷题4——删除链表的倒数第 N 个结点[双指针],只遍历一遍

力扣刷题4——删除链表的倒数第 N 个结点[双指针] 一、博客声明二、题目描述三、解题思路1、思路说明 四、解题代码&#xff08;附注释&#xff09; 一、博客声明 找工作逃不过刷题&#xff0c;为了更好的督促自己学习以及理解力扣大佬们的解题思路&#xff0c;开辟这个系列来记…

动态规划基础练习

我们需要先从数组较大的开始进行处理&#xff0c;每次考察上下左右的&#xff0c;比较当前存储的最大值和转移来的值&#xff0c;哪一个大一点 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std;int n, m; int a[105][105]; int addx[] { 0,…

队列的相关知识

目录 创建 初始化 销毁 头插 尾删 取出头 取出尾 数字个数 判空 队列的性质与特征 性质&#xff1a;一种先进先出的线性表 特征&#xff1a;FIFO&#xff08;先进先出&#xff09; 实现&#xff1a;用数组和链表的都可以 例子&#xff1a;在生产者消费者模型用到了…

比尔盖茨:Agent将是AI最大的赛道

Agent不仅将改变人们与计算机的互动方式&#xff0c;还将颠覆软件行业&#xff0c;引发自从我们从键入命令到点击图标以来计算机领域的最大革命。 保罗艾伦和我一起创立微软的至今&#xff0c;我对软件的热爱至今依然不减。 然而&#xff0c;尽管在过去的几十年中软件已经取得…

vue插槽的简单使用

默认插槽 1.在Category中创建插槽 <slot>默认值<slot/> 2.在App中使用 <Category tittle"美食"> <ul ><li v-for"(l,index) in foods" :key"index">{{l}}</li></ul> </Category> 3.运行后的…

文华缠论笔线段主图指南公式源码去包含

X:10; 笔参数:5; 段参数:6; 笔低A:LOW<LLV(LOW,笔参数),NODRAW; 笔高A:HIGH>HHV(HIGH,笔参数),NODRAW; 笔低:笔低A AND 笔高A0,NODRAW; 笔高:笔高A AND 笔低A0,NODRAW; VP1:BACKSET(笔高,BARSLAST(笔低)1); VP2:BACKSET(笔低,BARSLAST(笔高)1); VP3:(笔低A AND …

全网最详细Gradio教程系列——浏览器集成部署Gradio-Lite

全网最详细Gradio教程系列——浏览器集成Gradio-Lite 前言本篇摘要4 浏览器集成Gradio-Lite4.1 Gradio-Lite介绍4.2 构建Hello World例程4.3 routines4.3.1 multiple files4.3.2 Additional Requirements4.3.3 SharedWorker mode4.3.4 Code and Demo Playground 4.4 与Transfor…

OpenCV学习之cv2.imshow()函数

OpenCV学习之cv2.imshow()函数 一、简介 cv2.imshow 是 OpenCV 库中用于显示图像的基本函数之一。在图像处理和计算机视觉的过程中&#xff0c;使用该函数可以快速预览处理后的图像&#xff0c;便于调试和结果展示。 二、基本语法 cv2.imshow(WindowName, Imgmat)三、参数说…

Webpack: 借助 Babel+TS+ESLint 构建现代 JS 工程环境

概述 Webpack 场景下处理 JavaScript 的三种常用工具&#xff1a;Babel、TypeScript、ESLint 的历史背景、功能以及接入 Webpack 的步骤借助这些工具&#xff0c;我们能构建出更健壮、优雅的 JavaScript 应用 使用 Babel ECMAScript 6.0(简称 ES6) 版本补充了大量提升 JavaSc…

人生最有力,最棒的十句话!

人生最有力&#xff0c;最棒的十句话 1、允许一切事发生&#xff0c;所有一切发生的事不是你能阻挡了的&#xff0c;你接受&#xff0c;他也发生&#xff0c;你不接受&#xff0c;他也发生&#xff0c;你还不如坦然面对接受现实。 2、你焦虑的时候千万不要躺着啥也不干&#xf…

企业数据挖掘平台产品特色及合作案例介绍

泰迪企业数据挖掘平台是一款通用的、企业级、智能化的数据分析模型构建与数据应用场景设计工具&#xff0c;能够一体化地完成数据集成、模型构建、模型发布&#xff0c;为数据分析、探索、服务流程提供支撑&#xff0c;提供完整的数据探索、多数据源接入、特征处理、模型搭建、…