我的c语言笔记
1. 进制转换
- 二进制、八进制和十六进制向十进制转换都非常容易,就是“按权相加”。如:1010.1101 = 1×23 + 0×22 + 1×21 + 0×20 + 1×2-1 + 1×2-2 + 0×2-3 + 1×2-4 = 10.8125(十进制)
- 十进制整数转换为 N 进制整数采用“除 N 取余,逆序排列”法。
- 十进制小数转换成 N 进制小数部分采用“乘 N 取整,顺序排列”法。
- 二进制、八进制、十六进制之间转换24=16,即四位二进制可换位一位16进制;同理八进制也是如此,不过八进制与十六进制之间转换需要借助二进制。
- 使用储存:二进制由 0 和 1 两个数字组成,使用时必须以0b或0B(不区分大小写)开头 int b = -0b110010; //换算成十进制为 -50 并不是所有的编译器都支持二进制数字,只有一部分编译器支持,并且跟编译器的版本有关系。 八进制由 0~7 八个数字组成,使用时必须以0开头(注意是数字 0,不是字母 o)int b = -0101; //换算成十进制为 -65 十六进制由数字 0~9、字母 A~F 或 a~f(不区分大小写)组成,使用时必须以0x或0X(不区分大小写)开头,int b = -0XA0; //换算成十进制为 -160
- 使用输出可以直接以某种方式输出printf实现转换,同时也可以用scanf格式化实现输入转换但是输出时要加前缀就得在前面提前加上,在输出时带上特定的前缀。在格式控制符中加上#即可输出前缀,例如 %#x、%#o、%#lX、%#ho 等
2. 位运算
优先级从高到低依次为 :~、<<、>>、&(按位与)、^、| ;
其中~的结合方向自右至左,且优先级高于算术运算符,其余运算符的结合方向都是自左至右
一篇好的关于位运算的文章即[[位运算]]
3. 变量类别
ASCII码:0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等。32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
数据大小:int=21*10^8,long long =922*10^16
ceil和floor和(int)和round
运算符优先级
- (),[],. ,->
- 单目运算符:* ,&[取地址] ,! [逻辑非运算符],(类型) ,sizeof() ,++ ,+ ,-[负号运算] ,--,~[按位取反]
- / , * ,%
-
- ,-
- << , >>[左移右移]
-
,< ,<= ,>=
- == ,!=
- 位运算从大到小:&,^,|
- &&,||
- ?:
- 赋值运算符
- ,逗号运算符
4.数组和指针不等价
- 数组名的指向不可以改变,而指向数组的指针是可以改变的。使用数组名自增遍历数组元素会改变其指向会出错
- 字符串指针指向的字符串中的字符是不能改变的,而字符数组中的字符是可以改变的。第一个是在常量区是不可更改的,强制修改会产生错误。第二个实在栈区是可读写的。 ^dfa7e0
- 求数组长度时,借用数组名可求得数组长度,而借用指针却得不到数组长度,只能得到指针的大小
- 对C语言指针的总结
- 只需一招,彻底攻克C语言指针,再复杂的指针都不怕
5.联合(union)和枚举
uniom
共用体和结构体的不同:结构体类似于一个包裹,结构体中的成员彼此是独立存在的,分布在内存的不同单元中,他们只是被打包成一个整体叫做结构体而已;共用体中的各个成员其实是一体的,彼此不独立,他们使用同一个内存单元。可以理解为:有时候是这个元素,有时候是那个元素。更准确的说法是同一个内存空间有多种解释方式,共用体union就是对同一块内存中存储的二进制的不同的理解方式。
union的sizeof测到的大小实际是union中各个元素里面占用内存最大的那个元素的大小。因为可以存的下这个就一定能够存的下其他的元素。![[c附件#共用体]]
共用体变量的地址和它的个成员的地址都是同一个地址。 例如:&a.i,&a.ch,&a.f都是同一值。
共用体成员具有瞬时性:由于共用体成员之间共用一段内存,所以,共用体成员的赋值会相互影响,最后一次更新的值为有效值!如果定义了几个变量,但是在没有使用之前,就重新赋值,那么前后的值就会有覆盖。
共用体类型可以出现结构体类型定义中,也可以定义共用体数组。结构体中也可以出现在共用体类型定义中,数组也可以作为共用体成员。
共用体的主要用途:共用体就用在那种对同一个内存单元进行多种不同规则解析的这种情况下。C语言中其实是可以没有共用体的,用指针和强制类型转换可以替代共用体完成同样的功能,但是共用体的方式更简单、更便捷、更好理解。
一个实例是: 现有一张关于学生信息和教师信息的表格。学生信息包括姓名、编号、性别、职业、分数,教师的信息包括姓名、编号、性别、职业、教学科目。f 和 m 分别表示女性和男性,s 表示学生,t 表示教师。如果把每个人的信息都看作一个结构体变量的话,那么教师和学生的前 4 个成员变量是一样的,第 5 个成员变量可能是 score 或者 course。当第 4 个成员变量的值是 s 的时候,第 5 个成员变量就是 score;当第 4 个成员变量的值是 t 的时候,第 5 个成员变量就是 course。
enum
在枚举值表中应罗列出所有可用值。这些值也称为枚举元素。例如:枚举名为weekday,枚举值共有7个,即一周中的七天。凡被说明为weekday类型变量的取值只能是七天中的某一天。当不写对应的值,枚举值默认从0开始,当定义了第一个枚举变量为0后,枚举值就会从0开始递增,和上面的写法是一样的。后续枚举成员的值在前一个成员上加1![[c附件#枚举]]
enum是枚举型,所占内存空间恒等于4字;union是共用体,成员共用一个变量缓冲区。
注意:
- 不能定义同名的枚举的类型
- 不能包含同名的枚举成员
- 枚举元素不是变量,而是常数,因此枚举元素又称为枚举常量。因为是常量,所以不能对枚举元素进行赋值。
- 枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。以分号;结束。
- 宏定义和枚举的区别: (枚举型是预处理指令#define的替代。)
1、枚举是将多个有关联的符号封装在一个枚举中,而宏定义是完全散的。也就是说枚举其实是多选一,而且只能在这里面选,有时候有效防止其他不符数据的输入。
2、什么情况下用枚举?当我们要定义的常量是一个有限集合时(譬如一星期有7天,譬如一个月有31天,譬如一年有12个月····),最适合用枚举。(其实宏定义也行,但是枚举更好)
3、不适合用枚举的情况下(比如定义的常量符号之间无关联,或者无限的)用宏定义。 - 对枚举型的变量可以直接赋任意整数值,如果赋值浮点数,也会自动去掉小数部分。赋整数值时,可直接赋值,因为枚举的本质就是整型(int)数据的集合。其实,更正规的做法是进行显式的强制类型转换:enum week oneday = (enum week)100;直接使用枚举的情况更常见,因为一旦通过变量,那么就可以赋任何值,那就失去了枚举的意义了,本来枚举就是为了放一些特定整数集合的数据类型,值一般是固定的,比如对错、比如星期、比如状态等等。
6.数组数组名也是一种类型,只是在某些时候会转化为指针
二维数组可以a[][2]传入函数
字符串
- 储存在char类型的数组中,如果末尾包含一个表示字符串末尾的空字符\0,则构成了字符串。使用memset(name,0,sizeof(name))进行初始化。数组名是数组元素的首地址所以字符串名字就是其地址指向。尽量不要产生垃圾值,也就是在字符串中间放入’/0‘。我们在使用'\0'时后面最好不要连着数字,因为有可能几个数字连起来刚好是一个转义字符
- 若没有存放'\0'也就是字符数组,使用字符串形式输出会产生意想不到的后果
- const * s字符串不能修改[[我的c语言笔记#^dfa7e0]]
- 一维字符串初始化:![[c附件#字符串]]同时,为了防止字符个数超出数组空间的现象,我们在采用字符串对字符数组进行初始化时,一般省略一维数组空间的大小,即:char a[] = {"Hello World"} ;数组元素不会超过定义大小,否则会报错。对单个字符的初始化不能使用字符串进行赋值
- 二维字符串初始化:![[c附件#二维字符串初始化]]
数组:在c99之后可以使用不是常量来定义数组,即可以输入,但是c++中array不行
![[c附件#二维数组初始化]]![[c附件#二维数组行数和列数]]
7. 链表[[链表]]
链表的引入
从数组的缺陷说起数组有2个缺陷,
(1)一个是数组中所有元素的类型必须一致;第二个是数组的元素个数必须事先制定并且一旦指定之后不能更改。
(2)如何解决数组的2个缺陷:数组的第一个缺陷靠结构体去解决。结构体允许其中的元素的类型不相同,因此解决了数组的第一个缺陷。所以说结构体是因为数组不能解决某些问题所以才发明的。
(3)如何解决数组的第二个缺陷?我们希望数组的大小能够实时扩展。譬如我刚开始定了一个元素个数是10,后来程序运行时觉得不够因此动态扩展为20.普通的数组显然不行,我们可以对数组进行封装以达到这种目的;我们还可以使用一个新的数据结构来解决,这个新的数据结构就是链表。
总结:几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组。
链表是什么样的?
(1)顾名思义,链表就是用锁链连接起来的表。这里的表指的是一个一个的节点(一个节点就是一个校区),节点中有一些内存可以用来存储数据(所以叫表,表就是数据表);这里的锁链指的是链接各个表的方法,C语言中用来连接2个表(其实就是2块内存)的方法就是指针。
(2)链表是由若干个节点组成的(链表的各个节点结构是完全类似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。
单链表的实现,单链表的节点构成
(1)链表是由包含有效数据和指针的节点组成的。
(2)定义的struct node只是一个结构体,本身并没有变量生成,也不占用内存。结构体定义相当于为链表节点定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。
堆内存的申请和使用
(1)链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存。
(2)使用堆内存来创建一个链表节点的步骤:1、申请堆内存,大小为一个节点的大小(检查申请结果是否正确);2、清理申请到的堆内存;3、把申请到的堆内存当作一个新节点;4、填充你哦个新节点的有效数据和指针区域。
链表的头指针
(1)头指针可以是一个普通指针,只占4字节。头指针的类型是struct node *类型的,所以它才能指向链表的节点。
(2)一个典型的链表的实现就是:头指针指向链表的第1个节点,然后第1个节点中的指针指向下一个节点,然后依次类推一直到最后一个节点。这样就构成了一个链。
(3)问题:因为我们在insert_tail中直接默认了头指针指向的有一个节点,因此如果程序中直接定义了头指针后就直接insert_tail就会报段错误。我们不得不在定义头指针之后先create_node创建一个新节点给头指针初始化,否则不能避免这个错误;但是这样解决让程序看起来逻辑有点不太顺,因为看起来第一个节点和后面的节点的创建、添加方式有点不同。
(4)链表还有另外一种用法,就是把头指针指向的第一个节点作为头节点使用。头节点的特点是:第一,它紧跟在头指针后面。第二,头节点的数据部分是空的(有时候不是空的,而是存储整个链表的节点数),指针部分指向下一个节点,也就是第一个节点。
(5)这样看来,头节点确实和其他节点不同。我们在创建一个链表时添加节点的方法也不同。头节点在创建头指针时一并创建并且和头指针关联起来;后面的真正的存储数据的节点用节点添加的函数来完成,譬如insert_tail.
(6)链表有没有头节点是不同的。体现在链表的插入节点、删除节点、遍历节点、解析链表的各个算法函数都不同。所以如果一个链表设计的时候就有头节点那么后面的所有算法都应该这样来处理;如果设计时就没有头节点,那么后面的所有算法都应该按照没有头节点来做。实际编程中两种链表都有人用,所以大家在看别人写的代码时一定要注意看它有没有头节点。
链表逆序 = 遍历 + 头插入
8. 循环,递归
最大公约数和最小公倍数
最大公约数:[常见]https://blog.csdn.net/Dumbking/article/details/117635614()
穷举法:从m,n当中小的哪个开始递减找到符合m和n都%它为0的唯一解
辗转相除法:一直使用r = m1%n1;m1 = n1;n1 = r;当r=0时,m1就是最大公约数,m* n/m1就是最大公倍数
pell数列:a1 = 1, a2 = 2, … , an = 2 * an − 1 + an - 2,求Pell数列的第k项模上32767是多少
模运算和两种方法尝试
其实完全可以使用循环:
#include<bits/stdc++.h>
using namespace std;
long long a[1000001];
int main()
{
long long i,k,n;
scanf("%lld",&n);
a[1]=1;
a[2]=2;
for(i=3;i<=1000001;i++)
{
a[i]=(a[i-1]*2+a[i-2])%32767; //此题特别注意超时
}
for(long long j=1;j<=n;j++)
{
scanf("%d",&k);
printf("%d\n",a[k]);
}
return 0;
}
9.优化的冒泡排序
教程上的:
#include <stdio.h>
int main(){
int nums[10] = {4, 5, 2, 10, 7, 1, 8, 3, 6, 9};
int i, j, temp;
//冒泡排序算法:进行 n-1 轮比较
for(i=0; i<10-1; i++){
//每一轮比较前 n-1-i 个,也就是说,已经排序好的最后 i 个不用比较
for(j=0; j<10-1-i; j++){
if(nums[j] > nums[j+1]){
temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
//输出排序后的数组
for(i=0; i<10; i++){
printf("%d ", nums[i]);
}
printf("\n");
return 0;
}
优化后:
#include <stdio.h>
int main(){
int nums[10] = {4, 5, 2, 10, 7, 1, 8, 3, 6, 9};
int i, j, temp;
//冒泡排序算法:进行 n-1 轮比较
for(i=0; i<10-1; i++){
//每一轮比较前 n-1-i 个,也就是说,已经排序好的最后 i 个不用比较
for(j=0; j<10-1-i; j++){
if(nums[j] > nums[j+1]){
temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
//输出排序后的数组
for(i=0; i<10; i++){
printf("%d ", nums[i]);
}
printf("\n");
return 0;
}
c语言标准库
strlen(),sizeof()
(ctype.h)int isspace(int ch);其他的通过ASCII判断,isdigit,isxdigit,isalpha,isalnum,ispunct(空格英文数字),isblank,islower,isupper
(string.h)包含NULL、mem*系针对字节 str*系针对字符
1. memset函数:memset(void* s,int ch,size_t n)将buffer所指的n位元素初始化为ch
- 定义数组的同时进行赋值,就叫做初始化。如果全局数组不做初始化,那么编译器将其初始赋值为零。但是局部数组如果不进行初始化,那么其内容将为随机值。也可int e[5] = { 0 };//所有的成员都设置为0,初始化部分,其他也会初始化为0。
- 此函数按字节对内存块进行初始化,所以只能将int数组初始化为0或-1,而且在设置元素个数时是sizeof(s),而不是直接元素个数;
- 对于占一个字节的字符来说其实c的实际范围应该在0~255,因为memset函数只能取c的后八位给所输入范围的每个字节。也就是说无论c多大只有后八位二进制是有效的。
- 当数组作为参数传递时,其传递的实际上是一个指针,这个指针指向数组的首地址,如果用sizeof(a)函数得到的只是指针的长度,而不是数组的长度。解决方案: 在函数中加入数组长度参数,在传递前先获取数组长度,然后将数组长度作为参数传递进去。
2.strlen 函数:size_t strlen( const char* str);strlwr;strupr;
- 功能:计算字符串的有效长度,即字符数,不包含 \0 ,而sizeof 返回的是变量所占的内存数,不是实际内容的长度包含\0,并且要除以元素种类才会得到字符数
- strlen 函数计算的是字符串的实际长度,遇到第一个\0结束
- 函数返回值一定是size_t,是无符号的整数,即typedef unsigned int size_t
- 如果您只定义字符串没有初始化,求它的长度是没意义的,它会从首地址一直找下去,遇到0停止
- char *strlwr(char *s)
函数原型 char *strlwr(char *s)
函数功能: 将字符串s中的字符变为小写 - char *strupr(char *s)
函数原型 char *strupr(char *s)
函数功能: 将字符串s中的字符变为大写
3.strcpy和strncpy函数:memcpy和memmove函数
char * strcpy(char* dest, const char* src);
1. 功 能: 将参数src字符串拷贝至参数dest所指的地址,返回参数dest的字符串起始地址并且复制完字符串后,在dest后追加0
2. 如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况
char * strncpy(char* dest,const char* src, const size_t n);
1. 把src前n字符的内容复制到dest中并且返回dest字符串起始地址
2. 如果src字符串长度小于n,则拷贝完字符串后,在dest后追加0,直到n个
3. 如果src的长度大于等于n,就截取src的前n个字符,不会在dest后追加0
4. dest必须有足够的空间放置n个字符,否则可能会造成缓冲溢出的错误情况
void *memcpy(void *dest, const void *src, size_t n)从 src 复制 n 个字符到 dest。复制后不是是接续而是覆盖通常,dest是一个未初始化的字符数组。
与strcpy比较
1. strcpy只能应用字符类型的复制,而memcpy应用范围更广,任何类型都可以;
2. memcpy相比使用strcpy会更加的安全,当然也可以使用strcpy的安全板本strncpy函数;
3. strcpy一定会拷贝字符串结尾符'\0',memcpy在拷贝字符串的时候,根据指定拷贝字节数拷贝字符串,是否拷贝‘\0’结束符,根据count的大小;
4. 在拷贝相同的字符串,且字节数相同(包括‘]0’)的情况下,strcpy效率比memcpy效率更快。
void *memmove(void *dest, const void *src, size_t n)用于从 src 复制 n 个字符到 dest 的函数。memmove和memcpy的作用都是内存拷贝,唯一的区别是,当内存发生局部重叠时:memmove保 证了拷贝的结果是正确的,但是memcopy不一定是正确的。但是memcpy比memmove速度快。
4.strncat函数:char *strncat (char* dest,const char* src, const size_t n);
-
对于strcat函数:char *strcat(char* dest,const char* src);功能:将src字符串拼接到dest所指的字符串尾部并且返回dest字符串起始地址,以'\0'作为结束标准
-
dest最后原有的结尾字符0会被覆盖掉,并在连接后的字符串的尾部再增加一个0
-
dest要有足够的空间来容纳要拼接的字符串,否则可能会造成缓冲溢出的错误情况
-
目标空间必须可修改(前面不能加const并且不能说常量字符串)
-
追加的字符串src和目标字符串dest中都必须要带有字符’\0’,并且追加字符串src必须以‘\0’结尾,否则追加过程无法顺利实现。
-
strcat函数是不安全的。然数组s的长度是1,但将t连接到s的后面时,不会进行越界检查,而是直接将s追加到t的后面。这样,就会占用不属于s的内存,所以运行程序时可能出现多种情况。写越界就有可能覆盖这些数据,引起其他bug,这是严重的安全隐患! 其实所谓“缓冲区溢出漏洞”大致就是这样来的。
-
strncat与之类似:将src字符串的前n个字符拼接到dest所指的字符串尾部,如果n大于等于字符串src的长度,那么将src全部追加到dest的尾部,如果n小于字符串src的长度,只追加src的前n个字符。以字符长度作为结束标准。
注意,当dest在src后,且有覆盖时。在第一次循环的时候strcat就自身的‘\0’就已经被第一个元素覆盖了,导致后面找不到'\0'所以无法退出循环,进入了死循环中。这时候,就需要用到strncat,strncat的原理不同于strcat,strcat是利用'\0'来退出循环,strncat则是用长度来退出,因为有长度的限制,所以只要长度达到了就能结束,安全性有了更好的保证。
5.strcmp和strncmp函数:
int strncmp(const char *str1,const char *str2 ,const size_t n);
功能:比较str1和str2前n个字符的大小,相等返回0,str1大于str2返回1,str1小于str2返回-1。两个字符串比较的方法是比较字符的ASCII码的大小,从两个字符串的第一个字符开始,如果分不出大小,就比较第二个字符,如果全部的字符都分不出大小,就返回0,表示两个字符串相等
int strcmp(const char *str1, const char *str2 );
功能:比较str1和str2的大小相等返回0,str1大于str2返回1,str1小于str2返回-1
int memcmp(const void *str1, const void *str2, size_t n)把 str1 和 str2 的前 n 个字节进行比较。
6.strchr和strrchr函数memchr
char *strchr(const char *s,const int c);
返回一个指向在字符串s中第一个出现c的位置,如果找不到,返回0
char *strrchr(const char *s,const int c);
返回一个指向在字符串s中最后一个出现c的位置,如果找不到,返回0
void *memchr(const void *str, int c, size_t n)
输入输出的都是字符指针,传入其他类型的指针无操作意义。输出时通常需要将void *强转成其他所需类型的指针。因为函数输出的结果总要是一个具体的类型。这里的位置,指的就是目标字符所在的地址,即指针。
当字符串中要查找的内容被两个0包着时就不会受影响,可以继续搜索
7.strstr函数字符串中找字符串:char *strstr(const char* str,const char* substr);
功能:检索子串在字符串中首次出现的位置返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回0
一些常见函数的重写
(math.h)
三角类
double acos(double x),double asin(double x),double atan(double x),double atan2(double y, double x): C 库函数 double atan2(double y, double x) 返回以弧度表示的 y/x 的反正切。y 和 x 的值的符号决定了正确的象限。
double cos(double x),double cosh(double x),double sin(double x),double sinh(double x),double tan(double x),double tanh(double x)
指数类
double exp(double x):返回 e 的 x 次幂的值,double frexp(double x, int *exponent) //函数功能: 把浮点数 x 分解成尾数和指数。返回值是尾数,并将指数存入 exponent 中。所得的值是 x = mantissa * 2 ^ exponent。就是将x拆分为(0.5~1.0)* 2x,double ldexp(double x, int exponent) 函数功能: 计算并返回 x 乘以 2 的 exponent 次幂。
sqrt()
对数,绝对值,xy,取整,随机数类
double log(double x):即返回ln(x);double log10(double x)
int abs(int x);double fabs(double x) : 返回 x 的绝对值。
double pow(double x, double y) double sqrt(double x)
double ceil(double x) :返回不小于 x 的最小整数值 ,double floor(double x) 返回不大于 x 的最大整数值。//直接类型转换来的方便;double round (double x);四舍五入返回double,(lroud返回long,同理llroud)
int rand(void);返回-90~32767间的随机数
在c++头文件
(time.h)
有time_t, tm, timeval等几种类型的时间
1. time_t
time_t实际上是长整数类型,定义为:typedef long time_t; /* time value /
2、timeval
timeval是一个结构体,在time.h中定义为:
struct timeval {
__time_t tv_sec; / Seconds. /
__suseconds_t tv_usec; / Microseconds. */
};
其中,tv_sec为Epoch(1970-1-1零点零分)到创建struct timeval时的秒数,tv_usec为微秒数,即秒后面的零头。
3、tm是一个结构体,定义为:![[c附件#tm]]
time()函数原 型:time_t time(time_t * timer):time(&timer)和timer = time(NULL)一样;
功 能: 获取当前的系统时间,返回的结果是一个time_t类型,其实就是一个大整数,其值表示从CUT(Coordinated Universal Time)时间1970年1月1日00:00:00(称为UNIX系统的Epoch时间)到当前时刻的秒数。
然后调用localtime、 gmtime、 ctime将time_t所表示的CUT时间转换为本地时间(我们是+8区,比CUT多8个小时)并转成struct tm类型,该类型的各数据成员分别表示年月日时分秒。
time函数也常用于 随机数的生成,用日历时间作为种子。![[c附件#time和srand,rand生成多个随机数]]
gmtime,localtime和localtime_r()和gmtime_r()函数
struct tm * gmtime(long * clock);
功 能:把日期和时间转换为格林威治(GMT)时间的函数。将参数timep 所指的time_t 结构中的信息转换成真实世界所使用的时间日期表示方法,然后将结果由结构tm返回,但是此函数返回的时间日期未经时区转换,而是UTC时间。
struct tm * localtime(const time_t * clock);
功 能: 把从1970-1-1零点零分到当前时间系统所偏移的秒数时间转换为日历时间 ,即返回指向已经进行过时区转化为的tm 结构体的指针,是本地时间
![[c附件#localtime和gmtime的使用]]
struct tm * gmtime_r (const time_t * timep, struct tm * result);
struct tm * localtime_r(const time_t * timep, struct tm * result);
gmtime_r()函数功能与此相同,但是它可以将数据存储到用户提供的结构体中。
localtime_r()函数功能与此相同,但是它可以将数据存储到用户提供的结构体中。它不需要设置tzname。
使用gmtime和localtime后要立即处理结果,否则返回的指针指向的内容可能会被覆盖。 一个好的方法是使用gmtime_r和localtime_r,由于使用了用户分配的内存,这两个函数是不会出错的。
clock()
#include<studio.h>
#include<time.h>
/*要用clock( )函数必须要包含time.h*/
clock_t start,stop;/*clock_t是clock( )函数返回的变量类型*/
double duration;/*记录被测函数运行时间,以秒为单位*/
int main( )
{ /*不在测试范围内的准备工作写在clock( )调用之前*/
start=clock();/*开始计时*/
MyFunction();/*把被测函数加在这里*/
stop=clock();/*停止计时*/
duration=((double)(stop-start))/CLK_TCK;/*计算运行时间*/
/*其他不在测试范围的处理写在后面,例如输出duration的值*/
return 0;
}
还可以使用difftime计算时间但是只能计算整数。
asctime(),ctime,mktime,difftime,strftime
char* asctime(const struct tm* tblock);
char * ctime(const time_t * time);
都是转换日期和时间为相应的字符串(英文简写形式,形如: Mon Feb 16 11 :29:26 2009)
但是ctime是通过日历时间来生成时间字符串,而asctime是通过tm结构来生成时间字符串
time_t mktime(strcut tm * timeptr);将tm时间结构数据转换成经过的秒数(日历时间)。
double difftime(time_t time1, time_t time0);计算时间间隔才长度,以秒为单位,且只能精确到秒。difftime(stop,start);
说 明:虽然该函数返回值是double类型的,但这并不说明该时间间隔具有同double一样的精度,这是由它的参数决定的。
size_t strftime(char * strDest, size_t maxsize, const char* format , const struct tm * timeptr);![[c附件#strftime example]]
我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在strDest指向的字符串中, 最多向strDest中存放maxsize个字符。返回该函数返回向strDest指向的字符串中放置的字符数。类似于sprintf():识别以百分号(%)开始的格式命令集合,格式化输出结果放在一个字符串中。 格式化命令说明串strDest中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。
%Y 年份 %y 年份,最后两个数字(00-99) %j 十进制表示的每年的第几天
%m 十进制表示的月份 %B 月份的全称 %b 月份的简写
%d 十进制表示的每月的第几天 %D 月/天/年 %F 年-月-日
%W 每年的第几周,把星期一做为第一天(值从0到53)%U 第年的第几周,把星期日作为第一天(值从0到53)
%a 星期几的简写 %A 星期几的全称 %u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
%H 24小时制的小时 %I 12小时制的小时
%p 本地的AM或PM的等价显示 %r 12小时的时间
%M 十时制表示的分钟数 %S 十时制表示的秒数
%T 显示时分秒:hh:mm:ss %R 显示小时和分钟:hh:mm
%c 标准的日期的时间串(Sun Aug 19 02 :56:02 2012) %x 标准的日期串 %X 标准的时间串
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
提示:与 gmstrftime() 的行为相同,不同的是返回时间是本地时间。
(stdio.h)
变量类型:
size_t :这是无符号整数类型,它是 sizeof 关键字的结果。
FILE :这是一个适合存储文件流信息的对象类型。FILE *fp;为文件指针
fpos_t :这是一个适合存储文件中任何位置的对象类型
文件操作
tmpfile
tmpnam
FILE * fopen(const char * path, const char * mode) ;path为包含了路径的文件名,mode为文件打开方式。
r 以只读方式打开文件,该文件必须存在。r+以读/写方式打开文件,该文件必须存在。rb+以读/写方式打开一个二进制文件,只允许读/写数据。rt+以读/写方式打开一个文本文件,允许读和写。
w打开只写文件,若文件存在则长度清为0,即该文件内容消失,若不存在则创建该文件。w+打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)。a+以附加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符 不保留)。
wb以只写方式打开或新建一个二进制文件,只允许写数据。wb+以读/写方式打开或建立一个二进制文件,允许读和写。
wt+以读/写方式打开或建立一个文本文件,允许读写。at+以读/写方式打开一个文本文件,允许读或在文本末追加数据。ab+以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
注意:
1. 一般而言,开文件后会作一些文件读取或写入的动作,若开文件失败,接下来的读写动作也无法顺利进行,所以在fopen()后请作错误判断及处理。
2. 文件操作完成后,需要将文件关闭,一定要注意,否则会造成文件所占用内存泄露和在下次访问文件时出现问题。
3. 文件关闭后,需要将文件指针指向空,这样做会防止出现游离指针,而对整个工程造成不必要的麻烦,如fp = NULL。FILE * pFile; pFile = fopen ("myfile.txt","w"); if (pFile!=NULL){fputs ("fopen example",pFile); fclose (pFile); }
4. 对于打开进行更新的文件(包括“+”符号的文件),允许输入和输出操作,在写入操作之后的读取操作之前,应刷新(fflush)或重新定位(fseek、fsetpos、rewind)流。在读取操作之后的写入操作之前(每当该操作未到达文件末尾时),应重新定位流(fseek、fsetpos、rewind)。
int fclose(FILE *stream);stream是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流。如果流成功关闭,则该方法返回零。如果失败,则返回 EOF。
freopen
setbuf
setvbuf
fread
fwrite
remove
rename
rewind
fgetpos
fseek
fsetpos
ftell
scanf等格式化输入函数
fflush(stdout);清空缓冲区,getchar()接收换行
int scanf(const char *format, ...);format 是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符 和 format 说明符。format 说明符形式为 [=%[*][width][modifiers]type=],具体讲解如下:
- *,这是一个可选的星号,表示数据是从流 stream 中读取的,但是可以被忽视,即它不存储在对应的参数中。即空读一个数据
- width,这指定了在当前读取操作中读取的最大字符数。
- modifiers,为对应的附加参数所指向的数据指定一个不同于整型(针对 d、i 和 n)、无符号整型(针对 o、u 和 x)或浮点型(针对 e、f 和 g)的大小: h :短整型(针对 d、i 和 n),或无符号短整型(针对 o、u 和 x) l :长整型(针对 d、i 和 n),或无符号长整型(针对 o、u 和 x),或双精度型(针对 e、f 和 g) L :长双精度型(针对 e、f 和 g)
- type,一个字符,指定了要被读取的数据类型以及数据读取方式。
- %i 读入十进制,八进制,十六进制整数 %o 读入八进制整数 %x %X 读入十六进制整数 %n 不消耗任何输入。迄今为止从stdin读取的字符数存储在指定位置。
- scanf的高级用法
int fscanf(FILE *stream, const char *format, …) 从流 stream 读取格式化输入。stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。fscanf(fp, "%s %s %s %d", str1, str2, str3, &year);
fscanf() 可以指定读取的流,scanf() 只能从标准输入流(stdin)读取。即scanf为fscanf的特化
while(scanf("%d",&k)!=EOF){ } 可以按住Ctrl+z,再按enter键就可以手动结束
printf等格式化输出函数
int printf( char * format, ... );返回写入字符总数,如果发生写入错误,将会设置文件错误标志(可通过 ferror() 检测),并返回一个负数。等价于 fprintf(stdout, format, ...)
%(flags)(width)(. prec)type
type:
%u 整数的参数会被转成无符号的十进制数字%o 整数的参数会被转成无符号的八进制数字
%x 整数的参数会被转成无符号的十六进制数字,并以小写abcdef 表示
%X 整数的参数会被转成无符号的十六进制数字,并以大写ABCDEF 表示浮点型数
%e double 型的参数以指数形式打印,有一个数字会在小数点前,六位数字在小数点后,而在指数部分会以小写的e 来表示 %E 与%e 作用相同,唯一区别是指数部分将以大写的E 来表示
%g double 型的参数会自动选择以%f 或%e 的格式来打印,其标准是根据打印的数值及所设置的有效位数来决定。 %G 与%g 作用相同,唯一区别在以指数形态打印时会选择%E 格式。
width:width 为参数的最小长度,若此栏并非数值,而是*符号,则表示以下一个参数当做参数长度。
.prec:
- 正整数的最小位数- 在浮点型数中代表小数位数- 格式代表有效位数的最大值
- 在%s 格式代表字符串的最大长度- 若为×符号则代表下个参数值为最大长度
flags:
- 在给定的字段宽度内左对齐,默认是右对齐(参见 width 子说明符)。
+ 一般在打印负数时,printf ()会加印一个负号,整数则不加任何负号,此旗标会使得在打印正数前多一个正号 (+)。
# 此旗标会根据其后转换字符的不同而有不同含义。当在类型为o 之前 (如%#o),则会在打印八进制数值前多印一个o。而在类型为x 之前 (%#x)则会在打印十六进制数前多印'0x',在型态为e、E、f、g 或G 之前则会强迫数值打印小数点。在类型为g 或G 之前时则同时保留小数点及小数位数末尾的零。
0 当有指定参数时,无数字的参数将补上0。默认是关闭此旗标,所以一般会打印出空白字符。
int snprintf(char *str, int n, char * format [, argument, ...]);其中str为要写入的字符串;n为要写入的字符的最大数目,超过n会被截断;format为格式化字符串,与printf()函数相同;argument为变量。返回值:成功则返回参数str 字符串长度,失败则返回-1,错误原因存于errno 中。
snprintf()可以认为是sprintf()的升级版,比sprintf()多了一个参数,能够控制要写入的字符串的长度,更加安全,只要稍加留意,不会造成缓冲区的溢出。
int fprintf(FILE *stream, const char *format, …) 从流 stream 读取格式化输入。
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。fprintf(fp, "%s %s %s %d", str1, str2, str3, &year);fprintf() 可以指定读取的流,printf() 只能从标准输入流(stdin)读取。即printf为fprintf的特化
get等函数
put等函数
错误处理等函数
clearerr
feof
ferror
perror
(stdlib.h)
rand和srand函数
rand返回(0~32767)
v2 = rand() % 100 + 1; // v2 in the range 1 to 100
v3 = rand() % 30 + 1985; // v3 in the range 1985-2014
想要使用rand()函数产生一个(a,b)区间的数num,可以使用以下两种方式:
(1)num=a+(b-a+1)*rand()/(RAND-MAX+1.0);
(2)a+rand%(b-a+1);
注意公式(1)用的是“/”,而公式(2)是“%”。
由于C语言是利用linear congruential generator作为生成器来生成伪随机数,但是这个生成器生成伪随机数,需要一个“种子”来进行运算。而如果我们仅仅调用rand()函数,而没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。解决办法就是使用srand()函数产生随机种子。
为保证每次产生不同的种子,可以使用time(NULL)和getpid(NULL)的返回值作为srand的参数,以产生不同的种子,因为:
(1)time(NULL)得到每次程序运行的时间,每一次运行程序的时间是不同的。 [[c附件#time和srand,rand生成多个随机数]]
(2)getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。[[c附件#随机数:getpid()函数法:添加srand(getpid(NULL))语句,注意1不要将该语句放到循环中,否则每次执行会出现10个相同的数]]
bsearch和qsort函数
qsort函数:void qsort(void *base, size_t num, size_t size, int (*compar)(const void*, const void*));
函数描述:对数组进行排序。base指向要排序的数组的第一个元素的指针。nitems 由 base 指向的数组中元素的个数。size数组中每个元素的大小,以字节为单位sizeof (int)。compar 用来比较两个元素的函数。无返回值。 1:不稳定排序(基于快速排序的操作)。 2:复杂度num*log2(num)。
int compareMyType (const void * a, const void * b){
if ( *(MyType*)a < *(MyType*)b ) return -1;
if ( *(MyType*)a == *(MyType*)b ) return 0;
if ( *(MyType*)a > *(MyType*)b ) return 1;
}
bsearch函数:void* bsearch (const void* key, const void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
函数描述:key为指针或者地址指向要查找的元素,base 指向进行查找的数组,对 num个 对象的数组执行二分查找,size 指定数组中每个元素的大小。数组的内容应根据 compar 所对应的比较函数升序排序。
函数返回:如果查找成功,该函数返回一个指向数组中匹配元素的指针,为void,要类型转换,否则返回空指针。
特点:
若数组中有多个满足条件的值,则函数可能返回任意一个,不一定是第一个。时间复杂度log2(num)+2。
exit和abort函数
exit和abort都是用来终止程序的函数,都是存在于stdlib中的函数,他们的不同如下:
exit会做一些释放工作:释放所有的静态的全局的对象,缓存,关掉所有的I/O通道,然后终止程序。如果有函数通过atexit来注册,还会调用注册的函数。不过,如果atexit函数扔出异常的话,就会直接调用terminate。 exit(0) 表示程序正常退出 exit⑴/exit(-1)表示程序异常退出
abort:立刻terminate程序,没有任何清理工作。abort();
补充:如果是用c++的话,exit调用的时候,对象还是不会被正确析构的,所以在exit前一定要释放应该释放的资源,特别内核驻留的像共享内存之类
malloc和free
1 | void *calloc(int num, int size); | 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。 | |
---|---|---|---|
2 | void free(void *address); | 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 | |
3 | void *malloc(int num); | 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。 | |
4 | void *realloc(void *address, int newsize); | 该函数重新分配内存,把内存扩展到 newsize。 | |
void * 类型表示未确定类型的指针。C、C++ 规定 void 、* 类型可以通过类型转换强制转换为任何其它类型的指针。 | |||
escription = (char *)malloc( 200 * sizeof(char) ); if( description == NULL ) { fprintf(stderr, "Error - unable to allocate required memory\n"); }else{}; | |||
/* 假设想要存储更大的描述信息 */ description = (char *) realloc( description, 100 * sizeof(char) ); if( description == NULL ) { fprintf(stderr, "Error - unable to allocate required memory\n"); } else |
字符串转换函数
double atof (const char*);把参数 str 所指向的字符串转换为一个浮点数。函数返回转换后的双精度浮点数,如果没有执行有效的转换,则返回零(0.0)。函数会不理会字符串开始之后的空格,直到遇到第一个有效字符,从第一个有效字符开始,到最后一个有效字符为止,函数会将这段字符转换为浮点数。最后一个有效字符之后的字符不会影响该函数。
int atoi (const char*); long atol (const char*); atoll
double strtod (const char*, char*);strtof;strtold; long strtol (const char*, char*, int); unsigned long strtoul (const char*, char**, int);
atoi 函数族是 strtol 函数族的一种特例
atol 胜在易用性,但是不能进行其他进制的解析和错误处理。
函数会忽略字符串刚开始的空格,直到遇到第一个有效字符。
+和-位于第一个字符是被认为是有效字符。最后一个有效字符之后的字符不会影响该函数。
超出所表示的最大范围,将产生未定义的行为。