您的位置:首页 > 编程语言 > C语言/C++

14:枚举enum、联合union、动态内存分配malloc

2017-01-14 20:20 211 查看
【枚举】

枚举也可以用来创建新的数据类型

枚举类型存储区就是"整数类型存储区",枚举类型存储区在使用的时候存放"有限"的几个整数

声明枚举类型的时候需要提供一组名称,计算机为每个名称分配一个对应整数,只有这些整数可以记录在这种枚举类型的存储区里

不同枚举类型存储区里可以记录的整数范围不同

声明枚举类型的时候应该使用 enum 关键字,也"不会分配内存"

    enum {CHUN, XIA, QIU, DONG};

计算机把从 0 开始的连续的非负整数分配给枚举类型中的所有名称,一一对应

可以在声明枚举类型时指定某个名称对应的整数,后面的名称对应的整数都会随之变化

<示例>

#include <stdio.h>

enum /*season*/ {CHUN, XIA, QIU = 5, DONG}; //名称可省略,使用时不需要

int main(){

    printf("CHUN是%d\n", CHUN);//0

    printf("XIA是%d\n", XIA);//1

    printf("QIU是%d\n", QIU);//5

    printf("DONG是%d\n", DONG);//6

    return 0;

}

【联合】

联合也可以用来创建新的数据类型

声明联合的时候需要使用 union 关键字(使用 typedef 关键字和结构体一样)

联合的成员变量对应的存储区互相重叠

联合存储区可以当作多种不同类型的存储区使用,每个成员变量代表一种对应的类型

联合的所有成员变量存储区的开始地址一样,联合中所有成员分配的是同一块内存

联合存储区的大小是"最大存储区的大小"

<示例>

#include <stdio.h>

typedef union {

    int num;    //最大存储区,占4个字节

    float fnum;

    char buf[2];

} Tmp;

int main(){

    Tmp p = {0};

    printf("&(p.num)是%p\n", &(p.num)); //0xbfc5ac3c

    printf("&(p.fnum)是%p\n", &(p.fnum)); //0xbfc5ac3c

    printf("sizeof(Tmp)是%d\n", sizeof(Tmp)); //4

    return 0;

}

【高级指针】

记录普通变量地址的指针叫一级指针

"记录一级指针的地址"的指针叫做二级指针

声明二级指针的时候需要写"**" //两个星号

二级指针前使用 ** "两个星号"可以表示捆绑的普通变量的存储区

二级指针前使用 * "一个星号"可以表示捆绑的一级指针的存储区

<示例>

#include <stdio.h>

int main(){

    int num = 0;

    int *p_num = #

    int **pp_num = &p_num; //二级指针声明

    **pp_num = 10; //两个星号的二级指针赋值,就是修改num的值

    printf("num = %d\n", num);

    return 0;

}

二级指针"可以用来代表指针数组",但是不可以代表二维数组

<示例>

#include <stdio.h>

int main(int argc, char **argv){  //打印命令行参数二级指针用法

    int num = 0;

    for(num = 0; num < argc; num++){

        printf("%s\n", *(argv + num));

    }

    return 0;

}

无类型指针有可能实际代表的是二级指针

"二级指针通常作为形式参数使用",它可以从调用函数向被调用函数传递一个一级指针存储区

【练习】

编写函数从两个圆里找出面积比较大的圆,并把它传递给调用函数

函数不能使用返回值

<思想>

结构体,一级结构体指针记录面积较大的圆,二级指针存储比较大的圆的结构体变量地址

/*代码*/

#include <stdio.h>

typedef struct {

    int row, col;

} Pt; 

typedef struct {

    Pt center;

    int r;

} Circle;

void larger(const Circle *p_cl1,const Circle *p_cl2,Circle **pp_cl) { //两个const常量结构体指针地址,一个结构体二级指针

    *pp_cl = (Circle *)(p_cl1->r > p_cl2->r ? p_cl1 : p_cl2); //因为有const常量,所以做结构体强制类型转换,注意后面三目表达式整体括号

}

int main() {

    Circle cl1 = {0}, cl2 = {0}, cl = {0}, *p_cl = &cl;

    printf("请输入第1个圆位置(圆心行,列,半径):"); 

    scanf("%d%d%d", &cl1.center.row, &cl1.center.col, &cl1.r);

    printf("请输入第2个圆位置(圆心行,列,半径):");

    scanf("%d%d%d", &cl2.center.row, &cl2.center.col, &cl2.r);

    larger(&cl1, &cl2, &p_cl);

    printf("面积大的圆是((%d, %d), %d)\n", p_cl->center.row, p_cl->center.col, p_cl->r); //如果->写成.访问成员会导致"在非结构或联合中请求成员"的错误

    return 0;

}

【函数指针】

C语言里函数也有地址

"函数名称可以表示函数的地址"

函数指针可以"用来记录函数的地址"

