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 12int 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.mzph.cn/news/863495.shtml

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

相关文章

java之动态代理

1 代理模式 代理模式提供了对目标对象额外的访问方式&#xff0c;即通过代理对象访问目标对象&#xff0c;这样可以在不修改原目标对象的前提下&#xff0c;提供额外的功能操作&#xff0c;扩展目标对象的功能。简言之&#xff0c;代理模式就是设置一个中间代理来控制访问原目标…

网络io与select,poll,epoll

前言 网络 IO&#xff0c;会涉及到两个系统对象&#xff0c;一个是用户空间调用 IO 的进程或者线程&#xff0c;另一个是内核空间的内核系统&#xff0c;比如发生 IO 操作 read 时&#xff0c;它会经历两个阶段&#xff1a; 1. 等待数据准备就绪 2. 将数据从内核拷贝到进程或…

网络编程常见问题

1、TCP状态迁移图 2、TCP三次握手过程 2.1、握手流程 1、TCP服务器进程先创建传输控制块TCB&#xff0c;时刻准备接受客户进程的连接请求&#xff0c;此时服务器就进入了LISTEN&#xff08;监听&#xff09;状态&#xff1b; 2、TCP客户进程也是先创建传输控制块TCB&#xff…

改进经验模态分解方法-通过迭代方式(IMF振幅加权频率,Python)

一种新颖的改进经验模态分解方法-通过迭代方式&#xff08;IMF振幅加权频率&#xff09;有效缓解了模态混叠缺陷&#xff0c;以后慢慢讲&#xff0c;先占坑。 import numpy as np import matplotlib.pyplot as plt import os import seaborn as sns from scipy import stats i…

C语言图书管理系统控制台程序

程序示例精选 C语言图书管理系统控制台程序 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《C语言图书管理系统控制台程序》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读…

加密与安全_三种方式实现基于国密非对称加密算法的加解密和签名验签

文章目录 国际算法基础概念常见的加密算法及分类签名和验签基础概念常见的签名算法应用场景 国密算法对称加密&#xff08;DES/AES⇒SM4&#xff09;非对称加密&#xff08;RSA/ECC⇒SM2&#xff09;散列(摘要/哈希)算法&#xff08;MD5/SHA⇒SM3&#xff09; Code方式一 使用B…

智慧园区综合平台解决方案PPT(75页)

## 智慧园区的理解 ### 从园区1.0到园区4.0的演进 1. 园区1.0&#xff1a;以土地经营为主&#xff0c;成本驱动&#xff0c;提供基本服务。 2. 园区2.0&#xff1a;服务驱动&#xff0c;关注企业成长&#xff0c;提供增值服务。 3. 园区3.0&#xff1a;智慧型园区&#xff…

机器学习引领教育革命:智能教育的新时代

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀目录 &#x1f4d2;1. 引言&#x1f4d9;2. 机器学习在教育中的应用&#x1f31e;个性化学习&#x1f319;评估与反馈的智能化⭐教学资源的优…

STC89C52RC单片机设计的FM收音机+自动搜台+存储电台(程序+原理图+PCB)

资料下载地址&#xff1a;STC89C52RC单片机设计的FM收音机自动搜台存储电台&#xff08;程序原理图PCB) 1、实物图 2、部分程序 #include <reg52.h> #include "tea5767.h" #include "delay.h" #include "lcd1602.h" //K1:上一台 K2:下一…

mac电脑游戏推荐:NBA 2K24 街机版下载

NBA 2K24 街机版是一款由2K Sports开发并发行的篮球游戏&#xff0c;属于著名的NBA 2K系列。这款游戏为玩家提供了与NBA联赛中真实球员和球队互动的机会&#xff0c;体验篮球比赛的激情与紧张。街机版的NBA 2K24通常会在游戏厅、商场等公共场所设置&#xff0c;供玩家投币游玩。…

c++重载(运算符)

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

AWTK 用 icon_at 属性设置图标位置

1. style 在 style 文件中通过 icon_at 属性设置图标位置。 <style name"right_bottom" icon_at"right_bottom"><normal icon"unchecked_right_bottom" /><pressed icon"unchecked_right_bottom" /><over i…

redis实战-短信登录

基于session的登录流程 session的登录流程图 1. 发送验证码 用户在提交手机号后&#xff0c;会校验手机号是否合法&#xff0c;如果不合法&#xff0c;则要求用户重新输入手机号 如果手机号合法&#xff0c;后台此时生成对应的验证码&#xff0c;同时将验证码进行保存&#x…

第一节:如何开发第一个spring boot3.x项目(自学Spring boot 3.x的第一天)

大家好&#xff0c;我是网创有方&#xff0c;从今天开始&#xff0c;我会记录每篇我自学spring boot3.x的经验。只要我不偷懒&#xff0c;学完应该很快&#xff0c;哈哈&#xff0c;更新速度尽可能快&#xff0c;想和大佬们一块讨论&#xff0c;如果需要讨论的欢迎一起评论区留…

Pytorch实战(二)

文章目录 前言一、LeNet5原理1.1LeNet5网络结构1.2LeNet网络参数1.3LeNet5网络总结 二、AlexNext2.1AlexNet网络结构2.2AlexNet网络参数2.3Dropout操作2.4PCA图像增强2.5LRN正则化2.6AlexNet总结 三、实战3.1LeNet5模型搭建3.2模型训练 前言 参考原视频&#xff1a;哔哩哔哩。 …

【后端面试题】【中间件】【NoSQL】ElasticSearch面试基本思路和高可用方案(限流、消息队列、协调节点、双集群)

基本思路 业务开发面试Elasticsearch的时候基本问的是基础知识以及倒排索引。 Elasticsearch最基本的可用性保障就是分片&#xff0c;而且是主从分片&#xff0c;所以遇到Elasticsearch如何做到高可用这个问题的时候&#xff0c;首先要提到这一点。 Elasticsearch高可用的核心…

手机屏幕贴合项目(ni视觉如何找矩形的角坐标)

首先&#xff0c;我们存储了cg和dito感兴趣八个角图像的模板&#xff0c;用来匹配位置。 cover指的是cg的四个角模板&#xff0c;lcm是dito四个角匹配模板。 其次&#xff0c;我们采集的8副图像&#xff08;m_DlgCCDViewArr[2][4]&#xff09;中一定包含匹配模板的特征。 好&…

Json与Java类

简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也易于机器解析和生成。JSON数据由键值对构成&#xff0c;并以易于阅读的文本形式展现&#xff0c;支持数组、对象、字符串、数字、布尔值…

笔灵AI写作:释放创意,提升写作效率的秘诀

内容为王&#xff0c;在内容创作的世界中尤为重要。然而&#xff0c;面对写作时常常感到无从下手&#xff1a;有时缺乏灵感&#xff0c;有时难以表达清楚自己的想法。AI写作助手的出现&#xff0c;为这些问题提供了创新的解决方案&#xff0c;极大地改变了内容创作的过程。 今…

Pytest+Allure+Yaml+Jenkins+Gitlab接口自动化中Jenkins配置

一、背景 Jenkins&#xff08;本地宿主机搭建&#xff09; 拉取GitLab(服务器)代码到在Jenkins工作空间本地运行并生成Allure测试报告 二、框架改动点 框架主运行程序需要先注释掉运行代码&#xff08;可不改&#xff0c;如果运行报allure找不到就直接注释掉&#xff09; …