私信我可以获得word版
目录
gcc
system("pause");
关键字
C99 新增关键字
C11 新增关键字
数据类型
转义序列
字符串常量
const
位运算符
判断语句
循环语句
goto:无条件跳转语句
引用调用函数参数
inline:内联函数
函数重载
初始化数组
数组与指针
枚举
指针
定义:
指针的算术运算
指针数组=指针的数组
数组指针=数组的指针
指针与函数
函数指针:指向函数的指针
指针函数:返回值为指针的函数
两者混用:不常用
函数指针数组
函数与指针的复杂声明:不做要求,一般用typedef代替它
从函数返回指针
回调函数Callback
size_t
字符串
结构体
共用体Union
位域
输入&输出
scanf、printf等函数中的格式占位符(%)
字符输入
Windows、Unix、Mac不同操作系统的换行问题:
文件读写
fopen()、fopen_s
mode的值
fclose( )
fputc()、fputs、fprintf
fgetc、fgets、fscanf
二进制输入和输出
fseek
预处理器
预处理器指令
预定义宏
预处理器运算符
参数化的宏:用来模拟函数
头文件:即扩展名为.h的文件
强制类型转换
错误处理
递归recursion
可变参数:函数带有可变数量的参数
动态内存管理
void*类型:
动态可变长的结构体
内存泄漏
命令行参数/ main(int argc, char *argv[])
排序算法
C语言是一种通用的、面向过程式的多范式计算机程序设计语言。1972年,为了移植与开发UNIX操作系统(即最初用于系统开发语言),Dennis Ritchie在贝尔电话实验室设计开发了C语言。本文帮您在短期内捡回C的绝大部分语法。
gcc -v #检查是否安装了 GCC
gcc 进行 c 语言编译分为四个步骤:
gcc –E hello.c –o hello.i #1.预处理,生成预编译文件(.i 文件)
gcc –S hello.i –o hello.s #2.编译,生成汇编代码(.s 文件)
gcc –c hello.s –o hello.o #3.汇编,生成目标文件(.o 文件)
gcc hello.o –o hello #4.链接,生成可执行文件
编译多个文件:
$ gcc test1.c test2.c -o main.out #gcc [源文件名] -o [目标文件名],
#[源文件名] 和 -o [目标文件名] 的顺序可互换
$ ./main.out
因编译器的原因,生成的.exe文件打开时会一闪而过,从而观察不到其运行的结果,这是因为main()函数结束时,DOS窗口会自动关闭。为了避免这个问题可在 return 0; 前加入:
#include
system("pause"); //暂停函数,请按任意键继续...
char、double、float、int、long、short、enum
unsigned、signed、const
if、else、switch、case、default
for、while、do、break、continue
struct
typedef:由编译器执行解释;而define语句只是简单替换,且由预编译器进行处理
sizeof(算字节数)
return、goto
void
auto |
声明自动变量,是所有局部变量默认的存储类。只用在函数内,即只能修饰局部变量 |
extern |
声明变量或函数:extern int a;,声明可以出现无数次; 而extern int a =0;为定义,定义只能出现在一处 |
register |
定义存储在寄存器中而不是内存RAM中的局部变量,只用于需要快速访问的变量 |
static |
指示编译器在程序的生命周期内保持局部或全局变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁;全局声明的一个static变量或方法可被任何方法调用,只要这些方法出现在跟static变量或方法同一个文件中 |
union |
声明共用体类型 |
volatile |
说明变量在程序执行中可被隐含地改变 |
#define STRING char *
STRING name, sign;//只有name是指针
_Bool
_Complex
_Imaginary
inline
restrict
_Alignas
_Alignof
_Atomic
_Generic
_Noreturn
_Static_assert
_Thread_local
整数类型
char |
1字节 |
-128到127或0到255 |
unsigned char |
1字节 |
0到255 |
signed char |
1字节 |
-128到127 |
int |
2或4字节 |
-32,768到32,767或-2,147,483,648到2,147,483,647 |
unsigned int |
2或4字节 |
0到65,535或0到4,294,967,295 |
short |
2字节 |
-32,768到32,767 |
unsigned short |
2字节 |
0到65,535 |
long |
4字节 |
-2,147,483,648到2,147,483,647 |
unsigned long |
4字节 |
0到4,294,967,295 |
整数:
小数
浮点类型
类型 |
存储大小 |
值范围 |
精度 |
float |
4字节 |
1.2E-38到3.4E+38 |
6位有效位 |
double |
8字节 |
2.3E-308到1.7E+308 |
15位有效位 |
long double |
16字节 |
3.4E-4932到1.1E+4932 |
19位有效位 |
float:
double:
有静态存储持续时间的变量会被隐式初始化为NULL(所有字节的值都是0),其他所有变量的初始值是未定义的
\\ |
\字符backslash |
\' |
'字符ASCII apostrophe |
\" |
"字符ASCII quotation mark |
\? |
?字符 |
\a |
警报铃声alert |
\b |
退格键backspace |
\f |
换页符form feed |
\n |
换行符 |
\r |
回车carriage return |
\t |
水平制表符tab |
\v |
垂直制表符vertical tab |
\nnn |
一到三位的八进制数character with given octal code (1, 2 or 3 digits) 如 char ch = '\101'; 等价于 char ch = 0101; (以0开头的表示八进制) |
\xnn... |
一个或多个数字的十六进制数character with given hex code (1 or more hex digits) 如char ch = '\x41'; 就是用十六进制来表示,它与前面的 \101 是等价 |
下面这三种形式所显示的字符串是相同的:
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
在C语言中没有专门的字符串类型,因此双引号内的字符串会被存储到一个数组中,这个字符串代表指向这个数组起始字符的指针;
而单引号中的内容是一个char类型,是一个字符,这个字符对应的是ASCII表中的序列值。
可以节省空间,避免不必要的内存分配。如:
const doulbe Num = 3.14159; //此时并未将Pi放入ROM中 ......
double i = Num; //此时为Pi分配内存,以后不再分配!
double j = Num; //没有内存分配
#define NUM 3.14159 //常量宏
double I= NUM; //编译期间进行宏替换,分配内存
double J = NUM; //再进行宏替换,又一次分配内存!
& |
与 |
| |
或 |
^ |
异或 |
~ |
按二进制位取反 |
<< |
二进制左移:左边的二进制位丢弃,右边补0 |
>> |
二进制右移:正数左补0,负数左补1,右边丢弃 |
利用异或^来swap两个数的值(只适用于整型变量,不能用于浮点型变量):
//仅用一行代码实现的方法:
a^=b^=a^=b;
//其等价于:
a=a^b;
b=a^b;
a=a^b;
//用三次加减法也可以(但都不如临时变量法):
#define SWAP1(x,y) {x=x+y;y=x-y;x=x-y;}
判断一个整数是否是2的整数次幂:
int func(int num){
return ((num > 0) && ((num & (num - 1)) == 0));//2的n次幂大于0
}
if( ){
}
else if( ){
}
else {
}
switch(表达式){//swich也可以像if那样嵌套
case 常量表达式1:语句1;
case 常量表达式2:语句2;
...
default:语句n+1;
}
while( ){
}
for (init; condition; increment){
}
do{
}while( );
goto label;
..
label: statement;
void swap(int *x, int *y);
int main (){
int a = 100;
int b = 200;
swap(&a, &b); //&取地址
}
指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。
其用来消除函数调用时的时间开销,通常用于频繁执行的、小内存空间的函数。
使用内联函数的时候要注意:
可以出现参数列表不同但是函数名相同的函数
char a[]="runoob"; //这样赋值之后在结尾会自动加上'\0'。
char a1[]={'r','u','n','o','o','b'}; //这样赋值是正好的6个空间不会自动加上'\0'
多维数组的两种初始化方法:
int a[3][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7}
};
int a[3][4] = {0,1,2,3,4,5,6,7};
将多维数组当作参数的时候,可省略第一维的,但是不能省略其他更高维的大小。因为事实上,设有数组int a[m][n],如果要访问a[i][j]的值,编译器的寻址方式为:
&a[i][j]=&a[0][0]+i*sizeof(int)*n+j*sizeof(int); // 注意n为第二维的维数
实际上数组名是指向该数组a最开始的一个元素的const的指针,所以:
但有特殊性:
int size = sizeof(a)/sizeof(a[0]);//是数组a的长度
二维数组传递给函数的方法
void print_b(int (*a)[5])//指向一个有5个元素一维数组的指针
void print_c(int *a)
enum DAY{//DAY为枚举名
MON=1, TUE, WED, THU, FRI, SAT, SUN//枚举元素
};
enum DAY day;
//或者
enum DAY{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
//或者
enum{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
第一个枚举成员的默认值为整型的0
没有指定值的枚举元素,其值为前一元素加1
每个枚举元素可以看作为一个整形变量的宏定义,其仅有的作用是简化了多行#define代码
int main (){
int var_runoob = 10;
int *p==NULL; //定义指针变量,并赋值为空指针
p = &var_runoob;
printf("var_runoob变量的地址:%p\n", p);
printf("var_runoob变量的值:%d\n", *p);
}
多级指针=多维指针:指向指针的指针
失控指针=野指针:没有被初始化的指针
悬空指针:即free()后指针不赋NULL。使用free()释放那片内存时,指针依旧存放着那片内存的地址,也就是依旧指向那片内存,但这片内存已经释放,不可访问,这时若不小心使用了这个指针,便会内存错误。一定要在释放内存后,把指向这片内存的指针都赋值为NULL!
对于int *(*ptr)[4];:指针的类型是int*(*)[4],指针所指向的的类型是int*()[4]
指针的每一次递增/减,它其实会指向下/前一个元素的存储单元。
指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,如int是4个字节
若p1和p2指向两个相关的变量,如同一个数组中的不同元素,则可对p1和p2进行大小比较
const char *names[] = { "Zara Ali", "Hina Ali", "Sara Ali", };//[]的优先级高于*
int a[5]={ 1,2,3,4,5 };
int (*prt)[5];//步长为5的数组指针,即prt指向的数组里有5个元素
prt=&a; //prt为数组a的地址,*prt表示数组a本身
prt[0]; //表示数组首元素的地址
*prt[0]; //表示数组的首元素的值,即1
**prt; //表示数组的首元素的值,即1
*prt[1]; //表示指向数组的下一行元素的首元素的值,但是a是一维数组,只有一行,所以指向的地址中的值不确定。数组指针加1,步长为所指向数组的列数,即为下一行的首地址
typedef int arr[3];
int main() {
arr b = {1, 2, 3};
int (*a)[3] = &b;//一个指向3个元素的一维数组指针
arr *c = a;
for (int i = 0; i < 3; ++i) {
printf("%d ", (*a)[i]);
}//输出结果:1 2 3
}
int (* uuf) [3][4]; //一个指针,指向3X4的int数组
int (* uof[3]) [4]; //一个具有3个元素的数组,每个元素是一个指向具有4个元素的int数组的指针
函数名:
一个函数在编译之后,会占据一部分内存,而它的函数名,就是这段函数的首地址(即一个函数的入口地址)。在使用中函数名会被转换为指向这个函数的指针,指针的值就是函数的入口地址
直接引用函数名等效于在函数名上应用&运算符:
两种方法都会得到指向该函数的指针。C语言规定函数名会被转换为指向这个函数的指针,除非这个函数名作为&操作符或sizeof操作符的操作数(注意:函数名用于sizeof的操作数是非法的)。也就是说f=test中test被自动转换为&test,而f=&test;中已经显示使用了&test,所以test就不会再发生转换了。
指向函数的指针必须初始化,或者具有0值,才能在函数调用中使用。
与数组一样:
int f(int a, int b){
return a+b;
}
int main(){
int (*p)(int,int);
p=f;//这里&f和f的值和类型都一样,用哪个无所谓
int a=(*p)(5,7); //通过函数指针调用函数
int b = (&f)(5,7);
int c = (*f)(5,7);
int d = f(5,7);
printf("%d %d %d %d\n",a,b,c,d);//由*f==*&f==f==&f知,输出12 12 12 12
}
void test( ){
printf("test called!/n");
}
int main( ){
printf("%p/n", test);//输出004013EE004013EE004013EE
printf("%p/n", &test);//输出004013EE004013EE004013EE
printf("%p/n", *test);//输出004013EE004013EE004013EE
}
*test 可以认为由于 test 已经被转换成了函数指针, 指向这个函数,所以 *test 就是取这个指针所指向的函数名,而又根据函数名会被转换指向该函数的指针的规则,这个函数也转变成了一个指针,所以 *test 最终也是一个指向函数 test 的指针。即*test --> *(&test) --> test --> &test。
C 语言的库函数中有很多都是指针函数,如一些字符串处理函数:
char *strcat( char *dest, const char *src );
char *strcpy( char *dest, const char *src );
char *strchr( const char *s, int c );
char *strstr( const char *src, const char *sub );
注意函数的返回值不仅仅局限于指向变量的指针,也可以是指向函数的指针。
*int (*function(int)) (double*, char);:首先来看function(int),将function声明为一个函数,它带有一个int型的形式参数,这个函数的返回值为一个指针,正是函数指针int(*)(double*,char);这个指针指向一个函数,此函数返回int型并带有两个分别是double*型和char型的形参。
如果使用typedef可以将这个声明简化:
typedef int (*ptf) (double*, char);
ptf function(int);
另一个例子:
void (*signal (int sig, void (*func) (int siga))) (int siga);
现在要分析的是signal,因为紧邻signal的是优先级最高的括号,首先与括号结合,所以signal为一个函数,括号内为signal的两个形参,一个为int型,一个为指向函数的指针。接下来从向左看,*表示指向某对象的指针,它所处的位置表明它是signal的返回值类型,现在可以把已经分析过的signal整体去掉,得到void(*)(int siga)。又是一个函数指针,这个指针与signal形参表中的第二个参数类型一样,都是指向接受一个int型形参且不返回任何值的函数的指针。
用typedef将这个声明简化:
typedef void (*p_sig) (int);
p_sig signal(int sig, p_sig func);//signal是C的库函数,在signal.h中定义,用来处理系统中产生的信号
假设现在有一个文件处理程序,通过一个菜单按钮来选择相应的操作(打开文件,读文件,写文件,关闭文件)。这些操作都实现为函数且类型相同,分别为:
void open();
void read();
void write();
void close();
typedef void (*PF) ( );//定义一个函数指针类型的别名PF
PF file_options[ ] = {//若不用typedef,则是void (*file_options[ ]) ( );
&open, &read, &write, &close//把以上4种操作取地址放入一个数组中
};
PF* action = file_options;//一个函数指针类型的指针action并初始化为函数指针数组的第一个元素
对指针action进行下标操作可调用数组中的任一操作,如action[2]()会调用write操作
int *(*(*fp)(int)) [10];//是函数指针,该函数返回一个指向数组的指针,此数组有10个int*型元素
阅读步骤:
1.从未定义的变量名开始阅读:fp
2.往右看,什么也没有,遇到了),因此往左看,遇到一个*:一个指向某对象的指针
3.跳出括号,遇到了(int):一个带一个int参数的函数
4.向左看,发现一个*:(函数)返回一个指向某对象的指针
5.跳出括号,向右看,遇到[10]:一个10元素的数组
6.向左看,发现一个*:一个指向某对象指针
7.向左看,发现int:int类型
int *(*p(int))[3];:从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。
如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数:
int * myFunction() { . . . }
另外,C不支持在函数外返回局部变量的地址,除非定义局部变量为static变量。因为局部变量是存储在内存的栈区内,当函数调用结束后,局部变量所占的内存地址便被释放了,因此当其函数执行完毕后,函数内的变量便不再拥有那个内存地址,所以不能返回其指针。而static 变量的值存放在内存中的静态数据区,不会随着函数执行的结束而被清除,故能返回其地址。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>//要生成和返回随机数的函数
int * getRandom( ){
static int r[10];
srand( (unsigned)time( NULL ) );//设置种子
for (int i = 0; i < 10; ++i) {
r[i] = rand();
}
return r;
}
int main (){
int *p;
p = getRandom();
for (int i = 0; i < 10; i++){
printf("*(p + [%d]) : %d\n", i, *(p + i));
}
}
函数指针变量可作为某个函数的参数来使用,回调函数就是一个通过函数指针调用的函数。
在A函数中将参数与C函数传递给B函数,B函数调用C函数;B函数调用的动作称为回调,C函数称为回调函数。
回调函数的作用完全可以通过普通函数来达到,其实回调函数最大的意义在于解耦,降低了代码之间的耦合度。
size_t类型在C语言标准库函数原型使用的很多,数值范围一般是要大于int和unsigned。
但凡不涉及负值范围的表示size取值的,都可以用size_t,如array[size_t]。
size_t在stddef.h头文件中定义。
在其他常见的宏定义以及函数中常用到有:
1,sizeof运算符返回的结果是size_t类型;
2,void *malloc(size_t size)...
字符串””实际上是使用空字符\0结尾的一维字符数组;在使用不定长数组初始化字符串时默认结尾为‘\0’,如char greeting[] = "Hello"
strcpy(s1,s2):copy字符串s2到s1
strcat(s1,s2):catenate字符串s2到s1的末尾
strcmp(s1,s2):若s1和s2相同,返回0;若s1
strchr(s,c):返回一个指针,指向字符串s中字符c的第一次出现的位置
strstr(s1,s2):返回一个指针,指向字符串s1中字符串s2的第一次出现的位置。
strlwr: string lowercase
strupr: string uppercase
strlen(s):返回s的length
strlen是函数,sizeof是运算操作符,二者得到的结果类型为size_t,即unsignedint类型。
sizeof计算的是变量的大小,不受字符\0影响;
而strlen计算的是字符串的长度,以\0作为长度判定依据,\0本身不计算在内
char *str1 = "asdfgh";//str1是字符指针变量
char str2[] = "asdfgh";
char str3[8] = {'a', 's', 'd'};
char str4[] = "as\0df";
执行结果:
sizeof(str1) = 4; strlen(str1) = 6;// sizeof获得指针str1所占地址空间,32位操作系统对应4字节
sizeof(str2) = 7; strlen(str2) = 6;//sizeof获得该数组所占内存空间大小,包括字符串结尾的\0
sizeof(str3) = 8; strlen(str3) = 3;
sizeof(str4) = 6; strlen(str4) = 2;//str4是常量字符数组,sizeof得到字符总数即6
struct tag {
member-list
...
}(variable-list);
struct tag variable;
struct student stu[] = {{...},{...},{...}};
成员访问运算符:.
使用指向该结构的指针访问结构的成员:必须使用->
可以对指针解引用再访问,如*struct_pointer.title
结构体中成员变量分配的空间:
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。
union tag{
member definition;
...
} [one or more union variables];
成员访问运算符:.
判断机器是大端机还是小端机:
union{
char str;
int data;
};
data=0x01020304;
if(str==0x01){
cout<< "此机器是大端!"<<endl;
}
else if(str==0x04){
cout<<"此机器是小端!"<<endl;
}
else{
cout <<" 暂无法判断此机器类型!"<<endl;
}
字:"字"由若干个字节构成,字的位数叫做字长,不同的机器有不同的字长。例如一台8位机,它的1个字就等于1个字节,字长为8位。字是计算机进行数据处理和运算的单位。
一个汉字占几个字节的存储空间,要看汉字的编码格式
字节对齐原则:
【1】数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
【2】结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
【3】结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
原则一:结构体中元素按照定义顺序存放到内存中,但并不是紧密排列。从结构体存储的首地址开始 ,每一个元素存入内存中时,它都会认为内存是以自己的宽度来划分空间的,因此元素存放的位置一定会在自己大小的整数倍上开始。
原则二: 在原则一的基础上,检查计算出的存储单元是否为所有元素中最宽的元素长度的整数倍。若是,则结束;否则,将其补齐为它的整数倍。
在结构内,可以定义变量的宽度来告诉编译器,您将只使用这些字节。如:
struct{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;//status变量将占用4个字节的内存空间,但是只有2位被用来存储值
位域是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示
位域的宽度不能超过它所依附的数据类型的长度
位域的使用和结构成员的使用相同:位域变量名.位域名、位域变量名->位域名
位域允许用各种格式输出
定义位域时,各个成员的类型最好保持一致,比如都用char,或都用int,不要混合使用,这样才能达到节省内存空间的目的,因为不同的类型不能写到一个字节里面
超出范围并不是直接丢弃,而是保留对应的位数的值
标准文件 |
文件指针 |
设备 |
标准输入 |
stdin |
键盘 |
标准输出 |
stdout |
屏幕 |
标准错误 |
stderr |
您的屏幕 |
%d:十进制整数,short/int
%ld:long
%lld:long long
%u:读入一个无符号十进制整数
%o:八进制整数,以 0 开头
%x,%X:十六进制整数,以 0x 开头
%i:十进制,八进制,十六进制整数
%f:浮点数。printf()只会看到双精度数,printf的%f格式总是得到double,所以在printf()中使用%f跟%lf的输出显示效果是一样的。输出默认都是6位小数,但可自己控制,如%.10lf就输出10位小数。所以一般double类型的占位符可以用%lf。
用%20.15f的格式声明,指定输出的数据占20列,其中包括15位小数
用%-m.nf:在m.n前加一个负号,其作用与%m.nf形式作用基本相同,但当数据长度不长过m时,数据向左靠,右端补空格
但对scanf而言,double必须用%lf,float必须用%f,不能混用
%fL:长
%e:科学计数
%g:小数或科学计数
%a,%A:浮点值(仅C99有效)
%f,%F,%e,%E,%g,%G:实数,可以用小数形式或指数形式
%c:char
%s:字符串,遇空格、制表符或换行符结束
%%:%符号
%[]:扫描字符集合
%n:至此已读入值的等价字符数
%p:指针
scanf():有返回值且为int型,当发生错误时立刻返回EOF。其返回的值为:正确按指定格式输入变量的个数,即能正确接收到值的变量个数。
int getchar(void):从屏幕读取下一个可用的字符,并返回一个整数。这个函数在同一个时间内只会读取一个单一的字符。您可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c):把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。您可以在循环内使用这个方法,以便在屏幕上输出多个字符。
char *gets(char *s):从stdin读取一行到s所指向的缓冲区,直到一个换行符或EOF。
读取的换行符被转换为null值,做为字符数组的最后一个字符,来结束字符串。
gets()不安全是因为未指定缓冲区大小,故可用fgets();
linux系统下:不支持gets与puts,需用fgets和fputs
int puts(const char *s) 函数把字符串s和一个尾随的换行符写入到stdout。
char* fgets(char *buf, int bufsize, FILE *stream):
buf:字符型指针,指向用来存储所得数据的地址。
bufsize:整形数据,指明缓冲区的大小,拷贝到buf地址的最大字符数量。最多只能读入bufsize-1个字符。读入结束后,系统将自动在最后加'\0',
stream:指明输入流的FILE对象的指针,stdin可以作为参数,表示从标准输入读取。
返回值:成功,则函数返回buf。
如果当尝试读取一个字符时遇到了文件结尾,则eof被置位(feof),如果还没有成功读入任何一个字符就遇到了文件结尾,那么就会返回null,buff中的内容保持不变。如果读取错误发生,那么errorindicator(ferror)被置位,还是返回null。
我们平时可以这么使用:fgets(str, sizeof(str), stdin);
举例:
int i;
char c;
scanf("%d%c", &i,&c);//这时候变量c中存储的往往不是你想输入的字符,而是一个空格
然后我们又会这样来写:
int i;
char c;
scanf("%d", &i);
scanf("%c", &c);//这时我们发现,根本没有输入字符C的机会,因为输入流是有缓冲区的,我们输入的字符存储在那,然后再赋值给我们的变量
我们可以这样改:
int i;
char c;
scanf("%d", &i);
while((c=getchar())==' ' || c=='\n');//这个办法是一直读取,读到没有空格和换行就跳出循环
c = getchar();
但是有一个更好的解决办法:
int i;
char c;
scanf("%d%[^' '^'\n']", &i, &c);//用正则表达来控制输入格式为非空格非换行
在输入时注意格式对应:
scanf("a=%d",&a);
scanf("x=%f",&x);
scanf("c1=%c",&c1);
//正确的输入应为:a=1x=1.2c1=3
应用:
(1)微软的MS-DOS和Windows用“回车CR('\r')”和“换行LF('\n')”两个字符作为换行符
(2)Windows系统里面,每行结尾是回车+换行(CR+LF),即“\r\n”
(3)Unix系统里,每行结尾只有换行LF,即“\n”
(4)Mac系统里,每行结尾是回车CR即'\r'
Mac OS 9 以及之前的系统的换行符是CR,从Mac OS X(后来改名为“OS X”)开始的换行符是LF即‘\n',和Unix/Linux统一了。
影响:
(1)一个直接后果:Unix/Mac系统下的文件在Windows里打开后,所有文字会变成一行
(2)Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号
(3)Linux保存的文件在windows上用记事本看的话会出现黑点。
可以相互转换:
(1)在linux下,命令unix2dos是把linux文件格式转换成windows文件格式,命令dos2unix是把windows格式转换成linux文件格式。
(2)在不同平台间使用FTP软件传送文件时,在ascii文本模式传输模式下,一些FTP客户端程序会自动对换行格式进行转换.经过这种传输的文件字节数可能会发生变化
(3)如果你不想ftp修改原文件,可以使用bin模式(二进制模式)传输文本。
(4)一个程序在windows上运行就生成CR/LF换行格式的文本文件,而在Linux上运行就生成LF格式换行的文本文件。
FILE *fopen(const char *filename, const char *mode);
创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型FILE的一个对象,类型FILE包含了所有用来控制流的必要的信息。该函数可能执行失败,返回值是NULL,安全起见必须对返回值进行合法性判断。
在 Visual Studio 2015 开发环境中运行下列代码, 会提示 fopen 函数不安全:
FILE *filePointer = NULL;//申明文件指针
filePointer = fopen("C:/Users/Administrator/Desktop/test.txt", "a+");//路径参数用正斜杠("/")
fputs("The text was added to the file by executing these codes.", filepointer);
fclose(filepointer);
错误提示:错误 C4996 'fopen': This function or variable may be unsafe. Consider using fopen_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
解决办法,在代码首行写上:
#define _CRT_SECURE_NO_WARNINGS//定义宏忽略警告, 即可忽略安全警告, 生成运行
在新版的VS编译环境中提示fopen不安全,推荐用fopen_s代替。fopen_s和fopen的区别:
errno_t fopen_s( FILE** pFile, const char *filename, const char *mode );
FILE *fopen(const char *filename, const char *mode)
fopen_s有三个参数,第一个参数是二级指针,用于存放文件流指针地址,其他同fopen一致。
用fopen:
FILE* fp=NULL;
fp=fopen("\\123.txt","w+");
而用fopen_s:
FILE* fp=NULL;
fopen_s(&fp,"\\123.txt","w+");
r |
打开一个已有的文本文件,允许读取文件。 |
w |
打开一个文本文件,允许写入文件。若文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入。若文件存在,则会被截断为零长度,重新写入 |
a |
打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ |
打开一个文本文件,允许读写文件。 |
w+ |
打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。如果文件已存在,则文件会被截断为零长度, |
a+ |
打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式 |
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
r+和w+的区别;
1.模式r+找不到文件不会自动新建,而w+会;
2.模式r+打开文件后,不会清除文件原数据,若直接开始写入,只会从起始位置开始进行覆盖,而w+会直接清零后,再开始读写;
模式的合法性说明:
不能大写,只能小写,且rb+和r+b都合法,但br+和+rb等都非法,w和a也是一样;
模式w的自动新建文件是有条件的:只有对应路径存在(即文件所在的文件夹存在),文件不存在才会新建,否则不会新建,返回NULL
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零;
如果关闭文件时发生错误,函数返回EOF,EOF是一个定义在头文件stdio.h中的常量。
这个函数会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。
int fputc( int c, FILE *fp );//把c的字符值写入到fp所指向的输出流中。若写入成功,返回写入的字符;若发生错误,则会回EOF。
int fputs(const char *s, FILE *fp);//把字符串s写入到fp所指向的输出流中。若写入成功,返回一个非负值;若发生错误,则返回EOF。
int fprintf(FILE *fp,const char *format, ...);//把一个字符串写入到文件中
FILE *fp = NULL;
fp = fopen("/tmp/test.txt", "w+");//需有可用的 tmp 目录,若不存在该目录,则需在您的计算机上先创建该目录。/tmp 一般是 Linux 系统上的临时目录,如果在 Windows上运行,则需修改为本地环境中已存在的目录,如C:\tmp、C:\\temp\\test.txt等。
fprintf(fp, "This is testing for fprintf...\n");
fputs("This is testing for fputs...\n", fp);
fclose(fp);
上面的代码会在/tmp目录中创建一个新的文件test.txt,并使用两个不同的函数写入两行。
int fgetc( FILE * fp );//从fp所指文件中读取一个字符。返回值是读取的字符,若有错误则返回EOF
char *fgets( char *buf, int n, FILE *fp );//从fp所指向的输入流中读取n-1个字符,把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。如果这个函数在读取最后一个字符之前就遇到一个换行符 '\n' 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。
int fscanf(FILE *fp, const char *format, ...)//从文件中读取字符串,但在遇到第一个空格和换行符时,它会停止读取。
实例
FILE *fp = NULL;
char buff[255];
fp = fopen("/tmp/test.txt", "r");
fscanf(fp, "%s", buff);//fscanf()只读取了This,因为它在后边遇到了一个空格
printf("1: %s\n", buff );
fgets(buff, 255, (FILE*)fp);//fgets()读取剩余的部分,直到行尾
printf("2: %s\n", buff );
fgets(buff, 255, (FILE*)fp);//fgets()完整地读取第二行
printf("3: %s\n", buff );
fclose(fp);
当上面的代码被编译和执行时,它会读取上一部分创建的文件,产生下列结果:
1: This
2: is testing for fprintf...
3: This is testing for fputs...
size_t fread(void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写 - 通常是数组或结构体。
fseek 可以移动文件指针到指定位置读,或插入写:
int fseek(FILE *stream, long offset, int whence);//设置当前读写点到 offset 处, whence 可以是 SEEK_SET,SEEK_CUR,SEEK_END 这些值决定是从文件头、当前点和文件尾计算偏移量 offset
如, 相对当前位置往后移动一个字节:fseek(fp,1,SEEK_CUR);
如果你要往前移动一个字节,直接改为负值就可以:fseek(fp,-1,SEEK_CUR)。
执行以下实例前,确保当前目录下 test.txt 文件已创建:
FILE *fp = NULL;
fp = fopen("test.txt", "r+");//确保test.txt文件已创建
fprintf(fp, "This is testing for fprintf...\n");
fseek(fp, 10, SEEK_SET);
if (fputc(65,fp) == EOF) {
printf("fputc fail");
}
fclose(fp);//执行结束后,test.txt文件内容为:This is teAting for fprintf...
注意:只有用r+模式打开文件才能插入内容,w或w+模式都会清空掉原来文件的内容再来写,a或a+模式即总会在文件最尾添加内容,哪怕用fseek()移动了文件指针位置。
C预处理器只不过是一个文本替换工具而已。
所有的预处理器命令都是以井号(#)开头。下面列出了所有重要的预处理器指令:
#define |
定义宏 |
#include |
包含一个源代码文件 |
#undef |
取消已定义的宏 |
#ifdef |
如果宏已经定义,则返回真 |
#ifndef |
如果宏没有定义,则返回真 |
#if |
如果给定条件为真,则编译下面代码 |
#else |
#if 的替代方案 |
#elif |
如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif |
结束一个 #if……#else 条件编译块 |
#error |
当遇到标准错误时,输出错误消息 |
#pragma |
使用标准化方法,向编译器发布特殊的命令到编译器中 |
ANSI C定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。
__DATE__ |
当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。 |
__TIME__ |
当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。 |
__FILE__ |
这会包含当前文件名,一个字符串常量。 |
__LINE__ |
这会包含当前行号,一个十进制常量。 |
__STDC__ |
当编译器以 ANSI 标准编译时,则定义为 1。 |
宏延续运算符:\:当宏太长,一个单行容纳不下
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
字符串常量化运算符:#:把一个宏的参数转换为字符串常量。如:
#include <stdio.h>
#define message_for(a, b) printf(#a " and " #b ": We love you!\n")
int main(void){
message_for(Carole, Debra);
}//会输出Carole and Debra: We love you!
标记粘贴运算符:##:合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void){
int token34 = 40;
tokenpaster(34);
}//输出token34 = 40
defined()运算符:用在常量表达式中的,用来确定一个标识符是否已经使用#define定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void){
printf("Here is the message: %s\n", MESSAGE);
}//输出Here is the message: You wish!
#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))//宏名称和左圆括号之间不允许有空格
int main(void){
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
}//输出Max between 20 and 10 is 20
如果一个头文件被引用两次,编译器会处理两次头文件的内容,这可能会产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中(即所谓包装器):
#ifndef HEADER_FILE
#define HEADER_FILE
#include …
#endif//当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义
<>:表示系统自带的库
" ":表示用户自定义的库,若自定义的库里面没这个文件系统会自动查找自带的库,如果还是没有报错
隐式转换:
在发生错误时,会设置一个错误代码errno,该错误代码是全局变量。您可在errno.h中找到各种错误代码。所以,可通过检查返回值,然后根据返回值决定采取哪种动作。
perror():显示您传给它的字符串,后跟一个冒号、一个空格和当前errno值的文本表示形式
strerror():返回一个指针,指针指向当前errno值的文本表示形式
#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno;//开发人员应在程序初始化时,把errno设置为0(即程序没错误),这是个好的编程习惯
int main () {
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb");//模拟一种错误情况:尝试打开一个不存在的文件
if (pf == NULL){
errnum = errno;
fprintf(stderr, "错误号: %d\n", errno);//注意:用stderr文件流来输出所有错误
perror("通过 perror 输出错误");
fprintf(stderr, "打开文件错误: %s\n", strerror(errnum));
}
else{
fclose (pf);
}
}
输出结果:
错误号: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory
程序退出状态:
通常,程序成功执行完正常退出时会带有值EXIT_SUCCESS(是宏,被定义为0);
如果程序中存在错误,当您退出程序时,会带有状态值EXIT_FAILURE(被定义为-1)。
#include <stdio.h>
#include <stdlib.h>
int main () {
int dividend = 20;
int divisor = 5;
int quotient;
if(divisor == 0){//在进行除法运算前先检查除数是否为零
fprintf(stderr, "除数为 0 退出运行...\n");
exit(EXIT_FAILURE);//也可以写成exit(-1);
}//divisor==0的时候输出结果:除数为0退出运行…
quotient = dividend / divisor;
fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
exit(EXIT_SUCCESS);//也可以写成exit(0);
}//输出结果:quotient变量的值为:4
递归,就是递(一层一层地调用),归(一层一层地返回)。
递归应用:
数据结构——栈,可以用递归来实现。
有些算法,如搜索与回溯算法,广度优先搜索算法,分治(二分),都用到递归。
电脑空间大致分Heap(堆)和Stack(栈)两种。
栈是用于函数的空间。电脑调用一个函数,就会使用一层栈;相反,电脑中一个函数结束(return),就会释放这一层栈,连同在这层栈(这个函数)中定义的所有东西。
不在栈中的,应该就在堆中。(这就是定义全区变量与局部变量的用处)
如果调用太多层栈(太多个函数),电脑就会暴空间!
所以说,调用递归函数,就会一层一层地压栈,电脑就会暴空间!
#include <stdio.h>
#include <stdarg.h>//定义了va_list、va_start、va_arg、va_end
double average(int num,...) {//省略号(...)前的参数int:代表要传递的可变参数的总数
va_list valist;
va_start(valist, num);//为num个参数初始化valist
double sum = 0.0;
for (int i = 0; i < num; i++) {//访问所有赋给valist的参数
sum += va_arg(valist, int);
}
va_end(valist);//清理为valist保留的内存
return sum/num;
}
int main() {
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));//返回平均值
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
va_list:
用来保存宏va_start、va_arg和va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明 va_list 类型的一个对象,定义: typedef char * va_list;
va_start:
访问变长参数列表中的参数之前使用的宏,它初始化用va_list声明的对象,初始化结果供宏va_arg和va_end使用;void va_start(va_list arg_ptr, prev_param);
va_arg:
展开成一个表达式的宏,该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改用va_list声明的对象,从而使该对象指向参数列表中的下一个参数;type va_arg(va_list arg_ptr, type);
va_end:
该宏使程序能从变长参数列表用宏va_start引用的函数中正常返回。void va_end(va_list arg_ptr);
va:variable-argument(可变参数)
#include
#include
#include
//该函数至少有一个整数参数,第二个参数也是整数,是可选的。函数只是打印这两个参数的值
int demo(char *msg, ... ){//ANSI标准形式的声明方式,括号内的省略号表示可选参数
va_list valist;//定义保存函数参数的结构,是指向参数的指针
va_start(valist, msg);//valist指向传入的第一个可选参数,msg是最后一个确定的参数
int argno = 0;//纪录参数个数
char *para;//存放取出的字符串参数
while (1) {
para = va_arg(valist, char *);//取出当前的参数,类型为char *
if (strcmp(para, "/0") == 0)//采用空串指示参数输入结束
break;
printf("Parameter #%d is: %s/n", argno, para);
argno++;
}
va_end(valist);//将valist置为NULL
return 0;
}
int main(void) {
demo("DEMO", "This", "is", "a", "demo!" ,"333333", "/0");
}
存储结构:
#include
void debug_arg(unsigned int num, ...) {
unsigned int i = 0;
unsigned int *addr = #
for (i = 0; i <= num; i++) {
printf("i=%d,value=%d\r\n", i, *(addr + i));//从左往右依次取出传递进来的参数,类似于出栈过程
}
}
int main(void) {
debug_arg(3, 66, 88, 666);
}
// 64 位机器用 8 字节对齐, 32 位 4 位对齐
#ifdef X64
#define t long long
#else
#define t int
#endif
//VA_LIST套宏中可以使用,用来改变INTSIZEOF中t的类型
void test(int a, double b, char* c){//固定参数实验
char *p = (char*)&a;
//因为&a = void 类型 需要转换,void * =&a 不需要转换但是使用时要转换
printf("%p %p %p\n", &a, &b, &c);
//观察地址变化
printf("%p %s",(p+8),*(char**)(p+8+8));//64位机器时加8内存大小8字节对齐
return;
}
void test1(char* s,char *st,...){//可变参数实验
char *ppt =(char*)&s;
//printf("%p %p %p %p,",ppt,&s,&st,(char*)ppt+8);
printf("%p %p %p %p\n", ppt, &s, &st, ppt + 8);
printf("%s\n", *(char**)(ppt+4));
printf(" %d\n",*(int*)(ppt + 4+4));//当是X64就加8 X86就加4因为内存对齐规则
return;
}
int main() {
char *p = "Hello world";
test1("111","eee",45234,23);
//test(2, 2.2, "Hello world");x
void *s = &p;
printf("%s", *(char**)s);
}
在
void *calloc(int num, int size);
在内存中动态地分配num个长度为size字节的连续空间,并将每个字节都初始化为0。
void free(void *address);
该函数释放address所指向的内存块,释放的是动态分配的内存空间,无返回值。
void *malloc(int num);
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。mess+allocate整块的分配
void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展或者减小到newsize。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char name[100];
char *description;
strcpy(name, "Zara Ali");
description = (char *)malloc( 30 * sizeof(char) );//也可用calloc():只需把malloc替换为calloc即可
if(description == NULL) {
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else{
strcpy(description, "Zara ali a DPS student in class 10th");
}
//假设您想要存储更大的描述信息
description = (char *) realloc( description, 100 * sizeof(char) );
if(description == NULL) {
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else {
strcat(description, "She is in class 10th");
}//若不重新分配额外内存,strcat()函数会错误,因为存储description时可用的内存不足
printf("Name = %s\n", name );
printf("Description: %s\n", description );
free(description);
}
当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议您在不需要内存时,都应该调用函数 free() 来释放内存。
或者,您可以通过调用函数 realloc() 来增加或减少已分配的内存块的大小。
表示未确定类型的指针。C、C++规定void*类型可通过类型强制转换为任何其它类型的指针。
对于 void 指针,GNU 认为 void * 和 char * 一样,所以以下写法是正确的:
description = malloc( 200 * sizeof(char) );
但按ANSI(American National Standards Institute)标准,需对void指针进行强制转换:
description = (char *)malloc( 200 * sizeof(char) );
同时,按照ANSI标准,不能对void指针进行算法操作:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
// ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
int *pint;
pint++; //ANSI:正确
对于我们手动分配的内存,在C语言中是不用强制转换类型的:
description = malloc( 200 * sizeof(char) ); // C 语言正确
description = malloc( 200 * sizeof(char) ); // C++ 错误,因为C++ 是强制要求的
typedef struct{//定义该结构体,只占用4字节的内存,name不占用内存
int id;
char name[0];//一个结构体中只能有一个可变长的成员,并且该成员必须是最后一个成员
}stu_t;
stu_t *s = NULL;//定义一个结构体指针
s = malloc(sizeof(*s) + 100);//sizeof(*s)得到4,4字节给id成员用;100字节属于name成员
s->id = 1010;
strcpy(s->name,"hello");
直接使用原来的指针变量接收realloc的返回值是可能存在内存泄漏的。例如以下语句:
description = (char *) realloc( description, 100 * sizeof(char) );
若realloc函数执行失败,description原先所指向的空间不变,realloc函数返回NULL。此时description的值被赋为NULL,但原先指向的空间未被释放,造成了内存泄漏。
命令行参数是使用main()函数参数来处理的。
并不一定这样写,只是约定俗成罢了,亦可以写成下面这样或任意你喜欢的名字:
int main( int test_argc, char *test_argv[] )
argc:为参数个数;
argv:是指针数组,下标从0开始,第一个存放的是可执行程序的文件名字,然后依次存放传入的参数。多个命令行参数之间用空格分隔,但是如果参数本身带有空格,那么传递参数的时候应把参数放置在双引号""或单引号''内部。
Linux下我们可使用getopt和getopt_long来对命令行参数进行解析。
HelloWorld.c :
#include
int main(int argc, char *argv[]){
printf("可执行程序 %s ,参数个数为[%d], 运行输出:[%s]\n",argv[0],argc,argv[1]);
return 0;
}
运行程序:
gcc HelloWorld.c
./a.out Hello,World!
可执行程序 ./a.out ,参数个数为[2], 运行输出:[Hello,World!]