12.C语言提高(二)

简介: 1.二维数组的本质二维数组的本质是一个数组指针,放宽来说多维数组的本质也是一个数组指针int arr[i][j](arr+i)代表第i行的地址 二级指针*(arr+i)代表第i行首元素的地址 一级指针 ,第i行地址和第i行首元素地址虽然...
1.二维数组的本质

二维数组的本质是一个数组指针,放宽来说多维数组的本质也是一个数组指针

int arr[i][j]

(arr+i)代表第i行的地址 二级指针
*(arr+i)代表第i行首元素的地址 一级指针 ,第i行地址和第i行首元素地址虽然是相同的
但是指针的表示形式不同
*(arr+i)+j == arr[i][j] 代表第i行第j列元素的地址
((arr+i)+j) 代表第i行第j列元素的值

img_a18437cb1d3c5c522f8e12310c53e09b.png
二维数组与指针.png
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
    int arr[3][5];
    int i = 0;
    int j = 0;
    int temp = 0;
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            arr[i][j] = temp++;
        }
    }
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            printf("%d ", arr[i][j]);
        } 
    }
    printf("\n");
    //观察这两行打印,看地址有什么特点,arr+1相对于arr移动了
    //20,这刚好等于二维数组一行的字节数(5*4(一个int4个字节))
    printf("arr %d,arr+1 %d\n",arr,arr+1);
    //&arr+1相对于&arr移动了60个字节,刚好等于一个二维数组
    //的总字节数(5*4*3)
    printf("&arr %d,&arr+1 %d\n",&arr,&arr+1);

    //定义一个指向数组的指针变量,指向以int[5]为元素的二维数组
    int(*p)[5];

    //指向arr
    p = arr;

    //打印,你会发现p[i][j]和arr[i][j]打印结果是一样的,其实二维数组的本质
    //就是一个数组指针,arr和p是等价的
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            printf("%d ", p[i][j]);
        }
    }

    //(arr+i)代表第i行的地址   二级指针
    //*(arr+i)代表第i行首元素的地址  一级指针  ,第i行地址和第i行首元素地址虽然是相同的
    //但是指针的表示形式不同
    //*(arr+i)+j   == arr[i][j]  代表第i行第j列元素的地址
    //*(*(arr+i)+j) 代表第i行第j列元素的值
    system("pause");
}
2.数组做函数参数的退化问题

1.C语言中只会以机械式的值拷贝方式传递参数(实参把值传递给形参)。

下边两个方法参数不同,但是打印结果是相同的,sizeof(数组)得到的值都是4,为什么?一个char 一个int,这是因为一维数组做函数参数会退化为一个一维指针,所以结果是一样的,解释一下C语言中只会以机械式的值拷贝方式传递参数这句话,当一个数组作为地址传递到一个方法中,实参向形参传递值的时候,并没有在内存中重新创建一个数组,将数组元素一个一个拷贝进去,而是将实参数组的地址传递给了形参,形参也指向了那个数组,这就是值传递,地址值的传递

原因:c语言的高效性体现在这里,这样做更高效

int fun(char a[20],size_t b){
    printf("%d  %d",b,sizeof(a));
}

int fun(int a[20],size_t b){
    printf("%d  %d",b,sizeof(a));
}

2.二维数组做参数

//一维数组做参数退化过程
void fun(int a[5])   ---> void fun(inta[]) ---> void fun(int *a)
//二维数组做参数退化过程
void fun(int a[3][5]) ---> void fun(int a[][5]) --->void fun(int (*a)[5])
数组做函数参数的等效性

一维数组 char a[30] 等价的指针参数 char*a
指针数组 char *a[30] 等价的指针参数 char *a
二维数组 char a[20][30] 等价的指针参数 char(
a)[30]

4.三种判断字符串到达结尾的方式

'\0' 数字0 NULL都可以作为判断字符串结尾的方式,原因在于stdlib.h中有这样的定义


img_241ea35753966a042ae2a15081b39d5e.png
NULL定义.png
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


void main() {
    char *a[5] = {
        "helloe",
        "world",
        "\0"
    };

    char *b[5] = {
        "today",
        "yesterday",
        0
    };

    char *c[5] = {
        "good",
        "bad",
        NULL
    };

    for (int i = 0; a[i] != NULL;i++) {
        printf("%s", a[i]);
    }

    printf("\n");
    for (int i = 0; b[i] != NULL; i++) {
        printf("%s", b[i]);
    }
    printf("\n");
    for (int i = 0; c[i] != NULL; i++) {
        printf("%s", c[i]);
    }
    system("pause");
}

