C语言指针、数组学习记录

指针

指针是什么

数据在内存中存放的方式

声明一个变量int i = 3;,那么在内存中就会分配一个大小为4字节(因为int类型占4字节)的内存空间给变量i,这块内存空间存放的数据就是变量i的值。

换句话说就是,在内存中给变量i分配了一个房间,房间号为2000,这个房间号就是变量i在内存中的地址。

image-20240307115334531

数据的读取

每次访问变量i其实就是到 2000 这个房间去把里面的数据读出来,比如:

printf("%d\n", i);

实际上是通过变量名i找到存储单元的地址,从而对存储单元进行存取操作。程序经过编译以后,已经把变量名转换为变量的地址,**对变量值的存取都是通过地址进行的。**再比如:

scanf("%d", &i);

这里的&就是取地址符,&i的值就等于变量i的地址 2000 ,这条语句就是要把键盘输入的数据存到内存中首地址为2000的整型存储单元。即从2000开始往后的4个字节。

以上这种直接根据变量名进行访问的方式,称为“直接访问”。

指针变量

与之相对应的就有“间接访问”的方式。即将变零i的地址值存放到另一个变量中,然后通过访问这个变量的值来找到变量i的地址,从而访问变量i。这个存放地址的变量就叫做指针变量。比如:

int *i_pointer = &i;

这里就声明了一个名为i_pointer的指针变量,他的值就是变量i的地址2000。

注意:

  • 指针变量:是存储地址的一个变量,即i_pointer
  • 变零的指针:是指该变量的地址,即i的地址2000

定义指针变量

一般形式为:类型名 指针变量名,如:int *p1;就是声明了一个指向整型数据*的指针变量p1

其中int表示该指针变量的基类型,他用来指定该指针变量可以指向的变量的类型。比如int *p1;这里的指针变量p,就只能指向int类型的变量,而不能是char类型的。原因如下:

不同类型的变量在内存中分配的空间大小是不同的,int是4字节,char是1字节。指针变量指向的地址表示的是该变量的存储单元的首地址,比如上面提到的变量i的地址是2000,其实2000、2001、2002、2003这4个字节的空间都是归变量i所有的。当我们拿着2000这个地址要去读数据的时候,就必须知道这个数据是什么类型,我们才知道要往后读多少个字节

**注意:指针变量只能存放地址,不要将一个整数赋值给一个指针变量。**比如:

*pointer = 100;

引用指针变量

在引用指针变量的时候可能有以下三种情况:

  1. 给指针变量赋值,如:

    int *p;
    p = &i;	//把变量i的地址赋给指针变量p
    
  2. 引用指针变量指向的变量

    printf("%d", *p);		//以整数形式输出指针变量p所指向的变量的值,即变量i的值
    *p = 10;			//表示把指针变量p所指向的变量i的值设为10,即i = 10,这种方式叫做指针的解引用
    
  3. 引用指针变量的值

    printf("%o", p);		//以八进制数形式输出指针变量p的值,即输出变量i的地址,即&i
    

注意:

  • &取地址运算符,用来得到一个变量的地址值
  • *指针运算符*p表示的是指针变量p指向地变量

指针变量作为函数参数

可以通过指针变量做为一个函数的参数,将某个变量的地址,传入到另一个函数中。

普通变量在作为函数参数时,传递的是该变量的一个副本,即在下一个函数中的变量与传递进来的变量根本不是在同一个内存空间,只不过是将上一个该变量的值进行了拷贝传递进来,在函数内部如果改变了该变量的值,在外部是感受不到的。举个栗子:

void swap(int x, int y){
    int temp = x;
    x = y;
    y = temp;
}
void main(){
    int a = 5;
    int b = 10;
    swap(a, b);
    printf("a = %d, b = %d", a, b);
}

上面这种方式swap()函数内部进行了变量x与变量y的交换,但是在main()函数中的ab是没有改变的,因为在函数调用的时候,是把变量ab的值分别赋值给了新的变量xy,这两个变量在内存中分别有自己的空间,互不影响。

image-20240307133112316

所以要将上面的代码的改为下面这样:

void swap(int *p1, int *p2){
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
} 
void main(){
    int a = 5;
    int b = 10;
    swap(&a, &b);	//传入a,b的地址
    printf("a = %d, b = %d", a, b);
}

指针与数组

指针与一维数组

int a[5] = {0};		//声明一个长度为5的整型数组

数组名a表示的是数组在内存中的首地址,即a[0]的地址。

int *p = a;		//数组名a表示数组的首地址
int *p = &a[0];		//数组a首元素的地址

