首页
前端面试题
前端报错总结
电子书
更多
插件下载
Search
1
JavaScript基础(二)操作符 流程控制
42 阅读
2
HTML基础
20 阅读
3
Vue基础
17 阅读
4
wctype.h
14 阅读
5
Vue2(知识点)
13 阅读
默认分类
HTML CSS
HTML基础
CSS
HTML5 CSS3
javaScript
javaScript基础
javaScript高级
Web APIs
jQuery
js小总结
WEB开发布局
Vue
PS切图
数据可视化
Git使用
Uniapp
c语言入门
标准库
嵌入式
登录
Search
liuxiaobai
累计撰写
108
篇文章
累计收到
12
条评论
首页
栏目
默认分类
HTML CSS
HTML基础
CSS
HTML5 CSS3
javaScript
javaScript基础
javaScript高级
Web APIs
jQuery
js小总结
WEB开发布局
Vue
PS切图
数据可视化
Git使用
Uniapp
c语言入门
标准库
嵌入式
页面
前端面试题
前端报错总结
电子书
插件下载
搜索到
104
篇与
的结果
2023-09-20
typedef 命令(类型定义)
typedef 命令简介typedef命令用来为某个类型起别名。typedef type name;上面代码中,type代表类型名,name代表别名。typedef unsigned char BYTE; BYTE c = 'z';上面示例中,typedef命令为类型unsign char起别名BYTE,然后就可以使用BYTE声明变量。typedef 可以一次指定多个别名。typedef int antelope, bagel, mushroom;上面示例中,一次性为int类型起了三个别名。typedef 可以为指针起别名。typedef int* intptr; int a = 10; intptr x = &a;上面示例中,intptr是int*的别名。不过,使用的时候要小心,这样不容易看出来,变量x是一个指针类型。typedef 也可以用来为数组类型起别名。typedef int five_ints[5]; five_ints x = {11, 22, 33, 44, 55};上面示例中,five_ints是一个数组类型,包含5个整数的typedef 为函数起别名的写法如下。typedef signed char (*fp)(void);上面示例中,类型别名fp是一个指针,代表函数signed char (*)(void)。主要好处typedef为类型起别名的好处,主要有下面几点。(1)更好的代码可读性。typedef char* STRING; STRING name;上面示例为字符指针起别名为STRING,以后使用STRING声明变量时,就可以轻易辨别该变量是字符串。(2)为 struct、union、enum 等命令定义的复杂数据结构创建别名,从而便于引用。struct treenode { // ... }; typedef struct treenode* Tree;上面示例中,Tree为struct treenode*的别名。typedef 也可以与 struct 定义数据类型的命令写在一起。typedef struct animal { char* name; int leg_count, speed; } animal;上面示例中,自定义数据类型时,同时使用typedef命令,为struct animal起了一个别名animal。这种情况下,C 语言允许省略 struct 命令后面的类型名。typedef struct { char *name; int leg_count, speed; } animal;上面示例相当于为一个匿名的数据类型起了别名animal。(3)typedef 方便以后为变量改类型。typedef float app_float; app_float f1, f2, f3;上面示例中,变量f1、f2、f3的类型都是float。如果以后需要为它们改类型,只需要修改typedef语句即可。typedef long double app_float;上面命令将变量f1、f2、f3的类型都改为long double。(4)可移植性某一个值在不同计算机上的类型,可能是不一样的。int i = 100000;上面代码在32位整数的计算机没有问题,但是在16位整数的计算机就会出错。C 语言的解决办法,就是提供了类型别名,在不同计算机上会解释成不同类型,比如int32_t。int32_t i = 100000;上面示例将变量i声明成int32_t类型,保证它在不同计算机上都是32位宽度,移植代码时就不会出错。这一类的类型别名都是用 typedef 定义的。下面是类似的例子。typedef long int ptrdiff_t; typedef unsigned long int size_t; typedef int wchar_t;这些整数类型别名都放在头文件stdint.h,不同架构的计算机只需修改这个头文件即可,而无需修改代码。因此,typedef有助于提高代码的可移植性,使其能适配不同架构的计算机。(5)简化类型声明C 语言有些类型声明相当复杂,比如下面这个。char (*(*x(void))[5])(void);typedef 可以简化复杂的类型声明,使其更容易理解。首先,最外面一层起一个类型别名。typedef char (*Func)(void); Func (*x(void))[5];这个看起来还是有点复杂,就为里面一层也定义一个别名。typedef char (*Func)(void); typedef Func Arr[5]; Arr* x(void);上面代码就比较容易解读了。x是一个函数,返回一个指向 Arr 类型的指针。Arr是一个数组,有5个成员,每个成员是Func类型。Func是一个函数指针,指向一个无参数、返回字符值的函数。
2023年09月20日
3 阅读
0 评论
0 点赞
2023-09-20
struct 结构(结构体)
struct 结构简介C 语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用中并不够用。实际使用中,主要有下面两种情况,需要更灵活强大的复合类型。复杂的物体需要使用多个变量描述,这些变量都是相关的,最好有某种机制将它们联系起来。某些函数需要传入多个参数,如果一个个按照顺序传入,非常麻烦,最好能组合成一个复合结构传入。为了解决这些问题,C 语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起。这样不仅为编程提供方便,也有利于增强代码的可读性。C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。下面是struct自定义数据类型的一个例子。struct fraction { int numerator; int denominator; };上面示例定义了一个分数的数据类型struct fraction,包含两个属性numerator和denominator。注意,作为一个自定义的数据类型,它的类型名要包括struct关键字,比如上例是struct fraction,单独的fraction没有任何意义,甚至脚本还可以另外定义名为fraction的变量,虽然这样很容易造成混淆。另外,struct语句结尾的分号不能省略,否则很容易产生错误。定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。struct fraction f1; f1.numerator = 22; f1.denominator = 7;上面示例中,先声明了一个struct fraction类型的变量f1,这时编译器就会为f1分配内存,接着就可以为f1的不同属性赋值。可以看到,struct 结构的属性通过点(.)来表示,比如numerator属性要写成f1.numerator。再提醒一下,声明自定义类型的变量时,类型名前面,不要忘记加上struct关键字。也就是说,必须使用struct fraction f1声明变量,不能写成fraction f1。除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。struct car { char* name; float price; int speed; }; struct car saturn = {"Saturn SL/2", 16000.99, 175};上面示例中,变量saturn是struct car类型,大括号里面同时对它的三个属性赋值。如果大括号里面的值的数量,少于属性的数量,那么缺失的属性自动初始化为0。注意,大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。否则,必须为每个值指定属性名。struct car saturn = {.speed=172, .name="Saturn SL/2"};上面示例中,初始化的属性少于声明时的属性,这时剩下的那些属性都会初始化为0。声明变量以后,可以修改某个属性的值。struct car saturn = {.speed=172, .name="Saturn SL/2"}; saturn.speed = 168;上面示例将speed属性的值改成168。struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。struct book { char title[500]; char author[100]; float value; } b1;上面的语句同时声明了数据类型book和该类型的变量b1。如果类型标识符book只用在这一个地方,后面不再用到,这里可以将类型名省略。struct { char title[500]; char author[100]; float value; } b1;上面示例中,struct声明了一个匿名数据类型,然后又声明了这个类型的变量b1。与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。struct { char title[500]; char author[100]; float value; } b1 = {"Harry Potter", "J. K. Rowling", 10.0}, b2 = {"Cancer Ward", "Aleksandr Solzhenitsyn", 7.85};上面示例中,在声明变量b1和b2的同时,为它们赋值。下一章介绍的typedef命令可以为 struct 结构指定一个别名,这样使用起来更简洁。typedef struct cell_phone { int cell_no; float minutes_of_charge; } phone; phone p = {5551234, 5};上面示例中,phone就是struct cell_phone的别名。指针变量也可以指向struct结构。struct book { char title[500]; char author[100]; float value; }* b1; // 或者写成两个语句 struct book { char title[500]; char author[100]; float value; }; struct book* b1;上面示例中,变量b1是一个指针,指向的数据是struct book类型的实例。struct 结构也可以作为数组成员。struct fraction numbers[1000]; numbers[0].numerator = 22; numbers[0].denominator = 7;上面示例声明了一个有1000个成员的数组numbers,每个成员都是自定义类型fraction的实例。struct 结构占用的存储空间,不是各个属性存储空间的总和,而是最大内存占用属性的存储空间的倍数,其他属性会添加空位与之对齐。这样可以提高读写效率。struct foo { int a; char* b; char c; }; printf("%d\n", sizeof(struct foo)); // 24上面示例中,struct foo有三个属性,在64位计算机上占用的存储空间分别是:int a占4个字节,指针char* b占8个字节,char c占1个字节。它们加起来,一共是13个字节(4 + 8 + 1)。但是实际上,struct foo会占用24个字节,原因是它最大的内存占用属性是char* b的8个字节,导致其他属性的存储空间也是8个字节,这样才可以对齐,导致整个struct foo就是24个字节(8 * 3)。多出来的存储空间,都采用空位填充,所以上面的`structfoo`真实的结构其实是下面这样。struct foo { int a; // 4 char pad1[4]; // 填充4字节 char *b; // 8 char c; // 1 char pad2[7]; // 填充7字节 }; printf("%d\n", sizeof(struct foo)); // 24为什么浪费这么多空间进行内存对齐呢?这是为了加快读写速度,把内存占用划分成等长的区块,就可以快速在 Struct 结构体中定位到每个属性的起始地址。由于这个特性,在有必要的情况下,定义 Struct 结构体时,可以采用存储空间递增的顺序,定义每个属性,这样就能节省一些空间。struct foo { char c; int a; char* b; }; printf("%d\n", sizeof(struct foo)); // 16上面示例中,占用空间最小的char c排在第一位,其次是int a,占用空间最大的char* b排在最后。整个strct foo的内存占用就从24字节下降到16字节。struct 的复制struct 变量可以使用赋值运算符(=),复制给另一个变量,这时会生成一个全新的副本。系统会分配一块新的内存空间,大小与原来的变量相同,把每个属性都复制过去,即原样生成了一份数据。这一点跟数组的复制不一样,务必小心。struct cat { char name[30]; short age; } a, b; strcpy(a.name, "Hula"); a.age = 3; b = a; b.name[0] = 'M'; printf("%s\n", a.name); // Hula printf("%s\n", b.name); // Mula上面示例中,变量b是变量a的副本,两个变量的值是各自独立的,修改掉b.name不影响a.name。上面这个示例是有前提的,就是 struct 结构的属性必须定义成字符数组,才能复制数据。如果稍作修改,属性定义成字符指针,结果就不一样。struct cat { char* name; short age; } a, b; a.name = "Hula"; a.age = 3; b = a;上面示例中,name属性变成了一个字符指针,这时a赋值给b,导致b.name也是同样的字符指针,指向同一个地址,也就是说两个属性共享同一个地址。因为这时,struct 结构内部保存的是一个指针,而不是上一个例子的数组,这时复制的就不是字符串本身,而是它的指针。并且,这个时候也没法修改字符串,因为字符指针指向的字符串是不能修改的。总结一下,赋值运算符(=)可以将 struct 结构每个属性的值,一模一样复制一份,拷贝给另一个 struct 变量。这一点跟数组完全不同,使用赋值运算符复制数组,不会复制数据,只会共享地址。注意,这种赋值要求两个变量是同一个类型,不同类型的 struct 变量无法互相赋值。另外,C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如==和!=)比较两个数据结构是否相等或不等。struct 指针如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本。#include <stdio.h> struct turtle { char* name; char* species; int age; }; void happy(struct turtle t) { t.age = t.age + 1; } int main() { struct turtle myTurtle = {"MyTurtle", "sea turtle", 99}; happy(myTurtle); printf("Age is %i\n", myTurtle.age); // 输出 99 return 0; }上面示例中,函数happy()传入的是一个 struct 变量myTurtle,函数内部有一个自增操作。但是,执行完happy()以后,函数外部的age属性值根本没变。原因就是函数内部得到的是 struct 变量的副本,改变副本影响不到函数外部的原始数据。通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性,就可以影响到函数外部。struct 指针传入函数的写法如下。void happy(struct turtle* t) { } happy(&myTurtle);上面代码中,t是 struct 结构的指针,调用函数时传入的是指针。struct 类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成&myTurtle。函数内部也必须使用(*t).age的写法,从指针拿到 struct 结构本身。void happy(struct turtle* t) { (*t).age = (*t).age + 1; }上面示例中,(*t).age不能写成*t.age,因为点运算符.的优先级高于*。*t.age这种写法会将t.age看成一个指针,然后取它对应的值,会出现无法预料的结果。现在,重新编译执行上面的整个示例,happy()内部对 struct 结构的操作,就会反映到函数外部。(*t).age这样的写法很麻烦。C 语言就引入了一个新的箭头运算符(->),可以从 struct 指针上直接获取属性,大大增强了代码的可读性。void happy(struct turtle* t) { t->age = t->age + 1; }总结一下,对于 struct 变量名,使用点运算符(.)获取属性;对于 struct 变量指针,使用箭头运算符(->)获取属性。以变量myStruct为例,假设ptr是它的指针,那么下面三种写法是同一回事。// ptr == &myStruct myStruct.prop == (*ptr).prop == ptr->propstruct 的嵌套struct 结构的成员可以是另一个 struct 结构。struct species { char* name; int kinds; }; struct fish { char* name; int age; struct species breed; };上面示例中,fish的属性breed是另一个 struct 结构species。赋值的时候有多种写法。// 写法一 struct fish shark = {"shark", 9, {"Selachimorpha", 500}}; // 写法二 struct species myBreed = {"Selachimorpha", 500}; struct fish shark = {"shark", 9, myBreed}; // 写法三 struct fish shark = { .name="shark", .age=9, .breed={"Selachimorpha", 500} }; // 写法四 struct fish shark = { .name="shark", .age=9, .breed.name="Selachimorpha", .breed.kinds=500 }; printf("Shark's species is %s", shark.breed.name);上面示例展示了嵌套 Struct 结构的四种赋值写法。另外,引用breed属性的内部属性,要使用两次点运算符(shark.breed.name)。下面是另一个嵌套 struct 的例子。struct name { char first[50]; char last[50]; }; struct student { struct name name; short age; char sex; } student1; strcpy(student1.name.first, "Harry"); strcpy(student1.name.last, "Potter"); // or struct name myname = {"Harry", "Potter"}; student1.name = myname;上面示例中,自定义类型student的name属性是另一个自定义类型,如果要引用后者的属性,就必须使用两个.运算符,比如student1.name.first。另外,对字符数组属性赋值,要使用strcpy()函数,不能直接赋值,因为直接改掉字符数组名的地址会报错。struct 结构内部不仅可以引用其他结构,还可以自我引用,即结构内部引用当前结构。比如,链表结构的节点就可以写成下面这样。struct node { int data; struct node* next; };上面示例中,node结构的next属性,就是指向另一个node实例的指针。下面,使用这个结构自定义一个数据链表。struct node { int data; struct node* next; }; struct node* head; // 生成一个三个节点的列表 (11)->(22)->(33) head = malloc(sizeof(struct node)); head->data = 11; head->next = malloc(sizeof(struct node)); head->next->data = 22; head->next->next = malloc(sizeof(struct node)); head->next->next->data = 33; head->next->next->next = NULL; // 遍历这个列表 for (struct node *cur = head; cur != NULL; cur = cur->next) { printf("%d\n", cur->data); }上面示例是链表结构的最简单实现,通过for循环可以对其进行遍历。位字段struct 还可以用来定义二进制位组成的数据结构,称为“位字段”(bit field),这对于操作底层的二进制数据非常有用。struct { unsigned int ab:1; unsigned int cd:1; unsigned int ef:1; unsigned int gh:1; } synth; synth.ab = 0; synth.cd = 1;上面示例中,每个属性后面的:1,表示指定这些属性只占用一个二进制位,所以这个数据结构一共是4个二进制位。注意,定义二进制位时,结构内部的各个属性只能是整数类型。实际存储的时候,C 语言会按照int类型占用的字节数,存储一个位字段结构。如果有剩余的二进制位,可以使用未命名属性,填满那些位。也可以使用宽度为0的属性,表示占满当前字节剩余的二进制位,迫使下一个属性存储在下一个字节。struct { unsigned int field1 : 1; unsigned int : 2; unsigned int field2 : 1; unsigned int : 0; unsigned int field3 : 1; } stuff;上面示例中,stuff.field1与stuff.field2之间,有一个宽度为两个二进制位的未命名属性。stuff.field3将存储在下一个字节。弹性数组成员很多时候,不能事先确定数组到底有多少个成员。如果声明数组的时候,事先给出一个很大的成员数,就会很浪费空间。C 语言提供了一个解决方法,叫做弹性数组成员(flexible array member)。如果不能事先确定数组成员的数量时,可以定义一个 struct 结构。struct vstring { int len; char chars[]; };上面示例中,struct vstring结构有两个属性。len属性用来记录数组chars的长度,chars属性是一个数组,但是没有给出成员数量。chars数组到底有多少个成员,可以在为vstring分配内存时确定。struct vstring* str = malloc(sizeof(struct vstring) + n * sizeof(char)); str->len = n;上面示例中,假定chars数组的成员数量是n,只有在运行时才能知道n到底是多少。然后,就为struct vstring分配它需要的内存:它本身占用的内存长度,再加上n个数组成员占用的内存长度。最后,len属性记录一下n是多少。这样就可以让数组chars有n个成员,不用事先确定,可以跟运行时的需要保持一致。弹性数组成员有一些专门的规则。首先,弹性成员的数组,必须是 struct 结构的最后一个属性。另外,除了弹性数组成员,struct 结构必须至少还有一个其他属性。
2023年09月20日
3 阅读
0 评论
0 点赞
2023-09-20
C 语言的内存管理
C 语言的内存管理简介C 语言的内存管理,分成两部分。一部分是系统管理的,另一部分是用户手动管理的。系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。这些变量存放的区域称为”栈“(stack),”栈“所在的内存是系统自动管理的。用户手动管理的内存,主要是程序运行的整个过程中都存在的变量(全局变量),这些变量需要用户手动从内存释放。如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为”内存泄漏“(memory leak)。这些变量所在的内存称为”堆“(heap),”堆“所在的内存是用户手动管理的。void 指针前面章节已经说过了,每一块内存都有地址,通过指针变量可以获取指定地址的内存块。指针变量必须有类型,否则编译器无法知道,如何解读内存块保存的二进制数据。但是,向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。为了满足这种需求,C 语言提供了一种不定类型的指针,叫做 void 指针。它只有内存块的地址信息,没有类型信息,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。另一方面,void 指针等同于无类型指针,可以指向任意类型的数据,但是不能解读数据。void 指针与其他所有类型指针之间是互相转换关系,任一类型的指针都可以转为 void 指针,而 void 指针也可以转为任一类型的指针。int x = 10; void* p = &x; // 整数指针转为 void 指针 int* q = p; // void 指针转为整数指针上面示例演示了,整数指针和 void 指针如何互相转换。&x是一个整数指针,p是 void 指针,赋值时&x的地址会自动解释为 void 类型。同样的,p再赋值给整数指针q时,p的地址会自动解释为整数指针。注意,由于不知道 void 指针指向什么类型的值,所以不能用*运算符取出它指向的值。char a = 'X'; void* p = &a; printf("%c\n", *p); // 报错上面示例中,p是一个 void 指针,所以这时无法用*p取出指针指向的值。void 指针的重要之处在于,很多内存相关函数的返回值就是 void 指针,只给出内存块的地址信息,所以放在最前面进行介绍。malloc()malloc()函数用于分配内存,该函数向系统要求一段内存,系统就在“堆”里面分配一段连续的内存块给它。它的原型定义在头文件stdlib.h。void* malloc(size_t size)它接受一个非负整数作为参数,表示所要分配的内存字节数,返回一个 void 指针,指向分配好的内存块。这是非常合理的,因为malloc()函数不知道,将要存储在该块内存的数据是什么类型,所以只能返回一个无类型的 void 指针。可以使用malloc()为任意类型的数据分配内存,常见的做法是先使用sizeof()函数,算出某种数据类型所需的字节长度,然后再将这个长度传给malloc()。int* p = malloc(sizeof(int)); *p = 12; printf("%d\n", *p); // 12上面示例中,先为整数类型分配一段内存,然后将整数12放入这段内存里面。这个例子其实不需要使用malloc(),因为 C 语言会自动为整数(本例是12)提供内存。有时候为了增加代码的可读性,可以对malloc()返回的指针进行一次强制类型转换。int* p = (int*) malloc(sizeof(int));上面代码将malloc()返回的 void 指针,强制转换成了整数指针。由于sizeof()的参数可以是变量,所以上面的例子也可以写成下面这样。int* p = (int*) malloc(sizeof(*p));malloc()分配内存有可能分配失败,这时返回常量NULL。Null的值为0,是一个无法读写的内存地址,可以理解成一个不指向任何地方的指针。它在包括stdlib.h等多个头文件里面都有定义,所以只要可以使用malloc(),就可以使用NULL。由于存在分配失败的可能,所以最好在使用malloc()之后检查一下,是否分配成功。int* p = malloc(sizeof(int)); if (p == NULL) { // 内存分配失败 } // or if (!p) { //... }上面示例中,通过判断返回的指针p是否为NULL,确定malloc()是否分配成功。malloc()最常用的场合,就是为数组和自定义数据结构分配内存。int* p = (int*) malloc(sizeof(int) * 10); for (int i = 0; i < 10; i++) p[i] = i * 5;上面示例中,p是一个整数指针,指向一段可以放置10个整数的内存,所以可以用作数组。malloc()用来创建数组,有一个好处,就是它可以创建动态数组,即根据成员数量的不同,而创建长度不同的数组。int* p = (int*) malloc(n * sizeof(int));上面示例中,malloc()可以根据变量n的不同,动态为数组分配不同的大小。注意,malloc()不会对所分配的内存进行初始化,里面还保存着原来的值。如果没有初始化,就使用这段内存,可能从里面读到以前的值。程序员要自己负责初始化,比如,字符串初始化可以使用strcpy()函数。char* p = malloc(4); strcpy(p, "abc");上面示例中,字符指针p指向一段4个字节的内存,strcpy()将字符串“abc”拷贝放入这段内存,完成了这段内存的初始化。free()free()用于释放malloc()函数分配的内存,将这块内存还给系统以便重新使用,否则这个内存块会一直占用到程序运行结束。该函数的原型定义在头文件stdlib.h里面。void free(void* block)上面代码中,free()的参数是malloc()返回的内存地址。下面就是用法实例。int* p = (int*) malloc(sizeof(int)); *p = 12; free(p);注意,分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用free()对该地址释放第二次。一个很常见的错误是,在函数内部分配了内存,但是函数调用结束时,没有使用free()释放内存。void gobble(double arr[], int n) { double* temp = (double*) malloc(n * sizeof(double)); // ... }上面示例中,函数gobble()内部分配了内存,但是没有写free(temp)。这会造成函数运行结束后,占用的内存块依然保留,如果多次调用gobble(),就会留下多个内存块。并且,由于指针temp已经消失了,也无法访问这些内存块,再次使用。calloc()calloc()函数的作用与malloc()相似,也是分配内存块。该函数的原型定义在头文件stdlib.h。两者的区别主要有两点:(1)calloc()接受两个参数,第一个参数是某种数据类型的值的数量,第二个是该数据类型的单位字节长度。void* calloc(size_t n, size_t size);calloc()的返回值也是一个 void 指针。分配失败时,返回 NULL。(2)calloc()会将所分配的内存全部初始化为0。malloc()不会对内存进行初始化,如果想要初始化为0,还要额外调用memset()函数。int* p = calloc(10, sizeof(int)); // 等同于 int* p = malloc(sizeof(int) * 10); memset(p, 0, sizeof(int) * 10);上面示例中,calloc()相当于malloc() + memset()。calloc()分配的内存块,也要使用free()释放。realloc()realloc()函数用于修改已经分配的内存块的大小,可以放大也可以缩小,返回一个指向新的内存块的指针。如果分配不成功,返回 NULL。该函数的原型定义在头文件stdlib.h。void* realloc(void* block, size_t size)它接受两个参数。block:已经分配好的内存块指针(由malloc()或calloc()或realloc()产生)。size:该内存块的新大小,单位为字节。realloc()可能返回一个全新的地址(数据也会自动复制过去),也可能返回跟原来一样的地址。realloc()优先在原有内存块上进行缩减,尽量不移动数据,所以通常是返回原先的地址。如果新内存块小于原来的大小,则丢弃超出的部分;如果大于原来的大小,则不对新增的部分进行初始化(程序员可以自动调用memset())。下面是一个例子,b是数组指针,realloc()动态调整它的大小。int* b; b = malloc(sizeof(int) * 10); b = realloc(b, sizeof(int) * 2000);上面示例中,指针b原来指向10个成员的整数数组,使用realloc()调整为2000个成员的数组。这就是手动分配数组内存的好处,可以在运行时随时调整数组的长度。realloc()的第一个参数可以是 NULL,这时就相当于新建一个指针。char* p = realloc(NULL, 3490); // 等同于 char* p = malloc(3490);如果realloc()的第二个参数是0,就会释放掉内存块。由于有分配失败的可能,所以调用realloc()以后,最好检查一下它的返回值是否为 NULL。分配失败时,原有内存块中的数据不会发生改变。float* new_p = realloc(p, sizeof(*p * 40)); if (new_p == NULL) { printf("Error reallocing\n"); return 1; }注意,realloc()不会对内存块进行初始化。restrict 说明符声明指针变量时,可以使用restrict说明符,告诉编译器,该块内存区域只有当前指针一种访问方式,其他指针不能读写该块内存。这种指针称为“受限指针”(restrict pointer)。int* restrict p; p = malloc(sizeof(int));上面示例中,声明指针变量p时,加入了restrict说明符,使得p变成了受限指针。后面,当p指向malloc()函数返回的一块内存区域,就意味着,该区域只有通过p来访问,不存在其他访问方式。int* restrict p; p = malloc(sizeof(int)); int* q = p; *q = 0; // 未定义行为上面示例中,另一个指针q与受限指针p指向同一块内存,现在该内存有p和q两种访问方式。这就违反了对编译器的承诺,后面通过*q对该内存区域赋值,会导致未定义行为。memcpy()memcpy()用于将一块内存拷贝到另一块内存。该函数的原型定义在头文件string.h。void* memcpy( void* restrict dest, void* restrict source, size_t n );上面代码中,dest是目标地址,source是源地址,第三个参数n是要拷贝的字节数n。如果要拷贝10个 double 类型的数组成员,n就等于10 * sizeof(double),而不是10。该函数会将从source开始的n个字节,拷贝到dest。dest和source都是 void 指针,表示这里不限制指针类型,各种类型的内存数据都可以拷贝。两者都有 restrict 关键字,表示这两个内存块不应该有互相重叠的区域。memcpy()的返回值是第一个参数,即目标地址的指针。因为memcpy()只是将一段内存的值,复制到另一段内存,所以不需要知道内存里面的数据是什么类型。下面是复制字符串的例子。#include <stdio.h> #include <string.h> int main(void) { char s[] = "Goats!"; char t[100]; memcpy(t, s, sizeof(s)); // 拷贝7个字节,包括终止符 printf("%s\n", t); // "Goats!" return 0; }上面示例中,字符串s所在的内存,被拷贝到字符数组t所在的内存。memcpy()可以取代strcpy()进行字符串拷贝,而且是更好的方法,不仅更安全,速度也更快,它不检查字符串尾部的\0字符。char* s = "hello world"; size_t len = strlen(s) + 1; char *c = malloc(len); if (c) { // strcpy() 的写法 strcpy(c, s); // memcpy() 的写法 memcpy(c, s, len); }上面示例中,两种写法的效果完全一样,但是memcpy()的写法要好于strcpy()。使用 void 指针,也可以自定义一个复制内存的函数。void* my_memcpy(void* dest, void* src, int byte_count) { char* s = src; char* d = dest; while (byte_count--) { *d++ = *s++; } return dest; }上面示例中,不管传入的dest和src是什么类型的指针,将它们重新定义成一字节的 Char 指针,这样就可以逐字节进行复制。*d++ = *s++语句相当于先执行*d = *s(源字节的值复制给目标字节),然后各自移动到下一个字节。最后,返回复制后的dest指针,便于后续使用。memmove()memmove()函数用于将一段内存数据复制到另一段内存。它跟memcpy()的主要区别是,它允许目标区域与源区域有重叠。如果发生重叠,源区域的内容会被更改;如果没有重叠,它与memcpy()行为相同。该函数的原型定义在头文件string.h。void* memmove( void* dest, void* source, size_t n );上面代码中,dest是目标地址,source是源地址,n是要移动的字节数。dest和source都是 void 指针,表示可以移动任何类型的内存数据,两个内存区域可以有重叠。memmove()返回值是第一个参数,即目标地址的指针。int a[100]; // ... memmove(&a[0], &a[1], 99 * sizeof(int));上面示例中,从数组成员a[1]开始的99个成员,都向前移动一个位置。下面是另一个例子。char x[] = "Home Sweet Home"; // 输出 Sweet Home Home printf("%s\n", (char *) memmove(x, &x[5], 10));上面示例中,从字符串x的5号位置开始的10个字节,就是“Sweet Home”,memmove()将其前移到0号位置,所以x就变成了“Sweet Home Home”。memcmp()memcmp()函数用来比较两个内存区域。它的原型定义在string.h。int memcmp( const void* s1, const void* s2, size_t n );它接受三个参数,前两个参数是用来比较的指针,第三个参数指定比较的字节数。它的返回值是一个整数。两块内存区域的每个字节以字符形式解读,按照字典顺序进行比较,如果两者相同,返回0;如果s1大于s2,返回大于0的整数;如果s1小于s2,返回小于0的整数。char* s1 = "abc"; char* s2 = "acd"; int r = memcmp(s1, s2, 3); // 小于 0上面示例比较s1和s2的前三个字节,由于s1小于s2,所以r是一个小于0的整数,一般为-1。下面是另一个例子。char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'}; char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'}; if (memcmp(s1, s2, 3) == 0) // true if (memcmp(s1, s2, 4) == 0) // true if (memcmp(s1, s2, 7) == 0) // false上面示例展示了,memcmp()可以比较内部带有字符串终止符\0的内存区域。
2023年09月20日
4 阅读
0 评论
0 点赞
1
...
14
15
16
...
35