笔记-论数据结构2

指针与函数

设计一个函数,传入两个int参数,并交换这个两个参数的值

复制代码
#include<stdio.h>

void swap(int a, int b){
    int temp;
    temp = a;
    a = b;
    b = temp;
    printf("a的值为:%d, b的值为:%d\n", a, b);
}

1int main(int argc, char const *argv[])
{
    int m = 5;
    int n = 10;
    swap(m, n);
    pritnf("m的值为:%d, n的值为:%d\n", m, n);
    return 0;
}

这样录入的代码,会有两行结果:10 5和5 10

在main函数中执行swap语句,相当于是把m和n的值传到了另一个副本,而非是m和n这二者本身,在另外的副本中(函数swap)执行temp交换的代码部分之后,swap副本里面的值a和b就跟着发生了变化。

BUT!

这种改变并没有影响到原来的那个m和n

简单通俗地说,发生变化的只有他们的值,不是他们的地址

这样的函数,能够让两个变量的值发生转换......

可是,如何做到让m和n这两个参数变量的值直接转换,而不是通过不相关的变量实现呢?

复制代码
#include<stdio.h>
swap(int*a, int* b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
    printf("a=%d,b=%d\n",*a, *b);
    return 0;
}
int main(int argc, char const *argv[])
{
    int m = 5;
    int n = 10;
    swap(&m, &n);
    pirntf("a=%d,b=%d\n",m, n);
    return 0;
}

只需要加上地址就可以了。

如果m和n的地址分别是在101和105上,那么当在main函数中用&(取地址符)将二者的地址传入swap函数当中,那么此刻swap函数中的m和n不再是m和n的值,而是它们的地址里存放的值。

在swap函数中temp交换代码部分执行过后,a和b的值确实会互换,但互换的是m和n的地址里存放的值。

输出结果会是 10 5和 10 5

指针与数组

在C语言中,指针与数组的关系十分密切。

通过数组下标能完成的操作都可以通过指针完成。

一般来说,用指针编写的程序比用数组下标编写的程序执行速度快。

复制代码
int a[] = {15, 22, 67, 43, 87};
int *p;
p = a;

printf("%p\n", a);
printf("%p\n", p);
printf("%d\n", *p);

像上述代码,如果把a直接赋值给p,数组的首地址会给赋值给p,*p是根据地址找到值,那么*p会输出什么?

据我们所知,a数组中的首元素地址会被赋值给p,那么*p就会根据首元素地址找到首元素这个值,理所应当地输出15

那么按照这个定理,如果我想要输出的不是15,我想要输出后面的22,67,43,87这些数,是不是意味着如果我将a数组中别的地址赋值给p,*p就会输出该地址所含的值呢?

复制代码
int a[] = {15,22,67,43,87};
int *p;
p = a;
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
{
    printf("%d\n"; a[i]);
}
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
{
    printf("%d\n", *(p + i));
}

声明一个数组,并将数组首地址赋值给*p;

然后经历一次数组的遍历循环;

用指针遍历一下数组,*p存储的是首地址的值,那么让*p也遍历不就行了。

*(p + i) ......i++

这个时候,*p就会跟着i遍历数组而输出当前i所代表下标的值。

不过此时,问题又来了。*(p+i)能跟着i遍历而输出遍历的值,可是括号内的p此时代表的是地址,地址101+(假设i=1)1=102,那么他应该输出遍历的值应该是(101 102 103 104 105)的地址,又怎么能做到准确无误地输出(101 105 109 113 117 121)所下标的数组的值呢?

在对指针做算术运算的时候,规则和我们平常的规则有些不一样,由此引出我们下一个概念------

指针做算术运算

给指针加上一个整数,实际上加的是这个整数和指针数据类型对应字节数的乘积。

复制代码
int a = 5;
int *p = &a;
printf("%p\n", p);
p++;
printf("%p\n", p);
return 0;

p++ --> p = p + 1 --> p = p + 1 * 4

可以理解为 p + 1其实是 p + 1*sizeof(int)

对指针来说,指针+1其实代表的是指针走了一个步长。

(注意,p++要单独拿出来,因为*p++和*(p+1)不是一个意思。写作*p++,表示的是先用*访问p++的返回值也就是p,再p=p+1.)

结构体的声明

结构体是一个或多个变量的集合,这些变量可以是不同的类型。

本质上,结构体其实就是用户(你)定义的数据类型,只不过这种数据类型是自定义的。

可是,我们日常代码作业中能够遇到那么多的数据类型,char\short\long\int\double......等,难道这些数据类型不够用吗?