以上这两条语句是一样的效果,即将数组a的首地址赋给指针变量p(注意不是*p)。

在引用数组元素时指针的运算

在指针指向数组元素时,可以进行以下运算:

  • 加/减一个整数,如p + 1,表示该数组中的下一个元素
  • 自加/自减运算,如p++,表示指向该数组中的下一个元素
  • 两个指针相减,如p2 - p1,表示p2所指元素与p1所指元素之间相差几个元素。
    • 注意只有当p1p2都指向同一个数组中的元素时才有意义;
    • 两个地址不能相加,比如p1 + p2没有实际意义
通过指针引用数组元素
  1. 下标法

    我们可以通过下标来访问数组元素,也可以通过指针来访问。a[i],访问的就是从数组首地址a[0]开始的第i个元素。

  2. 指针法

    那么通过指针来访问就是*(a + i),这里的a就是数组首地址,再加上i个存储单元,那么a+i,就是数组中从0开始,第i个元素的内存地址,最后用*表示指针的解引用,得到的就是a[0+i]的元素值。

    注意:这里的a + i得到的是一个地址值,并且这里的i表示的不是i个字节数,而是**i个数组元素所占的字节数**。比如数组a中存储的是int类型的变量,那么a + i就是首地址a 加上i*4个字节。

    因此a[i]等价于*(a + i),等价于p[i],等价于*(p + i)

指针与多维数组

多维数组元素的地址

在C语言中,理解多维数组元素的地址是理解多维数组内存布局的关键。多维数组在内存中是连续存储的,这意味着无论数组有多少维,其元素都会被放置在一块连续的内存区域中。了解如何计算多维数组元素的地址可以帮助你更好地理解数组是如何工作的,以及如何通过指针访问数组元素。

一维数组的地址

在讨论多维数组之前,先简单回顾一下一维数组。假设有一个一维数组arr,其元素类型为T(可以是intfloat等),数组的第i个元素(从0开始计数)的地址可以通过以下方式计算:

&arr[i] = 基地址 + i * sizeof(T)

其中基地址是数组首元素arr[0]的地址,sizeof(T)是数组元素类型的大小。

二维数组的地址

对于二维数组,情况稍微复杂一些。假设有一个二维数组定义为T arr[M][N],其中M是行数,N是列数。数组的某个元素arr[i][j](其中i表示行索引,j表示列索引)的地址可以通过以下方式计算:

&arr[i][j] = 基地址 + (i * N + j) * sizeof(T)

这里,(i * N + j)计算的是元素arr[i][j]在按行展开的一维数组中的索引位置,然后乘以类型T的大小sizeof(T)得到从基地址开始的偏移量。

多维数组的地址

对于更高维度的数组,地址的计算方式遵循相似的逻辑。以三维数组T arr[X][Y][Z]为例,某个元素arr[i][j][k]的地址可以通过以下方式计算:

&arr[i][j][k] = 基地址 + ((i * Y + j) * Z + k) * sizeof(T)

这里,((i * Y + j) * Z + k)计算的是元素arr[i][j][k]在按行、面展开的一维数组中的索引位置。

总结
  • 多维数组在内存中是连续存储的。
  • 无论数组有多少维,其元素的地址计算都遵循将多维索引转换为一维索引的逻辑,然后根据一维索引计算地址。
  • 理解多维数组元素地址的计算方法有助于深入理解数组的内存布局,以及如何通过指针操作数组元素。

通过掌握这些概念,你将能够更加灵活和高效地使用C语言中的数组和指针。

动态分配内存

C语言的内存映像

image-20240308165045301

在C语言中,动态内存分配是一个非常重要的概念,它允许程序在运行时根据需要分配和释放内存。这种能力使得程序可以更加灵活地处理数据,尤其是在处理数据大小未知或数据大小会变化的情况下。C语言提供了几个标准库函数来支持动态内存管理,主要包括malloccallocreallocfree

malloc

malloc函数用于分配一块指定大小的内存区域。它的原型定义在stdlib.h头文件中,其基本语法如下:

void* malloc(size_t size);
  • size:要分配的内存大小(以字节为单位)。
  • 返回值:如果分配成功,返回指向分配内存的指针;如果失败,返回NULL。

分配的内存是未初始化的,可能包含任意数据。

calloc

calloc函数类似于malloc,但有两个不同之处:一是它可以分配多个连续的对象;二是分配的内存会自动初始化为零。其原型也定义在stdlib.h中,基本语法如下:

void* calloc(size_t num, size_t size);
  • num:要分配的元素个数。
  • size:每个元素的大小(以字节为单位)。
  • 返回值:如果分配成功,返回指向分配内存的指针;如果失败,返回NULL。

