C语言中的基础指针操作

在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.mfbz.cn/a/758405.html

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

相关文章

6.27-6.29 旧c语言

#include<stdio.h> struct stu {int num;float score;struct stu *next; }; void main() {struct stu a,b,c,*head;//静态链表a.num 1;a.score 10;b.num 2;b.score 20;c.num 3;c.score 30;head &a;a.next &b;b.next &c;do{printf("%d,%5.1f\n&…

Cesium Model 中的剪裁平面 (ClippingPlane)

Cesium Model 中的剪裁平面 (ClippingPlane) 参考: https://www.cnblogs.com/webgl-angela/p/9197672.html Cesium Model 中的剪裁平面 (ClippingPlane) // 相关类: class ClippingPlaneCollection {} class ClippingPlane {}// 剪裁的整体流程: Model.prototype.update () …

LINGO:生产计划问题

模型&#xff1a;有瓶颈设备的多级生产计划问题 某工厂的主要任务是通过组装生产产品 A &#xff0c;用于满足外部市场需求。产品 A 的构成与组装过程见下图 &#xff0c;即 D , E , F , G 是从外部采购的零件&#xff0c;先将零件 D , E 组装成部件 B &#xff0c;零…

看你那样,超出你想像:羊、羊、羊

一、I am me,羊羊羊英文中的 我就是我(I am me),其实就是:羊 羊 羊,为什么会有这么一个结论呢?请往下看:I,就是羊am(是),也是羊me(我),还是羊我就是我,不一样的烟火。其实,我就是我(I am me),没有什么不一样,因为全是羊。二、羊都一样吗?among(在...当中…

斜率优化DP——AcWing 303. 运输小猫

斜率优化DP 定义 斜率优化DP&#xff08;Slope Optimization Dynamic Programming&#xff09;是一种高级动态规划技巧&#xff0c;用于优化具有特定形式的状态转移方程。它主要应用于那些状态转移涉及求极值&#xff08;如最小值或最大值&#xff09;的问题中&#xff0c;通…

c++重载(运算符)

1&#xff09;C入门级小知识&#xff0c;分享给将要学习或者正在学习C开发的同学。 2&#xff09;内容属于原创&#xff0c;若转载&#xff0c;请说明出处。 3&#xff09;提供相关问题有偿答疑和支持。 对于系统的所有操作符&#xff0c;一般情况下&#xff0c;只支持基本数…

ubuntu安装显卡驱动

获取权限 chmod X NVIDlA-Linux-x86_64-550.90.07.run 安装程序 sudo bash NVIDlA-Linux-x86_64-550.90.07.run

告别模糊时代,扫描全能王带来清晰世界

模糊碑文引发的思考 上个月中旬去洛阳拜访了著名的龙门石窟&#xff0c;本就对碑文和文字图画感兴趣的我们&#xff0c;准备好好欣赏一下龙门石窟的历史文化古迹。到了地方之后&#xff0c;我发现石窟的高度和宽度远远超出了想象&#xff0c;正因如此&#xff0c;拍出来的文字…

多多代播24小时值守:电商直播时代是带货爆单的关键

在电商直播盛行的今天&#xff0c;直播带货已成为品牌与消费者沟通的关键。然而&#xff0c;流量波动大&#xff0c;竞争激烈&#xff0c;使品牌面临诸多挑战。因此&#xff0c;许多品牌寻求专业代播服务&#xff0c;并特别强调24小时值守的重要性。 流量来源的不稳定性是一个显…

Spring-循环依赖是如何解决的

1、bean被创建保存到spring容器的过程 1、实例化 -> 获取对象&#xff1b; 2、填充属性&#xff1b;这里可能需要依赖其它的bean。 3、AOP代理对象替换&#xff1b; 4、加入单例池&#xff1b; 问题&#xff1a; 循环依赖怎么处理 ServiceA 中有属性ServiceB b&#…

使用label-studio对OCR数据进行预标注

导读 label-studio作为一款数据标注工具相信大家都不陌生&#xff0c;对于需要进行web数据标注协同来说应该是必备工具了&#xff0c;标注的数据类型很全涉及AI的各个任务(图像、语音、NLP、视频等)&#xff0c;还支持自定义涉及模版。 然而&#xff0c;我们在标注数据的过程…

win11 内存占用过大优化尝试

关闭开机加速 wins打开搜索 控制面板&#xff0c;打开控制面板 找到硬件和声音-电源选项-选择电源按钮的功能-去掉勾选启用快速启动 关闭windows 更新 winr 输入services.msc打开服务-搜索windows 更新-双击打开设置-选择禁用 貌似没什么用。

【Python3的内置函数和使用方法】

目录 Python 特点 Python 中文编码 Python 变量类型 Python列表 Python 元组 元组是另一个数据类型&#xff0c;类似于 List&#xff08;列表&#xff09; Python 字典 Python数据类型转换 Python 运算符 Python算术运算符 Python比较运算符 Python赋值运算符 Pyt…

根据描述表示泥浆密度沿着管路的长度方向在不断变化,如何来表示泥浆密度随管路的变化?

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

文件夹内-资源名称前加序号排列

问题&#xff1a;在文件夹下的资源可以按时间排序&#xff0c;导入unity后资源顺序会乱掉&#xff0c;不方便按顺序赋值&#xff0c;为了方便&#xff0c;通过下面方法在文件夹下统一在资源名称前按顺序加上序号 win11在文件夹内右键&#xff0c;选择——在终端中打开 输入&a…

4.SQL注入-xx型

SQL注入-xx型 输入kobe查询&#xff0c;同样展示id和邮箱 xx型的sql语句查询方式&#xff0c;猜测 select 字段1,字段2 from 表名 where username(kobe)在后台语句中的查询 select id,email from member where username(kobe);根据上图构造payload语句为 username(kobe) …

spring-security安全框架(超精细版附带流程讲解图)

目录 一、回顾一下 二、security使用 2.1 覆盖掉默认配置「自定义配置」 2.2 如何自定义认证 2.3 纯纯自定义 2.4 jwt 2.5 官网认证流程 2.6 RBAC模型 4.1. 创建表结构 2.7 如何实现权限流程 一、回顾一下 security干啥的? 认证和授权 使用方式 引入依赖, 基于spri…

【自然资源】国家历史文化名城你知道多少?

【自然资源】国家历史文化名城你知道多少&#xff1f; 中国五千年的历史孕育出了一些因深厚的文化底蕴和发生过重大历史事件而青史留名的城市。根据《中华人民共和国文物保护法》&#xff0c;“历史文化名城”是指保存文物特别丰富&#xff0c;具有重大历史文化价值和革命意义…

小红书多账号管理平台哪个好用?可以快速监测多个小红书账号的数据吗?

随着品牌营销战线的不断扩展&#xff0c;小红书已经成为企业和个人品牌竞相展示的舞台。但是&#xff0c;随之而来的多账号管理问题也让众多运营者头疼不已。一个优秀的多账号管理平台&#xff0c;能让你事半功倍&#xff0c;轻松监控和分析账号数据。 如今&#xff0c;市面上出…

昇思25天学习打卡营第12天 | ResNet50图像分类

内容介绍&#xff1a; ResNet50网络是2015年由微软实验室的何恺明提出&#xff0c;获得ILSVRC2015图像分类竞赛第一名。在ResNet网络提出之前&#xff0c;传统的卷积神经网络都是将一系列的卷积层和池化层堆叠得到的&#xff0c;但当网络堆叠到一定深度时&#xff0c;就会出现…