答案是确实不够用,假设我们现在需要声明二维xy轴上的坐标点,普通的数据类型很难帮我表示出来这些,即便能够表示,那也是需要两个变量,定义个一个int数据类型声明x,在定义一个int数据类型声明y......

这样确实能够帮我们声明一个函数轴上的xy坐标点,可是如果我只想用一个变量就解决xy坐标点,这个时候结构体就是一个很好的选择。

复制代码
struct 结构体名
{
    数据类型 变量名;
    数据类型 变量名;
    ......
};

实现xy坐标轴的结构体

复制代码
struct point
{
    int x;
    int y;
};

结构体的初始化与调用

复制代码
struct 结构体名 变量名;

struct point p;

给结构体中的xy赋值规矩如下:

复制代码
变量名.结构体内部变量名 = 值

p.x = 10;
p.y = 15

#include<stdio.h>

struct point
{
    int x;
    int y;
}

int main(int argc, char const *argv[]){
    struct point p;
    p.x = 5;
    p.y = 10;
    printf("%d\n", p.x);
    printf("%d\n", p.y;
    return 0;
}

结构体实例--创建一个point

编写一个函数,传入两个int参数,在函数创建一个结构体point类型的变量,将传入的两个参数分别赋值给该结构体变量的x和y,最后将该结构体变量返回 。

复制代码
struct point createPoint(int x, int y)
{
    struct point temp;
    temp.x = x;
    temp.y = y;
    return temp;
}

#include<stdio.h>
struct point{
    int x;
    int y;
};


struct point creatPoint(int x, int y){
    struct point temp;
    temp.x = x;
    temp.y = y;
    return temp;
}

int main(int argc, char const *argv[])
{
    struct point p;
    p = creatPoint(5, 10);
    printf("%d\n", p.x);
    printf("%d\n", p.y);
    return 0;
}

结构体与指针

在一些场景中,如果传递给函数的结构体很大,使用指针方式的效率通常更高。

pp指向一个point结构体

复制代码
struct point *pp;

为pp所指向的结构体中的属性赋值

复制代码
(*pp).x = 10;
(*pp).y = 5;

为了方便使用,C语言提供了另外一种简写方式

复制代码
pp->x = 10;
pp->y = 5;

大概是这种书写形式:结构体指针->结构体成员名

复制代码
#include<stdio.h>

struct point
{
    int x;
    int y;
};

int main(int argc, char const *argv[])
{
    struct point p;
    p.x = 5;
    p.y = 10;
    
    struct point *pp;
    pp = &p;    

    pp->x = 10;
    pp->y = 5;

    printf("x=%d,y=%d\n", p.x, p.y);
    printf("x=%d,y=%d\n", pp->x, pp->y);
    return 0;
}

用指针通过地址去改原本struct point p里面的值

类型定义

复制代码
typedef 数据类型 别名

typedef int zx

#include<stdio.h>

typedef int myType1;
typedef char myType2;

int main(int argc, char const *argv[])
{
    myType1 a = 5;
    myType2 b = '0';
    printf("%d\n", a);
    printf("%c\n", b);
    return 0;
}

typedef也经常用在结构体上:

每次声明结构体变量都要写struct关键字,很麻烦,而且逻辑上也很难受,typedef可以解决这个问题。

复制代码
struct point
{
    int x;
    int y;
}
   
int main(int argc, char const *argv[])
{
    struct point p;
    p.x = 5;
    p.y = 10;
    printf("%d\n", p.x):
    printf("%d\n", p.y);
    return 0;
}

而利用typedef解决有两种解法:

解法1:

复制代码
typedef struct 结构体名
{
    数据类型 变量名;
    数据类型 变量名;
    ......
}别名;

解法2:

复制代码
typedef struct
{
    数据类型 变量名;
    数据类型 变量名;
    ......
}别名;

实际的运用

复制代码
typedef struct point
{
    int x;
    int y;
}po;

typedef struct
{
    int x;
    int y;
}po;

更多更常用的第二种,typedef+结构名

复制代码
#include<stdio.h>
typedef struct
{
    int x;
    int y;
}po;

int main(int argc, char const *argv[])
{
    po p;
    p.x = 5;
    p.y = 10;
    printf("%d\n", p.x);
    printf("%d\n", p.y);
    return 0;
}

内存的分类

C程序编译后,会以三种形式使用内存:

**静态/全局内存:**静态声明的变量和全局变量使用这部分内存,这些变量在程序开始时分配,直到程序终才消失。

**自动内存(栈内存):**函数内部声明的变量使用这部分内存,在函数被调用时才创建。

通常我们在函数里面写的东西,肯定不算是全局变量,也没有加类似static关键字的一定不叫静态。我们目前为止写的日常的代码,基本都保存在栈中。

我们平常在函数中写的代码(main函数又或者自己创建的函数),都是在函数中的代码基本上都是在栈内存当中的

栈内存:我们每一次写代码,它会自动在栈内存当中创建一片空间,存储这个东西。自动也意味着我们不能控制内存,栈内存的自动存储是不受控制的,我们没有办法决定去说它什么时候开辟空间,什么时候释放空间,还可能遇到我们写的代码在我们不知情下,把这片空间释放了。

**动态内存(堆内存):**根据需求编写代码动态分配内存,可以编写代码释放,内存中的内容直到释放才消失。

堆内存,就是受我们控制的内存空间。

动态内存分配:

在C语言中,动态分配内存的基本步骤:

1:使用malloc (memory allocate)函数分配内存

void* malloc(size_t)

用这个函数,就是要从堆内存中开辟空间,但是需要开辟多大的空间,堆内存不知道,所以需要传入这个参数,告诉系统要开辟多大的空间,再把地址指针返回。

比喻的话:就是让一个人去创建多大的房子,告诉他需要三室两厅还是两室一厅,要平房还是别墅,在房子盖好后,还需要从那个人口中知道房子在哪儿,房子的地址......

还有一个问题,为什么要用void*?

因为void是通用函数类型,如果往这片空间存入一个int类型的数据变量,那么返回值就是int*的指针;如果存入一个point结构体变量,那么返回值就是point*的结构体指针,诸如此类。

在一开始,系统并不不知道我们要往内存空间存储什么东西,它只负责给我返回指针,而得到返回指针的我们,清楚我们需要的是什么类型的数据类型,那么便可以强制转换,把void*转换成我们想要的(int* double* point*诸如此类)(但是要考虑字节的问题)

如果成功,会返回从堆内存上分配的内存指针

如果失败,会返回空指针

2:使用分配的内存

3:使用free函数释放内存

动态内存分配实例 - 基本数据类型

复制代码
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char const *argv[])
{
    int *p;
    p = (int*)malloc(sizeof(int));
    *p = 15;
    printf("%d\n", *p);
    free(p);
    return 0;
}