image-20240308165325340

realloc

realloc函数用于重新调整之前分配的内存块的大小。这可以用于扩大或缩小内存块。它的原型定义在stdlib.h中,基本语法如下:

void* realloc(void* ptr, size_t size);
  • ptr:指向先前由malloccallocrealloc分配的内存块的指针。
  • size:新的内存块大小(以字节为单位)。
  • 返回值:如果重新分配成功,返回指向新内存的指针;如果失败,返回NULL。

如果size为0,则realloc会释放ptr指向的内存,并返回NULL。

free

free函数用于释放之前通过malloccallocrealloc分配的内存。释放后的内存不能再被访问,否则会导致未定义行为。free的原型定义在stdlib.h中,基本语法如下:

void free(void* ptr);
  • ptr:指向先前分配的内存块的指针。

使用动态内存时,必须确保最终释放分配的内存,以避免内存泄露。正确管理动态内存是编写健壮、高效C程序的关键之一。

示例

以下是一个使用mallocfree的简单示例:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int*) malloc(sizeof(int)); // 分配一个整型变量的内存
    if (ptr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    *ptr = 10; // 使用分配的内存
    printf("%d\n", *ptr);
    free(ptr); // 释放内存
    return 0;
}

这个例子展示了如何动态分配一个整数的内存,使用它,然后释放它。正确地使用动态内存分配和释放对于避免内存泄露和保证程序稳定性至关重要。

动态分配内存实现二维数组

在C语言中,通过动态分配内存来创建二维数组需要使用指针和malloc()calloc()函数。这种方法提供了更多的灵活性,特别是当你事先不知道数组大小时。

使用一级指针模拟二维数组

在C语言中,可以使用一级指针来模拟二维数组的行为。这种方法通常涉及到计算索引以访问内存中连续存储的数据。这样做的一个好处是,你可以动态地根据需要分配和调整内存的大小,这在处理变长的数据结构时非常有用。

要使用一级指针模拟二维数组,你首先需要确定每个维度的大小。假设我们想模拟一个rows x cols的二维数组。接下来,我们分配足够的连续内存来存储所有元素,即rows * cols个元素的空间。然后,通过适当的索引计算,我们可以像访问二维数组那样访问这些元素。

动态分配内存
#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 5;
    int cols = 4;
    int *array;

    // 分配内存
    array = (int *)malloc(rows * cols * sizeof(int));
    if (array == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    // 初始化数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            *(array + i * cols + j) = i * cols + j; // 计算索引并赋值
        }
    }

    // 打印数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", *(array + i * cols + j));
        }
        printf("\n");
    }

    // 释放内存
    free(array);

    return 0;
}
关键点
  1. 内存分配:使用malloc根据二维数组的总大小(rows * cols)分配足够的内存。
  2. 索引计算:通过*(array + i * cols + j)访问元素,其中i是行索引,j是列索引。这里,我们将二维数组的索引映射到一维数组的索引上。
  3. 内存释放:使用完数组后,不要忘记使用free释放分配的内存,避免内存泄漏。
总结

通过一级指针和适当的索引计算,我们可以在C语言中模拟二维数组的功能。这种方法提供了更多的灵活性,特别是在处理动态数据结构时。然而,它也要求程序员更加小心地管理内存,包括正确地分配和释放内存,以避免内存泄漏或其他内存相关的错误。

使用二级指针分配二维数组

在C语言中,使用二级指针分配二维数组是一种模拟真正的二维数组行为的方法,同时提供了动态内存管理的灵活性。这种方法涉及到动态地为每一行分配内存,然后用一个指针数组(即二级指针)来管理这些行。这样做的好处是可以处理不同长度的行,从而创建“不规则”的二维数组。

步骤
  1. 分配指针数组:首先,你需要分配一个指针数组,其中每个指针将用于指向一行。
  2. 为每行分配内存:接着,对于指针数组中的每个指针,分别分配足够的内存以存储相应行的数据。
  3. 使用二维数组:一旦分配了内存,就可以像使用常规二维数组那样使用它,通过二级指针访问元素。
  4. 释放内存:使用完毕后,需要首先释放每一行的内存,然后释放存储行指针的数组。
示例代码