函数指针也需要先声明然后才能使用,函数指针声明语句可以根据函数声明语句变化得到

函数指针也有"格式区分",不同格式的函数指针适合与不同的函数捆绑

函数指针"可以用来调用函数"(跨文件调用)

<示例>

#include <stdio.h>

int add(int num, int num1) {

    return num + num1;

}

int main() {

    int (*p_func)(int, int); //函数指针声明,原函数声明写过来修改

    p_func = add; //函数的地址赋值给函数指针,实现捆绑

    printf("add是%p\n", add);

    int num = p_func(4, 7); //函数指针调用函数格式与函数调用一样

    printf("结果是:%d\n", num);

    return 0;

}

【回调函数】

函数指针的应用:回调函数

可以"作为实际参数使用的函数"叫回调函数

<示例>

#include <stdio.h>

/*

void print(int *p_num, int size) {

    int i = 0;

    for(i = 0; i < size; i++){

        printf("%d ", *(p_num + i)); //循环变量做下标

    }

    printf("\n");

}

*/

//回调函数:以下两个函数可以在一起合作,也可以不在一起,由调用函数决定

void print_cb(int *p_num) {

    printf("%d ", *p_num);

}

void for_each(int *p_num, int size, void (*p_func)(int *)) {

    int num = 0;

    for(num = 0; num < size; num++) {

        p_func(p_num + num);

    }       

}

int main() {

    int arr[] = {1, 2, 3, 4, 5};

    //print(arr, 5);

    for_each(arr, 5, print_cb);//函数名是地址,作为参数传递过去

    printf("\n");

    return 0;

}

【练习】

利用for_each函数把数组里每个存储区内容编程相反数

(实际工作中for_each这个通用的回调函数是已经写好的,需要写的绝大部分都是专用函数,如下例子中的print_cb,neg_cb)

<思想>

回调函数

/*代码*/

#include <stdio.h>

void print_cb(int *p_num) {

    printf("%d ", *p_num);

}

void neg_cb(int *p_num) {

    *p_num = 0 - *p_num;

}

void for_each(int *p_num, int size, void (*p_func)(int *)) {

    int num = 0;

    for(num = 0; num < size; num++) {

        p_func(p_num + num);

    }   

}

int main() {

    int arr[] = {1, 2, 3, 4, 5}; 

    for_each(arr, 5, neg_cb); //调用回调函数neg_cb

    for_each(arr, 5, print_cb); //调用回调函数print_cb

    printf("\n");

    return 0;

}

【动态内存分配】

可以在程序运行的时候临时决定需要分配多少存储区,这种分配方法叫"动态分配内存"

为了使用动态分配内存,需要用到一组"标准函数"

为了使用这些标准函数需要包含"<stdlib.h>"头文件(切记,否则编译会包警告隐式声明问题)

"malloc()" 函数可以动态分配一组连续的字节

这个函数可以动态分配一组连续的字节,这个函数需要一个整数类型参数表示希望分配的字节个数

它的【返回值】就是分配好的"第一个字节的地址"(以此可以找到每个字节)

"动态分配内存会有可能失败的",如果分配失败就返回空地址 NULL(nil)

根据此返回结果判断动态分配内存是否成功

这个函数的"返回值记录在无类型指针的存储区"里,需要首先强制类型转换为有类型指针,然后才能使用

<示例>

#include <stdio.h>

#include <stdlib.h>

int main() {

    int *p_num = (int *)malloc(5 * sizeof(int)); //动态分配5个整数类型存,必须强制类型转换为对应的数据类型

储区

    if(p_num) {

        //if区域内的内容使用动态分配内存

        printf("分配成功!\n"); //仅作演示

    }   

    return 0;

}

动态分配内存"不使用之后必须释放"

"free()" 标准函数可以用来释放动态分配内存

 free() 函数需要一个参数,这个参数代表动态分配的第一个字节的地址

 free() 函数会把一次分配的所有内存都释放掉

如果使用指针作为参数调用free()函数,则"函数调用后指针成为野指针",必须将指针"恢复成空指针"

<示例>/*此为动态分配内存的代码框架(固定写法)*/

#include <stdio.h>

#include <stdlib.h>

int main() {

    int *p_num = (int *)malloc(5 * sizeof(int)); //5可用整形变量代替

    //5表示分配该类型存储区个数,int是4个字节,此次分配共20个字节空间

    if(p_num) {

        printf("分配成功!\n"); //仅作演示,代表内容

        free(p_num); //释放使用过的动态内存

        p_num = NULL; //野指针恢复成空指针

    }   

    return 0;

}

/*动态分配内存代码框架(另外一种固定写法)*/

#include <stdio.h>

#include <stdlib.h>