5.结构体做形参和结构体指针做形参

看下边的函数,为什么通过结构体赋值失败,而通过结构体指针就成功了,我来画一下内存结构示意图,就明白了

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct Worker {
    char *name;
    int age;
    int id;
} Worker;


void copy(Worker from,Worker to){
    to = from;
}

void copy2(Worker *from, Worker *to) {
    *to = *from;
}
void main() {
    Worker w1 = {"zhangsan",12,1};

    Worker w2;
    Worker w3;
    //通过等号,会将结构体中的值拷贝到w2中
    w2 = w1;
    printf("w2.name=%s", w2.name);

    //通过函数进行赋值
    //copy(w1, w3);
    //运行直接报错。使用了未初始化的局部变量w3,但是在copy函数中
    //我们已经赋值了
    //printf("w3.name=%s", w3.name);

    //换一种方式,通过传递结构体指针,打印成功
    copy2(&w1,&w3);
    system("pause");
}

针对第一个copy函数
结构体通过等号赋值,做的是内存中值的拷贝,不是地址,这一点要和数组做形参区别开。因为是值拷贝,所以w2赋值给to之后,二者在除了值相同,是没有联系的,to的改变不会影响到w2,所以打印w2失败


img_bf76af9bd9c642ce20e8ee98ea981c51.png
结构体变量做形参.png

针对第二个copy2函数


img_1a04a1143f97913bc4a7e25818643b37.png
结构体指针做形参.png
6.结构体做参数
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct Worker {
    char *name;
    int age;
    int id;
} Worker;

void printWorker(Worker *worker, int num) {
    for (int i = 0; i < num; i++) {
        //printf("age:%d\n", (*(worker+i)).age);
        //printf("age:%d\n", (worker + i)->age);
        printf("age:%d\n", worker[i].age);
    }
}

void sortWorker(Worker *worker, int num) {
    int i, j;
    Worker tmp;
    for (i = 0; i < num; i++) {
        for (j = i + 1; j < num; j++) {
            /*if ((worker + i)->age > (worker + j)->age) {
                tmp = *(worker + i);
                *(worker + i) = *(worker + j);
                *(worker + j) = tmp;
            }*/
            if (worker[i].age >worker[j].age) {
                tmp = worker[i];
                worker[i] = worker[j];
                worker[j] = tmp;
            }
        }
    }
}

void main() {
    int i = 0, num = 3;
    Worker Array[3];
    for (i = 0; i < num; i++) {
        printf("请输入age:");
        scanf("%d", &(Array[i].age));
    }
    printf("\n排序前\n:");
    printWorker(Array, num);
    sortWorker(Array, num);
    printf("\n排序后\n:");
    printWorker(Array, num);
    system("pause");
}


7.结构体二级指针做参数

注意三个createWorker方法

#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct Worker {
    char *name;
    int age;
    int id;
} Worker;

Worker *createWorker1(int num) {
    Worker *tmp = (Worker*)malloc(sizeof(Worker)*num);
    if (tmp == NULL) {
        return NULL;
    }
    return tmp;
}

int createWorker2(Worker **worker, int num) {
    Worker *tmp = NULL;
    tmp = (Worker*)malloc(sizeof(Worker)*num);
    if (tmp == NULL) {
        return -1;
    }
    //这样做的效果是让二级指针Worker **worker指向了tmp,并没有改变
    //Worker *pWorker这个指针的指向,可以分析一下,但函数执行时,定义了一个
    //二级指针worker指向了Worker *pWorker,worker = &tmp这样操作改变的只是
    //临时变量worker,对pWorker没有影响,所以后边操作pWorker导致崩溃
    worker = &tmp;
    return 0;
}

int createWorker3(Worker **worker, int num) {
    Worker *tmp = NULL;
    tmp = (Worker*)malloc(sizeof(Worker)*num);
    if (tmp == NULL) {
        return -1;
    }
    *worker = tmp;
    return 0;
}

void printWorker(Worker *worker, int num) {
    for (int i = 0; i < num; i++) {
        //printf("age:%d\n", (*(worker+i)).age);
        //printf("age:%d\n", (worker + i)->age);
        printf("age:%d\n", worker[i].age);
    }
}

