13.C语言提高(三)

简介: 1.结构体中指针变量的深拷贝和浅拷贝疑问点记录:char *p = (char *)malloc(100);strcpy(p,"renzhenming");这两行代码定义了一个字符指针p,他指向堆内存中一段空间,这一段空间存储了一个字符串re...
1.结构体中指针变量的深拷贝和浅拷贝

疑问点记录:

char *p = (char *)malloc(100);
strcpy(p,"renzhenming");

这两行代码定义了一个字符指针p,他指向堆内存中一段空间,这一段空间存储了一个字符串renzhenming,那么既然这个指针指向了这块内存,那么我通过取指针元素符号*应该就可以得到这块内存中的元素了,也就是说按照猜想我打印*p

printf("%s",*p)

应该可以得到值renzhenming这个字符串才对,事实上程序却崩溃了,正确的写法是这样

printf("%s",p);

才能得到我们想要的结果:打印出renzhenming这个字符串

为什么?

我想很多初学者可能会有这种疑问,其实深入剖析一下你就会明白了,字符指针char *p他是指向字符的,看下边的代码

char a = 'c';
char *p=&a;
printf("%c",\*p);

这样是可以打印出c这个字符,为什么char *p是字符指针却可以指向一个字符串?c语言是使用字符数组来表示和存储字符串的,所以可以把字符串和字符数组等价起来,在数组中,数组名表示的就是数组首元素的地址,那么对字符数组来说,数组名表示的就是字符数组中首个字符的地址,因为字符指针可以保存字符的地址,那么就可以使用字符指针来保存字符数组的首个字符的地址,也就是字符数组名
也就是说
char temp[100] ="renzhenming"

char p = "renzhenming"
表示是同一个意思,那么字符指针就是可以指向一个字符数组,也就是可以指向一个字符串。数组名temp和p都是表示的字符串renzhenming的第一个字符 r 的地址。
那么此时,你就会明白,为什么
p不能把整个字符串打印出来了,因为*p表示的是第一个字符r,你通过%s去接收一个字符,会导致程序崩溃,如果你这样打印

printf("%c",*p)

就能打印出r这个字符了。

为什么通过%s去接收一个字符会导致程序崩溃?

因为%s格式字符串会从给定的内存空间开始向后逐个输出字符,直到遇到\0结束。格式字符串为%s时,后面的参数应该为一个内存地址(指针),如果给出的是一个字符变量,那么会将字符变量中的值认作地址,例如字符变量ch中保存的是字符'a',那么printf会将'a'对应的ASCII码97作为内存地址,试图转到该位置读出数据;而该位置的内存空间属于操作系统,为保护段空间不可访问,因而程序崩溃

那么为什么printf("%s",p)可以打印出整个字符串?我们知道字符串是以\0为结束标志的,p表示字符串第一个字符的地址,当你通过%s去打印的时候,他会自动向后移动指针,直到找到\0结束符为止,将从p这个为止开始到结束符为止的字符全部打印出来,就是最终的结果了。


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

typedef struct Worker {
    int age;
    char name[100];
    char *alias;
} Worker;

/*
    浅拷贝
*/
void lightCopy(Worker *temp1, Worker *temp2) {
    //*temp1 = *temp2;
    memcpy(temp1, temp2, sizeof(Worker));
    //上边两种拷贝方式是相同的

    printf("sizeof(temp2)=%d\n", sizeof(Worker));
}

void deepCopy(Worker *temp1, Worker *temp2) {
    memcpy(temp1, temp2, sizeof(Worker));
    temp1->alias = (char *)malloc(100);
    strcpy(temp1->alias, temp2->alias);
}
void main() {
    Worker w1;
    Worker w2;

    //给w1赋值
    strcpy(w1.name, "renzhenming");
    w1.alias = (char *)malloc(100);
    strcpy(w1.alias, "ren");

    //lightCopy(&w2, &w1);
    deepCopy(&w2, &w1);
    printf("w2.name = %s,w2.alias = %s\n", w2.name, w2.alias);

    //释放申请的内存
    if (w1.alias != NULL) {
        free(w1.alias);
        w1.alias = NULL;
    }

    if (w2.alias != NULL) {
        free(w2.alias);
        w2.alias = NULL;
    }

    system("pause");
}

简单来说,深拷贝和浅拷贝针对的是指针,浅拷贝拷贝的是指针的地址值,而深拷贝是拷贝的指针指向的内存。
编译器针对结构体中指向一块堆内存空间的指针进行等号操作,既*p1 = *p2,操作的结果是将p2的地址拷贝到了p1上此时p2和p1指向同一块内存空间。那么此时,由于执行浅拷贝,当free空间的时候,先判断if (w1.alias != NULL)满足,则将alias指向的空间释放,再判断if (w2.alias != NULL) 仍然满足,因为w2的alias指针和w1的alias指针并不是同一个,但是所指向的空间是同一个,这就导致free空间的时候由于已经被free,再次释放发生崩溃。深拷贝则可以避免这个问题。


img_ef9be636b54046d8bb4fea1e5f6b66d8.png
浅拷贝内存示意图.png

img_5b0def8542241cb1da180f56ab843f2e.jpe
深拷贝内存示意图.jpg
相关文章
|
人工智能 BI C语言
12.C语言提高(二)
1.二维数组的本质 二维数组的本质是一个数组指针,放宽来说多维数组的本质也是一个数组指针 int arr[i][j] (arr+i)代表第i行的地址 二级指针 *(arr+i)代表第i行首元素的地址 一级指针 ,第i行地址和第i行首元素地址虽然...
884 0
|
9天前
|
程序员 C语言
C语言库函数 — 内存函数(含模拟实现内存函数)
C语言库函数 — 内存函数(含模拟实现内存函数)
18 0
|
20天前
|
编译器 C语言 C++
【C语言】memset()函数(内存块初始化函数)
【C语言】memset()函数(内存块初始化函数)
24 0
|
20天前
|
编译器 C语言 C++
【C语言】memcpy()函数(内存块拷贝函数)
【C语言】memcpy()函数(内存块拷贝函数)
38 0
|
21天前
|
C语言 C++
【C语言】rand()函数(如何生成指定范围随机数)
【C语言】rand()函数(如何生成指定范围随机数)
16 0
|
29天前
|
C语言
在C语言中数组作为函数参数的应用与示例
在C语言中数组作为函数参数的应用与示例
15 0
|
29天前
|
算法 C语言
在C语言中函数的递归调用及应用示例
在C语言中函数的递归调用及应用示例
15 1
|
29天前
|
C语言
在C语言中多维数组名作为函数参数的应用与示例
在C语言中多维数组名作为函数参数的应用与示例
12 0
|
9天前
|
程序员 C语言 开发者
C语言库函数 — 字符串函数(含模拟实现字符串函数)
C语言库函数 — 字符串函数(含模拟实现字符串函数)
35 0
|
16天前
|
存储 C语言
【我爱C语言】详解字符函数isdigit和字符串转换函数(atoi和snprintf实现互相转换字符串)&&三种strlen模拟实现1
【我爱C语言】详解字符函数isdigit和字符串转换函数(atoi和snprintf实现互相转换字符串)&&三种strlen模拟实现