int main() {

    int *p_num = (int *)malloc(5 * sizeof(int));

    if(!p_num) { //如果记录的地址为空,证明没有动态内存分配失败

        return 0; //结束整个当前程序

    }   

    /* 使用动态分配内存的代码区域 */

    free(p_num);

    p_num = NULL;

    return 0;

}

<实际示例>

#include <stdio.h>

#include <stdlib.h>

int main() {

    int num = 0;

    int *p_num = (int *)malloc(5 * sizeof(int));//动态分配内存及大小

    if(p_num) {

        printf("分配成功!\n"); //分配成功!

        for(num = 0; num < 5; num++) {

            *(p_num + num) = num;

        }   

        for(num = 0; num < 5; num++) {

            printf("%d ", *(p_num + num)); //0 1 2 3 4

        }   

        printf("\n");

        free(p_num); //释放动态分配的内存

        p_num = NULL; //野指针置空

    }   

    return 0;

}

动态分配内存一个很重要的使用场景:

"调用函数可以使用被调用函数动态分配的内存"

(之前是把被掉用函数的存储区静态化 static)

<实际示例>/*生成7组(1-36)之间的数的彩票(动态分配内存+回调函数)*/

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

int *create(int size) { //彩票不重复

    int *p_num = (int *)malloc(size * sizeof(int));

    int num = 0, i = 0;

    if(p_num) { //必须加分支判断动态内存是否分支成功

        for(num = 0; num < size; num++) {

            *(p_num + num) = rand() % 36 + 1;

            for(i = 0;i < num; i++) {

                if(*(p_num + num) == *(p_num + i)) {

                    i--;

                }

            }

        }

    }

    return p_num;

}

void print(int *p_num) { //回调函数

    printf("%d ", *p_num);

}

void for_each(int *p_num, int size, void (*p_func)(int *)) {

    int num = 0;

    for(num = 0; num < size; num++) {

        p_func(p_num + num);

    }

}

int main() {

    srand(time(0));

    int *p_num = create(SIZE); //编译时-D指定SIZE宏的值

    if(p_num) {

        for_each(p_num, SIZE, print);

        printf("\n");

        free(p_num); //分支内释放内存

        p_num = NULL;

    }

    return 0;

}

【练习】

编写函数从键盘得到一个点的位置,把这个点的位置传递给调用函数

要求用动态分配内存记录点的位置

<思想>

动态内存分配代码框架

/*代码*/ 

#include <stdio.h>

#include <stdlib.h> //警告:隐式声明问题,切记不要忘记头文件

typedef struct {

    int row, col;

} Pt; 

Pt *dian(void) {

    Pt *p_pt = (Pt *)malloc(sizeof(Pt));

    if(p_pt) { //分支判断是框架一部分

        printf("请输入点的坐标行,列:");

        scanf("%d%d", &(p_pt->row), &(p_pt->col));

    }   

    return p_pt;

}

int main() {

    Pt *p_d = dian();

    if(p_d) {

        printf("坐标为:(%d, %d)\n", p_d->row, p_d->col);

        free(p_d);

        p_d = NULL;

    }   

    return 0;

}

【练习】

编写函数计算两个点的中间位置,并把这个位置传递给调用函数

用动态分配内存记录中间点的位置

<思想>

结构体/函数/动态分配内存框架

/*代码*/

#include <stdio.h>

#include <stdlib.h>

typedef struct {

    int row, col;

} Pt; 

Pt *mid(const Pt *p_d1, const Pt *p_d2) { //不会修改结构体指针的地址,所有要都加 const

    Pt *p_mid = (Pt *)malloc(sizeof(Pt));

    if(p_mid) {

        p_mid->row = (p_d1->row + p_d2->row)/2;

        p_mid->col = (p_d1->col + p_d2->col)/2;

    }

    return p_mid;

}

int main() {

    Pt d1, d2; 

    printf("请输入第1个点的坐标行,列:");

    scanf("%d%d", &(d1.row), &(d1.col));

    printf("请输入第2个点的坐标行,列:");

    scanf("%d%d", &(d2.row), &(d2.col));

    Pt *p_mid = mid(&d1, &d2);

    if(p_mid) {

        printf("中间点坐标为:(%d,%d)\n", p_mid->row, p_mid->col);

        free(p_mid);

        p_mid = NULL;

    }   

    return 0;
}

【动态分配内存】

"calloc" 函数也可以动态分配内存

这个函数可以把分配的所有内存内容都设置成 0 (malloc内容是随机的未初始化)

这个函数也需要包含"<stdlib.h>"的头文件

这个函数需要两个参数:第 1 个参数表示希望分配的"存储区个数",第 2 个参数表示"单个存储区的大小"

这个函数的【返回值】是分配好的第一个存储区的地址(同malloc函数)

这个函数也可能分配失败,如果失败则返回值是NULL(空地址nil)

"realloc" 函数可以调整一段动态分配内存的大小(尽量少使用这个函数)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  标准C语言