calloc:堆中分配m个连续 初始化为0的字节块 内存,返回首字节地址
void *calloc(size_t 多少个字节块, size_t 每个字节块多少个字节) #include <stdio.h> #include <stdlib.h> int main() { float *f ; f = (float*)calloc(3,4); printf("f:%f\n", f[0]); //f:0.000000 f[0] = 1; f[1] = 2; f[2] = 3; printf("f:%f\n", f[2]); //f:3.000000 float *f2 ; f2 = (float*)malloc(3*4); f2[1] = 4; printf("f2:%f\n", f2[1]);//f2:4.000000 return 0; }
分配(通常是临时开辟一段连续的内存),释放 举例
void flatten(float *x, int size, int layers, int batch, int forward) { float *swap = calloc(size*layers*batch, sizeof(float)); int i,c,b; for(b = 0; b < batch; ++b){ for(c = 0; c < layers; ++c){ for(i = 0; i < size; ++i){ int i1 = b*layers*size + c*size + i; int i2 = b*layers*size + i*layers + c; if (forward) swap[i2] = x[i1]; else swap[i1] = x[i2]; } } } memcpy(x, swap, size*layers*batch*sizeof(float)); free(swap); }
float有效小数位:小数点后6位
#include <stdio.h> int main(){ float a = 0.1234567; printf("%f\n",a); // 0.123457 printf("size of float :%d\n",sizeof(a)); //size of float :4 return 0; } 如果小数点后超出6位,直接四舍五入
c float的精度
#include <stdio.h> int main(){ float a = 1234567890.1234567; printf("a = %f\n", a); // a = 1234567936.000000 return 0; } float小数点后6位有效小数,但它的有效位数是7,第8位就开始不准了
float的存储结构
float类型数据占四个字节,一个字节是8位,共32位 以类似 3.402823466 E+38 的格式存储 第1位: 0表示正数,1表示负数 第2-9位:指数 第10-32:几点几的小数部分
AI中的小数
import numpy as np np.allclose(1e-8,0) # True AI中认为小数点高于7位的部分为0, 而C中float高于6位就自动四舍五入 第8位就是0,第7位四舍五入到第6位上,即合理又不浪费 还减少了计算的代价, AI框架最终还是要转化为C的float类型进行计算, 而double的有效位数为15,对AI来说太多了, 所以AI中数据类型通常指定为float32
float的最大值 :3.402823466 E+38
3.4乘以10的38次方是一个非常大的数 10的9次方为亿,其38次方不知道多少个亿 大部分运算使用float类型够用
fork:创建一个从fork调用位置开始与父进程并行的子进程 fork调用位置之前的资源与父进程一致,从fork位置开始创建一个子进程 fork调用,创建一个子进程,此时已经有两个进程了:父进程,子进程 fork返回时,父进程返回子进程的pid,子进程的fork返回的是0 这两个返回跟对方已经没任何关系了 这时候的代码有两份,各自执行各的,互不影响,因为他们在两个进程内 有点像小说中的分身的意思,你炼制分身之前,这世界上只有一个你, 你克隆一个你自己,从此之后你们是两个独立的个体了 |
#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main() { pid_t pid; int i; // 调用 fork 创建子进程 pid = fork(); if (pid < 0) { // fork 失败 fprintf(stderr, "Fork failed\n"); return 1; } else if (pid == 0) { // 子进程 for (i = 0; i < 100; i++) { printf("子进程: %d\n", i); } } else { // 父进程 printf("父进程: %d\n", pid); } printf("------main over-----\n"); return 0; } 注意 main over输出了两次;另外,主进程执行完毕后,代码执行没有结束,直到子进程也执行完才结束 xt@qisan:/opt/tpf/cwks/alg02$ ./a.out 父进程: 9029 ------main over----- 子进程: 0 子进程: 1 子进程: 2 子进程: 3 子进程: 4 子进程: 5 ... ... ... 子进程: 96 子进程: 97 子进程: 98 子进程: 99 ------main over----- |
|
|
|
free释放malloc/calloc在堆中分配的内存
#include <stdio.h> #include <stdlib.h> int main(){ char a[3]={1,2,3}; // free(a); // warning: ‘free’ called on unallocated object ‘a’ [-Wfree-nonheap-object] char *ss = "abc"; // free(ss); // c1.c:8:5: warning: ‘free’ called on a pointer to an unallocated object ‘"abc"’ [-Wfree-nonheap-object] char *p ; p = (char *)malloc(sizeof(char)); if (p != NULL){ free(p); } return 0; } free函数原型: void free(void *ptr); free只用于释放malloc/calloc分配的内存,除此之外就给你报错 对于一个普通变量,char a,这个a在栈中分配空间,定长,a的值是一个char型字符 对于一个指针变量,char *p,这个p在栈中分配空间,定长,p的值是32/64位地址 该地址指向了堆中某个位置,free就是要去释放从这个位置开始到指定长度的空间的内存
free参数是指针,释放的是指针对应的内存空间,但栈上的地址不需要free,会自动释放,
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char* ss = "wa ka ka"; // free(ss); //warning: incompatible implicit declaration of built-in function ‘free’ [enabled by default] printf("%d\n",sizeof(char)); //1 char* s1; s1 = (char *)malloc(9*sizeof(char)); printf("1 :%p\n",s1); //1 :0xbab010 为s1分配了地址 //这行是错误的,s1已经有指向的地址,接下来是往其指向的地址赋值,而不是改变这个指向 //C语言中的变量,都是在分配地址时就指定了长度,最多扩展地址对应的长度,没见过改地址的 // s1 = "wa ka ka"; strcpy( s1, "wa ka ka"); printf("2 :%p\n",s1);//2 :0xbab010 //free释放的是malloc分配的内存空间,表示这些空间可以由其他程序使用了, //它并不改变s1指向的地址,只是s1指向的地址它不能用了 if(s1 != NULL){ free(s1); } printf("3 :%p\n",s1);//3 :0xbab010 s1 = NULL; printf("4 :%p\n",s1);//4 :(nil) 这下连指向的地址也没了 return 0; }
char *fgets(char *str, int n, FILE *stream);
从指定的流 stream 读取一行,并把它存储在str所指向的字符串内。 当读取(n-1)个字符时,或者读取到换行符时,或者到达文件末尾时,停止 之所以是读取n-1个,而不是n个,是因为字符串默认会多一个\0字符, 比如,输入abc,实际上占4个字符,读取3个字符,就是所输入的字符 这个流,可以是标准输入,也可以是文件
#include <stdio.h> #include <string.h> int main() { char str[100]; printf("请输入一个字符串:"); fgets(str, sizeof(str), stdin); // 从标准输入读取字符串 printf("您输入的内容:%s\n", str); return 0; } $ ./a.out 请输入一个字符串:abc 您输入的内容:abc 顺带提一下stdin,stdin就是“用户区”(与内核区STDIN_FILENO对应)的标准输入,代表从键盘输入
fgets 从文件中读取
#include <stdio.h> int main() { FILE *fp = NULL; fp = fopen("/tmp/a.log", "w+"); // w+ 表示可读可写 fprintf(fp, "%d: ",1); // 格式化输入,要输入什么类型先指定格式 fprintf(fp, "每天 学习 五分钟\n"); // 不指定默认为字符串 fputs("2: 每天 运动 半小时\n", fp); // 专写字符串 fclose(fp); char buff[255]; fp = fopen("/tmp/a.log", "r"); fscanf(fp, "%s", buff); //遇到空格或换行符时,停止读取 printf("%s\n", buff ); //1: fgets(buff, 255, (FILE*)fp); //读一行或255个字符 printf("%s\n", buff ); // 每天 学习 五分钟 fgets(buff, 255, (FILE*)fp); printf("3: %s\n", buff ); //3: 2: 每天 运动 半小时 fclose(fp); } $ gcc c.c $ ./a.out 1: 每天 学习 五分钟 3: 2: 每天 运动 半小时
inline关键字加在方法的 “定义”前面, 可将方法的内容直接嵌入该方法被调用的地址, 可省去方法调用的性能消耗
通常用在调用频率极高的地方:这样才会考虑去节省每次方法调用消耗的那一点点损耗
#include <stdio.h> static inline float distance_from_edge(int x, int max) { int dx = (max/2) - x; if (dx < 0) dx = -dx; dx = (max/2) + 1 - dx; dx *= 2; float dist = (float)dx/max; if (dist > 1) dist = 1; return dist; } int main () { int i; for(i =0;i<5;i++){ float d = distance_from_edge(i, 2); printf("%f\n",d); } return 0; } 什么叫调用频率极高:比如千万级以上的运算,矩阵运算等
malloc:堆中分配n个连续 字节 的内存,返回首字节地址
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *pp; /* 动态分配内存 */ pp = (char *)malloc(100 * sizeof(char)); if( pp == NULL ) { fprintf(stderr, "error: - unable to allocate required memory\n"); }else { strcpy( pp, "静心,三思而后行"); } printf("pp: %s\n", pp ); free(pp); }
malloc与数组区别
linux OS进程内存有栈,堆两类, 编辑时用到的内存放在栈里,比如数组; malloc在堆里分配内存; 数组生命周期结束时,其占用内存自动释放; malloc分配的内存可以使用free函数由程序员提前释放;
字符指针到字符数组
#include <stdio.h> #include <string.h> int main () { char *path1 = "/tmp"; int count = strlen(path1); char ss[100]; memcpy(ss,path1,count); printf("%s--\n",ss); //tmp-- memcpy(ss,path1,count+1); printf("%s--\n",ss); ///tmp-- return 0; } count与count+1结果一样, 这是因为字符串最后一个字符'\0'代表空/终止,C编辑器会自动处理,所以才一样 推荐+1 memcpy比strcpy多一个参数,这个参数表示要copy的字节数
要地址 还是要内容
#include <stdio.h> #include <string.h> int main () { // char* c = "bbb"; char c[] = "bbb"; char* a1 = "/tmp"; char a2[10]; char* b = c; a1 = b; //a1指针变量,值为一个地址,地址变了 //memcpy,将内容copy走,不涉及地址变更 //从此,两份内容之间再也没有关系了 memcpy(a2, b, strlen(b)+1); printf("a1=%s--\n",a1); //a1=bbb-- c[2]='c'; //不允许使用memcpy将字符串中赋值,但字符数组是可以的 //这也是字符数组与字符串的一个区别 // memcpy(a1, b, strlen(b)+1); //Segmentation fault (core dumped) printf("a1=%s--\n",a1); ///a1=bbc-- printf("a2=%s--\n",a2); ///a2=bbb-- return 0; }
指针变量 终还是一个变量,其值是个地址 这不是内容,内容改变,地址未必改变 memcpy 专门COPY内容
按字节填充数据块
#include <stdio.h> #include <string.h> int main(){ int a[3]; int i; for(i=0;i<3;i++){ printf("%d ",a[i]); } memset(a,0,sizeof(a)); printf("\n"); for(i=0;i<3;i++){ printf("%d ",a[i]); } printf("\n"); return 0; }
还可以对char* ss字符指针进行填充,此时0还代表着NULL,即字符串的结束 也不是处处需要填充,字符串类操作会自动在字符结束处填充上0
NULL与0等价
#include <stdio.h> int main(){ printf("%d\n",NULL); //0 printf("%d\n",0==NULL);//1 return 0; }
NULL作为参数
#include <stdio.h> void test(char* ss){ if (ss!=NULL){ printf("%s\n",ss); }else{ printf("params is NULL\n"); } } int main(){ char* s1 = "123"; test(NULL); test(s1); } $ gcc b.c $ ./a.out params is NULL 123
linux pipe
管道是两个进程之间的连接,一个进程的标准输出成为另一个进程的标准输入。 在UNIX操作系统中,管道用于进程间通信。
pipe特性
类似文件,能缓冲数据,可将之看作内存中的“虚拟文件”,相比文件,它又多了一些约束 单向通信,一个进程向管道写入数据,另一个进程从管道读取数据 进程间共享信息,管道可以被 创建进程及其所有子进程 读写。一个进程可以写入这个“虚拟文件”或管道,另一个相关进程可以从中读取 原子性,如果在某个内容完全写入管道之前,某个进程试图读取该内容,则该进程将挂起,直到内容被写入 管道系统调用 在进程的 打开文件表中 找到 前两个可用位置,并将其分配给管道的读取和写入端。
指针可以当作一维数组
#include <stdio.h> #include <stdlib.h> int main() { float *f ; float arr[3] = {1,2,3}; f = arr ; printf("f:%f\n", f[0]); //f:1.000000 printf("f:%f\n", f[3]); //f:0.000000,这一行表示指针可越界 float a1 = 11; f = &a1; printf("f:%f\n", f[0]); //f:11.000000,这一行表示单个数也成数组 return 0; } 数组定长,在初始化数组时,需要明确给出数组元素的个数 而指针不需要确定要指多少个元素, 剩下的它们都一样
字符数组与字符指针
#include <stdio.h> #include <string.h> int main(){ typedef char string[]; string s1 = "abc"; char *s2 = "看上去是字符串,实际就是字符串"; s2 = "ABC"; // abc--3--4 printf("%s--%ld--%ld\n",s1,strlen(s1),sizeof(s1)); printf("%s\n",s2); return 0; } strlen只针对 const char * 类型,不是这个类型的,无法使用strlen计算长度 从这点也可以看出C中的字符串,是定长的多个字符形成的常量
数字数组与数字指针
#include <stdio.h> #include <string.h> int main(){ typedef int ints[]; ints s1 = {1,2,3}; int *s2 ; s2 = s1; // 1--12 printf("%d--%ld\n",s1[0],sizeof(s1)); printf("%d\n",s2[0]); return 0; }
数组与指针的区别:是否定长
对于指针,下面是正确的: char *s2 = "看上去是字符串,实际就是字符串"; s2 = "ABC"; 对于数组,下面是错误的: typedef char string[]; string s1 = "abc"; string s1 = "day day up"; 报错如下: a.c: In function ‘main’: a.c:7:12: error: redefinition of ‘s1’ 7 | string s1 = "day day up"; | ^~ a.c:6:12: note: previous definition of ‘s1’ with type ‘char[4]’ 6 | string s1 = "abc"; 虽然在定义string类型时,没有指定长度, 但在初始化string类型的变量s1时,s1的长度被固定为4, 并且后续不可对其修改 如果再换一个变量名,就没有问题,下面的是对的: typedef char string[]; string s1 = "abc"; string s2 = "day day up"; typedef char string[]; 这句虽然一定程度上实现了变长数组,但是有要求, 就是在定义变量时,必须对其初始化,以确定数组的长度; 下面的两句是错误的,因为没有对数组进行初始化,关键是没有指定数组的长度 typedef char string[]; string s1;
指针:变长的数组
#include <stdio.h> #include <string.h> #include <stdlib.h> #define N 3 typedef char string[]; struct Students { int student_id; // 4 bytes char* student_name; //sizeof在计算指针类型时,统一是16字节,计算的是地址的长度 float score; // 4 bytes }; int main(){ struct Students st1; st1.student_id = 1001; st1.student_name="南宫婉"; //一个汉字3字节,再加上1个自补的结束符,这三个汉字等价于char[10] // strcpy(st1.student_name,"南宫婉");//不支持这种写法,编辑报错 st1.score = 73; int size = sizeof(st1); printf("student1 size:%d\n",size); // student1 size:24 printf("student1.student_name :%s\n",st1.student_name); // return 0; } 名称这一步如果使用下面的写法则编辑报错:error: flexible array member not at end of struct string student_name; 无法做到在初始化变量时,让数组有固定的长度 而指针没有这个问题
指针变量可以换地址,而数组不可以
#include <stdio.h> #include <string.h> int main(){ int s1[] = {1,2,3}; int s2[] = {4,2,3,4}; int* s3 = s1; s3 = s2; // 4--12 printf("%d--%ld\n",s3[0],sizeof(s1)); return 0; } {1,2,3},"abc"有一个共性,它们都是常量,指针指的一个常量的地址, 虽然s1,s2是变量,没有加const,但这些变量的值最终还是常量 指针指的是地址,对于一个地址来说,它里面放的就是常量,虽然它可以换成别的常量 对于一个地址本身, 这个地址如果是数组,它自带长度,如果是字符数组,还会有个结束标志'\0' 下面的代码会报错 #include <stdio.h> int main(){ int s1[] = {1,2,3}; int s2[3] ; s2 = s1; // error: assignment to expression with array type } 与指针相比,数组更像是加上了const的指针,不能再变地址了 初始化时,必须就确定其长度 指针变量的初始化就是确定一个地址,然后就看这个地址有什么作用了 从名字看,指针变量,指针变量,地址当然能变了,不然还叫什么指针变量
**可以看作二维数组
float **vals; 等价于二维数组
指针变量的变 相对 数据地址的定
#include <stdio.h> int main(){ int s1[] = {1,2,3}; int s2[3] ; s2 = s1; // error: assignment to expression with array type }
s2 = s1 报错,说明数组的地址在初始化后,不可以再被改变了 int *p 指针变量的地址可以改变,是因为它本身就是被设计为 存储地址的变量 数组中可变的是地址中的元素,地址是不可变的
void*:泛化/抽象类型
一个指针就是一个地址,有时不知道这个地址放的是何种类型的数据, 此时,可认为该地址存储数据的类型为 void* #include <stdio.h> int main(){ float a = 0.1234567; printf("a = %f\n", a); // a = 0.123457 float *b = &a; void *x = b; float b2 = *(float*)x; printf("b2 = %f\n", b2);// b2 = 0.123457 double d = 3.1415926; printf("d = %f\n", d); void *d1 = &d; double d2 = *(double*)d1; printf("d2 = %f\n", d2);// d2 = 3.141593 double d3 = 12345678901234567.1415926; printf("d3 = %f\n", d3); //d3 = 12345678901234568.000000 void *d4 = &d3; double d5 = *(double*)d4; printf("d5 = %f\n", d5);// d5 = 12345678901234568.000000 return 0; } 指针是地址,地址是内存字节的位置编码,是整数,通常以16进制(0x)存储 指针变量存的就是类似0x这样的一串数字 指针变量也是有类型的,可以是int,float,double,struct等,存什么数据就是什么类型 在为函数传参方便,所有的类型都可以是void*, 即先不问是什么类型,把参数先传进行,具体用时再转换回去 从上面的的例子额外多说明一个事, double的精度是16位,超过16位的部分就不准了 在不超过16位的情况下,小数点后的精度最多为6位,其中第7位四舍五入到第6位上 void*在转换的过程中,完全不影响其精度的换算,还是其自身的精度
c float的精度
#include <stdio.h> int main(){ float a = 1234567890.1234567; printf("a = %f\n", a); // a = 1234567936.000000 return 0; } 同double一样,小数点后6位有效小数,但它的有效位数是7,第8位就开始不准了
浮点型的存储方式 c语言中void *的使用 了解C语言中的pipe()系统调用 C++ free()用法及代码示例