void sortWorker(Worker *worker, int num) {
    int i, j;
    Worker tmp;
    for (i = 0; i < num; i++) {
        for (j = i + 1; j < num; j++) {
            /*if ((worker + i)->age > (worker + j)->age) {
            tmp = *(worker + i);
            *(worker + i) = *(worker + j);
            *(worker + j) = tmp;
            }*/
            if (worker[i].age >worker[j].age) {
                tmp = worker[i];
                worker[i] = worker[j];
                worker[j] = tmp;
            }
        }
    }
}
void main() {
    int i = 0, num = 3;
    Worker *pWorker = NULL;
    //在堆内存中申请空间返回内存地址
    //pWorker = createWorker1(num);
    //createWorker2(&pWorker, num);
    createWorker3(&pWorker, num);
    for (i = 0; i < num; i++) {
        printf("请输入age:");
        scanf("%d", &(pWorker[i].age));
    }
    printf("\n排序前\n:");
    printWorker(pWorker, num);
    sortWorker(pWorker, num);
    printf("\n排序后\n:");
    printWorker(pWorker, num);
    system("pause");
}


8.练习,将前两个内存中的元素拷贝到一个新的二级指针指向的空间,并排序
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/*
把第一种内存模型第二种内存模型结果copy到第三种内存模型中,并排序打印
*/
int sort(char **myp1, int num1, char(*myp2)[30], int num2, char ***myp3, int *num3) {
    char **p3 = NULL;
    int i = 0, j = 0, k = 0;
    int tmplen = 0;
    char *tmpP = NULL;
    //p3指向的内存空间用于接收指向两个数组的值,所以他的大小应该是前两个空间的和
    p3 = (char **)malloc((num1 + num2) * sizeof(char*));
    if (p3 == NULL) {
        return -1;
    }

    //将第一个数组的值拷贝到申请的空间中
    for (i = 0; i < num1; i++) {
        //得到myp1指向的第i个元素的长度(+1是结束符空间)
        tmplen = strlen(myp1[i]) + 1;
        //开辟等长的空间
        p3[i] = (char *)malloc(tmplen * sizeof(char));
        //拷贝到开辟的空间中
        strcpy(p3[i], myp1[i]);
    }
    printf("i = %d\n", i);
    //同样道理拷贝myp2
    for (j = 0; j < num2; j++,i++){
        tmplen = strlen(myp2[j]) + 1;
        p3[i] = (char *)malloc(tmplen * sizeof(char));
        if (p3[i] == NULL) {
            return -3;
        }
        strcpy(p3[i], myp2[j]);
    }

    //排序
    tmplen = num1 + num2;
    //*num3 = num1+num2;
    for (i = 0; i < tmplen; i++) {
        for (j = i + 1; j < tmplen; j++) {
            if (strcmp(p3[i], p3[j]) > 0) {
                tmpP = p3[i];
                p3[i] = p3[j];
                p3[j] = tmpP;
            }
        }
    }

    //间接赋值
    *num3 = tmplen;
    *myp3 = p3;
    return 0;
}

void sortFree1(char **p, int len) {
    int i = 0;
    if (p == NULL) {
        return;
    }
    for (i = 0; i < len; i++) {
        free(p[i]);
    }
    free(p);
}

/*
    把二级指针指向的二维内存释放掉,同时间接修改了实参的值
*/
void sortFree2(char ***myp,int len) {
    int i = 0;
    char **p = NULL;
    if (myp == NULL) {
        return;
    }
    //还原成二级指针
    p = *myp;
    if (p == NULL) {
        return;
    }
    for (i = 0; i < len; i++) {
        free(p[i]);
    }
    //间接赋值是指针存在的最大意义
    *myp = NULL;
}
void main() {
    int ret = 0;
    char *p1[] = {"aaaaaaa","cccccc","bbbbbb"};
    char buf2[10][30] = {"11111","333333","222222"};
    char **p3 = NULL;

    int len1, len2, len3=0, i = 0;
    len2 = 3;
    len1 = sizeof(p1) / sizeof(*p1);
    printf("len1=%d", len1);
    ret = sort(p1, len1, buf2, len2, &p3, &len3);
    if (ret != 0) {
        printf("func sort err:%d \n",ret);
        return ret;
    }
    for (i = 0; i < len3; i++) {
        printf("%s\n", p3[i]);
    }

    system("pause");
}


相关文章
|
存储 编译器 C语言
13.C语言提高(三)
1.结构体中指针变量的深拷贝和浅拷贝 疑问点记录: char *p = (char *)malloc(100); strcpy(p,"renzhenming"); 这两行代码定义了一个字符指针p,他指向堆内存中一段空间,这一段空间存储了一个字符串re...
1051 0
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
20 2
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
66 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
52 1
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
63 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
68 15
|
2月前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
69 9
|
2月前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
64 6

热门文章

最新文章