1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > C语言指针 结构体与动态内存分配与释放复习

C语言指针 结构体与动态内存分配与释放复习

时间:2022-03-14 20:45:25

相关推荐

C语言指针 结构体与动态内存分配与释放复习

一、指针

1.1 指针的基本介绍

指针的重要性:指针是C语言的灵魂。

指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。

地址:

是从内存单元的编号 ,是从0开始的非负整数 ,如:0–FFFFFFFF【4G-1】

指针:

指针就是地址 ,地址就是指针 。指针变量是存放在内存单元地址的变量, 指针的本质是一个操作受限的非负整数

基本概念

int i = 0;int *p = &i;//等价于int *p; p = &i;

详解这两步操作:

(1)p 存放了 i 的地址,所以我们说 p 指向了 i

(2)p 和 i 是完全不同的两个变量,修改其中的任意一个变量的值不影响另一个变量的值

(3)p 指向 i, *p 就是i变量本身。更形象的说所有出现 *p 的地方都可以换成 i,所有出现 i 的地方都可以换成 *p

注意:

指针变量也是变量,只不过它存放的不能算是内存单元的内存地址

普通变量前不能加 * ,常量和表达式前不能加&

实例:

#include<stdio.h>int main(){int *p;//p是个变量名,int * 表示该p变量只能存储int类型变量的地址int i = 10;int j;//j = *p;//p没保存地址,那么*p就不知道指向哪个地址p = &i;j = *p;//等价于 j = i;//p = 10//errorprintf("i = %d, j = %d, *p = %d\n", i, j, *p);return 0;}

内存分析图:

1.2 指针的运算(++,–,+,-)

指针是一个用数值表示的地址。可以对指针执行算术运算。

数组名

一维数组名是个指针常量, 它存放的是一维数组第一个元素的地址, 它的值不能被改变,一维数组名指向的是数组的第一个元素

下标和指针的关系

a[i] <<==>> *(a+i)

假设指针变量的名字为p, 则 p+i 的值是p+i*(p所指向的变量所占的字节数)

实例:

#include<stdio.h>int main(){int arr[5] = {1,2,3,4,5};int i, *p;//p是一个int* 指针p = arr;//p指向了arr数组的首地址for(i = 0;i < 5;i++){printf("arr[%d] 地址=%p\n",i,p);printf("存储值:arr[%d]=%d\n",i,*p);p++;}return 0;}

内存结构图:

● 数组在内存中是连续分布的

● 当对指针进行++时,指针会按照它指向的数据类型字节数大小增加,比如int* 指针,每++,就增加 4 个字

节。

所有指针变量只占4个字节,用第一个字节的地址表示整个变量的地址。

1.3 指针数组

要让数组的元素指向 int 或其他 数据类型的地址(指针)。可以使用指针数组。

指针数组定义:

数据类型 *指针数组名[大小]

如:int *arr[3];

arr 声明为一个指针数组 。

arr 由 3 个整数指针组成。因此,arr 中的每个元素,都是一个指向 int 值的指针。

比如:

#include<stdio.h>int main(){int arr[3] = {100,200,300};int i, *p[3];//p是一个int* 指针for(i = 0;i < 3;i++){p[i] = &arr[i];//赋值为整数的地址}for(i = 0;i < 3;i++)//指针数组获取各个值{printf("arr[%d]=%d p[%d]地址=%p\n",i,*p[i],i,&p[i]);}return 0;}

内存布局:

1.4 指向指针的指针(多重指针)

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

(1)一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,声明了一个指向int 类型指针的指针:int **ptr;

(2)当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,比如**ptr

实例:

#include<stdio.h>int main(){int var;int *p;//一级指针int **pp;//二级指针var = 1000;p = &var;//var变量的地址赋值给ppp = &p;//表示将p存放的地址,赋值给ppprintf("var的地址=%p var = %d\n",&var,var);printf("p本身的地址=%p p存放的地址 = %d *p = %d\n",&p,p,*p);printf("pp本身的地址=%p pp存放的地址 = %d *pp = %d\n",&pp,pp,*pp);return 0;}

内存结构图:

1.4 传递指针(地址)给函数

当函数的形参类型是指针类型时,使用该函数时,需要传递指针,或者地址,或者数组给该形参。

如何通过被调函数修改主调函数中普通变量的值?

实参为相关变量的地址形参为以该变量的类型为类型的指针变量在被调函数中通过 *形参变量名 的方式就可以修改主函数中的变量的值

实例1(传地址或指针给指针变量):