下面是一个具体的例子,演示如何使用二级指针动态分配二维数组,并初始化、使用和释放这个数组:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 5; // 行数
    int cols = 4; // 列数
    int **array;

    // 第1步:为行指针分配内存
    array = (int **)malloc(rows * sizeof(int *));
    if (array == NULL) {
        fprintf(stderr, "内存分配失败\n");
        return 1;
    }

    // 第2步:为每行分配内存
    for (int i = 0; i < rows; ++i) {
        array[i] = (int *)malloc(cols * sizeof(int));
        if (array[i] == NULL) {
            fprintf(stderr, "内存分配失败\n");
            // 出错时,释放之前已分配的内存
            for (int j = 0; j < i; ++j) {
                free(array[j]);
            }
            free(array);
            return 1;
        }
    }

    // 第3步:使用二维数组
    // 初始化二维数组
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            array[i][j] = i * cols + j;
        }
    }

    // 打印二维数组
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }

    // 第4步:释放内存
    for (int i = 0; i < rows; ++i) {
        free(array[i]); // 释放每行的内存
    }
    free(array); // 释放行指针数组的内存

    return 0;
}
关键点
  • 使用二级指针分配二维数组提供了更多的灵活性,特别是对于行长度可能不同的情况。
  • 动态分配内存时,务必记得最后释放内存,以避免内存泄露。
  • 在为每行分配内存时,如果任何一次malloc调用失败,应该先释放之前已经成功分配的内存,然后退出程序。这是良好的错误处理实践。

通过这种方式,你可以灵活地处理各种大小和形状的二维数据结构,使你的C语言程序能够更有效地处理动态数据。

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

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

相关文章

指针的学习5

目录 sizeof和strlen的区别 sizeof strlen 数组和指针笔试题解析 一维数组 字符数组 二维数组 指针运算笔试题解析 题目1&#xff1a; 题目2&#xff1a; 题目3&#xff1a; 题目4&#xff1a; 题目5&#xff1a; 题目6&#xff1a; 题目7&#xff1a; sizeof和…

安装配置Hadoop集群

安装配置Hadoop集群的主要步骤 1、安装配置Hadoop 2、配置用户环境变量 3、配置Hadoop 配置core-site.xml文件配置hdfs-site.xml文件配置mapred-site.xml文件配置yarn-site.xml文件配置slaves文件配置hadoop-env.sh文件 更多配置文件的配置信息请参见官方网站的解释。 4、…

vue2中使用异步组件

在大型应用中&#xff0c;我们可能需要将应用分割成小一些的代码块&#xff0c;并且只在需要的时候才从服务器加载一个模块。这时就就可以使用异步组件。 1.通过import方式引入 //组件1<tempalte><Parent v-if"show"></Parent><button clickha…

关于Spring依赖注入简洁方式的探索

最近在项目开发过程中关注到一个依赖注入的写法差异&#xff0c;因为本人代码上有点强迫症&#xff0c;看到这种不同人不一样的写法&#xff0c;特意了解了一下&#xff0c;但是依然有部分疑惑未解。 两种写法&#xff1a;(就是传说中最常见的属性注入和构造函数注入) Service…

云打印机多少钱一台?

随着新的一年的开始&#xff0c;很多同学们都开始打印资料&#xff0c;以应对新一年的各种考试。但是对于学生们来说&#xff0c;去打印店打印价格贵、打印不方便、没时间去打印等多种原因导致我们没办法及时打印资料&#xff0c;这个时候我们就需要用到云打印机。那么云打印机…

浅谈游戏AI LOD的智能控制——LOD交易员

前引 LOD的概念 提到 细节层次 &#xff08;Level of Details&#xff0c;简写LOD&#xff09;&#xff0c;大家可能首先会想到图像渲染&#xff0c;像游戏中大地图的3D物体会随玩家与其距离的远近而变化精度&#xff08;主要是模型面数的变化&#xff0c;有时还会直接剔除&a…

CSS基础知识

font-family: "Trebuchet MS", Verdana, sans-serif; 字体栈&#xff0c;浏览器会一个一个试过去看下哪个可以用 font-size16px; font-size1em; font-size100%;//相对于16px 字体大小&#xff0c;需要进行单位换算16px1em font-weightnormal;//400font-weight属性…

ai直播数字人:AI大模型应用开发的神奇世界

当AI技术的发展走向一个新的高峰&#xff0c;AI直播数字人逐渐成为人们关注的焦点。这种全新的数字人形态&#xff0c;通过大模型应用开发&#xff0c;带来了一个神奇世界。 在这个神奇世界里&#xff0c;AI直播数字人可以展现出与真实人类相媲美的外貌和声音。通过先进的图像…

HarmonyOS ArkTS工程目录结构(Stage模型)

1. ArkTS工程目录结构&#xff08;Stage模型&#xff09; 官方文档&#xff08;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/start-with-ets-stage-0000001477980905-V2&#xff09; 1.1. AppScope AppScope > app.json5&#xff1a;应用的全局配…