动态内存分配示例 - 字符串

复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(int argc, char const *argv[]){
    char *s;
    s = (char*)malloc(10);
    strcpy(s, "Hello");
    printf("%s\n", s);
    return 0;
}

动态内存分配示例 - 数组

复制代码
#include<stdio.h>
#inlcude<stdlib.h>

int mian(int argc, char const *argv[]){
    int *arr = (int*)malloc(5 * sizeof(int));
    for(int i = 0; i < 5; i++)
    {
        arr[i] = 0;
    }
    
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

动态内存分配示例 - 结构体

复制代码
#include<stdio.h>
#include<stdlib.h>

typedf struct
{
    int x;
    int y;
}po;

int main(int argc, char const *argv[]){
    po *p;
    po = (po*)malloc(sizeof(po));
    p.x = 5;
    p.y = 10;
    printf("x = %d, y = %d\n", p.x, p.y);
    return 0;
}

值得注意的是,结构体变量占位字节具有补位机制

相关推荐
普通人618 小时前
PHP文件读写完全指南
开发语言·php·1024程序员节
Martina_032121 小时前
AI生成3D场景从白模换成精模后镜头全偏了?用6步检查单位、枢轴与层级替换
图像处理·人工智能·3d·ai·自然语言处理·材质·1024程序员节
星云_byto2 天前
CSS布局
人工智能·机器学习·目标跟踪·1024程序员节
张小姐的猫4 天前
【MySQL】访问 & 数据流动原理
android·数据库·mysql·adb·1024程序员节
工业HMI实战笔记6 天前
印刷电路板HMI:SMT贴装的元件定位界面
制造·设计规范·设计语言·1024程序员节
Martina_03216 天前
AI生成3D模型一合并网格,材质就串色?用6步检查材质槽、UV与图集重映射
图像处理·人工智能·3d·材质·uv·游戏策划·1024程序员节
橙光成长6 天前
android-ad-cleaner:android免 Root 批量关闭系统开屏与营销推送
android·1024程序员节
燃烧的枫7 天前
Windows 下 YOLO11 Pose 的 TensorRT C++ 部署
windows·yolo·tensorrt·1024程序员节·yolo11-pose