#include<stdio.h>void f(int *p)//不是定义了一个名字叫做*p的形参,而是定义了一个形参,该形参名字叫做p,它的类型是int *{*p += 1;}int main(){int i = 10;int *p = &i;//将i的地址赋值给pf(&i);//传地址printf("i = %d\n", i);//i=11f(p);//传指针printf("i = %d\n", i);//i=12return 0;}

内存分析:

实例2(传数组给指针变量):

#include <stdio.h>/* 函数声明 */double getAverage(int *arr, int size); //int main (){/* 带有 5 个元素的整型数组 */int balance[5] = {1000, 2, 3, 17, 50};double avg;/* 传递一个指向数组的指针作为参数 */avg = getAverage( balance, 5 ) ;/* 输出返回值 */printf("Average value is: %f\n", avg );return 0;}//说明//1. arr 是一个指针,double getAverage(int *arr, int size){int i, sum = 0; double avg;for (i = 0; i < size; ++i){// arr[0] = arr + 0// arr[1] = arr + 1个int字节(4) // arr[2] = arr + 2个int字节(8)sum += arr[i];// arr[0] =>数组第一个元素的地址 printf("\n arr存放的地址=%p \n", arr);}avg = (double)sum / size;return avg;}

在前面说过a[i] <<==>> *(a+i),所以上面的arr[i]还可以写成*(arr + i)。也可以这样:

sum += *arr;arr++; // 指针的++运算, 会对arr 存放的地址做修改

1.5 返回指针的函数

允许函数的返回值是一个指针(地址),这样的函数称为指针函数。

实例:

#include <stdio.h>#include <string.h>char *strlong(char *str1, char *str2){//函数返回的char * (指针)printf("\nstr1的长度:%d str2的长度:%d", strlen(str1), strlen(str2));if(strlen(str1) >= strlen(str2)){return str1;}else{return str2;}}int main(){char str1[30], str2[30], *str; // str 是一个指针类型,指向一个字符串printf("\n请输入第1个字符串:");gets(str1);printf("\n请输入第2个字符串:");gets(str2);str = strlong(str1, str2);printf("\nLonger string: %s \n", str);return 0;}

注意事项:

用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针不能指向这些数据。函数运行结束后会销毁该函数所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存C语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为static变量

1.6 函数指针(指向函数的指针)

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。

函数指针定义:

returnType (* pointerName)(param list);

returnType 为函数指针指向的函数返回值类型pointerName 为函数指针名称param list为函数指针指向的函数的参数列表参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称注意( )的优先级高于*,第一个括号不能省略,如果写作returnType *pointerName(param list);就成了函数原型,它表明函数的返回值类型为returnType*

实例(用函数指针来实现对函数的调用,返回两个整数中最大值):

#include <stdio.h>int max(int a,int b){return a>b?a:b;}int main(){int x,y,maxVal;//说明函数指针//1.函数指针的名字pmax//2. int表示该函数指针指向的函数是返回int类型//3. (int, int)表示该函数指针指向的函数形参是接收两个int//4.在定义函数指针时,也可以写上形参名int (*pmax)(int x, int y) = max;int (*pmax)(int , int)= max; printf("Input two numbers:"); scanf("%d %d", &x, &y);// (*pmax)(x, y)通过函数指针去调用函数maxmaxVal = (*pmax)(x, y);printf("Max value: %d pmax=%p pmax本身的地址=%p\n", maxVal, pmax, &pmax);return 0;}

内存结构图分析:

1.7 指针的注意事项和细节

指针变量存放的是地址,从这个角度看指针的本质就是地址。变 量声明的时候,如果没有确切的地址赋值,为指针变量赋一个NULL值是好的编程习惯。赋为NULL值的指针被称为空指针,NULL指针是一个定义在标准库<stdio.h>中 的值为零的常量#define NULL 0

二、结构体

2.1 为什么要有结构体

比如有一个学生有学号,名字,年龄等属性,多个学生如果按常规变量赋值就会使得代码冗余,那么就要把其各个属性抽象出来,形成一个新的类型,所以结构体诞生了。也就是:为了表示一些复杂的数据,而普通的基本类型变量无法满足要求。

2.2 什么是结构体

结构体是用户根据实际需要自己定义的复合数据类型。其实结构体就是面向对象语言中类,只是结构体中没有方法,并且最后有分号。

class Student{int sid;String name;int sage;void inputStudent(){}void showStudent(){}}

struct Student{int sid;char *name;int sage;}; //分号不能省

2.2.1 结构体和结构体变量的区别与联系
结构体是自定义的数据类型,表示的是一种数据类型.结构体变量代表 一个具体变量,好比:

int numl; //int 是数据类型,而num1是一个具体的int变量struct Student stu;// Student 是结构体数据类型,而 stu是一个Cat变量

2.3 如何使用结构体

两种方式:

struct Student st = {1000,"zhangsan",20};struct Student *pst = &st;

st.sidpst->sid

实际使用如下:

#include <stdio.h>#include<string.h>struct Student{int sid;char *name;//名字,用指针,指向一个字符串int age;};//分号不能省int main(){struct Student st = {1000,"zhangsan",20};//第一种方式/*st.sid = 99;//st.name = "lisi"; //errorst.name = "lisi";st.age = 19;*///第二种方式(常用)struct Student *pst;pst = &st;pst->sid = 99;//pst->sid 等价于 (*pst).sid, 而(*pst).sid等价于st.sid,所以pst->sid等价于st.sidpst->name = "wangwu";pst->age = 21;printf("%d %s %d \n",st.sid,st.name,st.age);return 0;}

2.4 注意事项

结构体变量不能加减乘除,但可以相互赋值普通结构体变量和结构体指针变量作为函数传参问题

#include <stdio.h>#include<string.h>typedef struct Student{int sid;char *name;//名字,用指针,指向一个字符串int age;}Student;//分号不能省void f(Student *pst){pst->sid = 88;pst->name = "mayun";pst->age = 18;}//这种方式消耗内存,耗时间,不推荐void g(Student st){printf("%d %s %d \n",st.sid,st.name,st.age);}void g2(Student *pst){printf("%d %s %d \n",pst->sid,pst->name,pst->age);}int main(){Student st;f(&st);//g(st);g2(&st);return 0;}

typedef的用法

typedef struct Student{int sid;char *name;int age;}Student,*pStudent;//等价于Student代表了struct Student ,pStudent代表了struct Student *

用法:

#include <stdio.h>#include<string.h>typedef struct Student{int sid;char *name;int age;}Student,*pStudent;//等价于Student代表了struct Student ,pStudent代表了struct Student * int main(){Student st;//struct Student st;pStudent ps = &st;//struct Student *st = &st; ps->sid = 999;printf("%d\n",ps->sid);return 0;}

三、动态内存分配与释放

3.1 不同数据在内存中的分配

全局变量一一内存中的静态存储区非静态的局部变量——内存中的动态存储区——stack栈临时使用的数据——建立动态内存分配区域,需要时随时开辟,不需要时及时释放——heap 堆根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名

来引用这些数据,只能通过指针来引用

3.2 内存动态分配相关函数

头文件#include <stdlib.h>声明了四个关于内存动态分配的函数,下面我只介绍最常用的函数malloc和free。

函数原型void * malloc (usigned int size)

● 作用:在内存的动态存储区(堆区)中分配一个长度为size的连续空间。

● 形参size的类型为无符号整型,函数返回值是所分配区域的第-一个字节的地址,即此函数是一个指针型函数,返回的指针指向该分配域的开头位置(函数首地址)。

malloc(100);开辟100字节的临时空间,返回值为其第一个字节的地址

函数原型:void free (void *p)

● 作用:释放变量p所指向的动态空间,使这部分空间能重新被其他变量使用。

● p 是最近一次调用calloc或malloc函数时的函数返回值

● free函数无返回值

free(p);//释放p所指向的已分配的动态空间

C99标准把以上malloc,calloc,realloc函数的基类型定为void类型,这种指针称为无类型指针(typeless pointer) ,即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即仅提供一个纯地址,而不能指向任何具体的对象。

代码示例:

#include <stdio.h>#include<stdlib.h>int main(){int a[5] = {4,10,2,8,6};//静态分配内存int len;printf("请输入你要分配的数组长度:len = ");scanf("%d",&len);// 在堆区开辟-一个5*4的空间,并将地址(void*) ,转成(int*),赋给pArrint *pArr = (int *)malloc(sizeof(int) * len);/**pArr = 4;//类似于a[0] = 4;pArr[1] = 10; //类似于a[1] = 10;printf("%d %d\n",*pArr,pArr[1]);//4 10*///pArr完全可以当做普通数组来使用for(int i = 0;i < len;i++){scanf("%d",&pArr[i]);}for(i = 0;i < len;i++){printf("%d\n",*(pArr + i));}free(pArr);//把pArr所代表的动态分配的20个字节内存释放return 0;}

内存结构图:

3.3 动态分配内存注意事项

避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵守原则:(谁分配,谁释放),否则可能出现内存泄漏总是确保释放已分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存在释放内存之前, 确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏。在循环中分配内存时,要特别小心

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。