图的单源最短路径问题

目录 一、简述 二、前置配置 三、迪杰斯特拉算法 四、改进的迪杰斯特拉算法 五、贝尔曼福特算法 一、简述 图是一种比较常用的数据结构&#xff0c;将问题转换成图相关的思路也是比较常用的。 图的单源最短路径问题&#xff0c;也就是图中某一个节点到图中其他节点的最短路…

基于SSM的植物园管理系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 开发技术简介 3 1.1 SSM框架 3 1.2 JSON 3 1.3 Ajax 4 1.4 Bootstrap前台框架 4 1.5 Eclipse 4 1.6 本章小结 4 2 系统分析 5 2.1可行性分析 5 2.1.1 技术可行性 5 2.1.2 经济可行性 5 2.1.3 操作可行性 5 2.2 功能需求 5 2.3 用例分析 6…

洞悉 Kubernetes 高阶奥秘:掌控资源、网络、存储,玩转容器化应用!

昨天我们已经入门了K8S&#xff0c;今天带大家学习一下资源、网络、存储这几个进阶的知识点模块内容。这几天陆陆续续会把K8S从头到尾讲一遍&#xff0c;最后会带大家实战一下&#xff0c;下面就开始今天的学习吧。 高级资源和控制器 Kubernetes 提供了一系列高级资源和控制器…

请编程输出无向无权图各个顶点的度 ← 链式前向星存图

【题目描述】请利用链式前向星存图&#xff0c;编程输出无向无权图各个顶点的度。【输入样例】 5 6 1 3 2 1 1 4 2 3 3 4 5 1【输出样例】 4 2 3 2 1【算法分析】 本例需要用到基于链式前向星的广度优先搜索&#xff08;BFS&#xff09;。 链式前向星广度优先搜索&#xff08;B…

JavaScript 实现飞机大战

文章目录 一些关键点概览&#xff1a;核心模块的具体实现示例&#xff1a;飞机类&#xff08;Plane&#xff09;的基本结构&#xff1a;子弹类&#xff08;Bullet&#xff09;的基本结构&#xff1a;敌机类&#xff08;Enemy&#xff09;的基本结构&#xff1a; 基于前面定义的…

Idea创建Maven项目

Maven安装配置步骤&#xff1a; 解压安装 bin目录 &#xff1a; 存放的是可执行命令。&#xff08;mvn 命令重点关注&#xff09; conf目录 &#xff1a;存放Maven的配置文件。&#xff08;settings.xml配置文件后期需要修改&#xff09; lib目录 &#xff1a;存放Maven依赖的j…

Python快速入门系列-2(Python的安装与环境设置)

第二章&#xff1a;Python的安装与环境设置 2.1 Python的下载与安装2.1.1 访问Python官网2.1.2 安装Python对于Windows用户对于macOS用户对于Linux用户 2.2 集成开发环境&#xff08;IDE&#xff09;的选择与设置2.2.1 PyCharm2.2.2 Visual Studio Code2.2.3 Jupyter Notebook2…

bat文件给多个Android设备安装apk

本文是安装一个apk 1、确保以下3个文件在同一个目录下 1>要安装的apk&#xff0c;这里是mmb.apk 2>设备名单&#xff0c;保存在.txt文件中&#xff0c;一行一个设备名&#xff0c;设备名通过adb devices获取&#xff0c;截图中是两个设备 txt文件中的样式 3>要运行…

基于springboot实现大学外卖管理系统项目【项目源码+论文说明】

基于springboot实现大学外卖管理系统演示 摘要 如今&#xff0c;信息化不断的高速发展&#xff0c;社会也跟着不断进步&#xff0c;现今的社会&#xff0c;各种工作都离不开信息化技术&#xff0c;更离不开电脑的管理。信息化技术也越来越渗透到各小型的企业和公司中&#xff…

AI 资讯 | GPT-4 时代终结!Claude 3 一举成为地表最强 AI 模型,今天就能用上!

AI 的飞速发展&#xff0c;对开发者而言意义重大。为此&#xff0c;我们精心筛选了最新 AI 相关资讯与大家分享交流。 未来&#xff0c;Apifox 也将时刻关注 AI 领域发展动态&#xff0c;及时呈现全面的 AI 资讯&#xff0c;与大家一起把握 AI 机遇。希望 在这些资讯中&#xf…

3.9Code

基于顺序存储结构的图书信息表的图书去重 #include<iostream> #include<stdlib.h> #include<string.h>typedef int status;#define OK 1using namespace std;typedef struct{char no[50];char name[50];float price; }Book;typedef struct{Book* elem;int …