一、基础知识
1.main函数
#include<stdio.h>
int main()
{
printf(“Hello World!\n”);
return 0;
}
#include将头文件添加展开,main函数为C语言程序的入口点,printf函数为格式化输出函数。
2.注释编译
/*注释*/
程序执行过程:预处理、编译、链接、执行
源文件.c 头文件.h 目标文件.obj 执行文件.exe
在宿主环境中,程序将和启动程序链接,程序运行时会分配一个堆栈,用来存储函数的局部变量和返回地址,程序也可以使用静态内存。
3.标识符
C语言关键字不能为标识符,C语言区分大小写,标识符以字母或下划线开头,可以是字母、数字、下划线。
4.字符
ASCII
转义字符:\? \’ \” \\ \a \n \b \r \t \ddd \xdd
5.输入输出函数
printf(); //从后向前执行,格式化输出函数
scanf(); //格式化输入函数
//例如%d %f,输出格式决定内存中的数据如何显示
char c; scanf("%d",&c); //会写入int型地址大小
float f=3.14; printf("%d\n",f);//会将float的IEEE754浮点格式保存的内存值直接解释为int打印
//注意输入输出格式,和一些宽度符号等问题
6.运行时环境
函数调用时会将局部变量保存在堆栈帧中,会产生一个帧指针,变量的值通过该帧加上偏移量进行访问,在函数调用过程中不断产生局部变量和要保存寄存器值,因此不断变换帧指针。
二、数据类型
1.整形
整形包括:字符型、短整型、整型、长整形,它们都分为有符号、无符号。
char 、short、 int、 long、 long、 signed、 unsigned
整形字面值:二进制0b开头、十进制、八进制0开头、十六进制0x开头
字符常量:’A’、‘\n’、 ‘??(’、‘\377’、 ‘\xab’
int a = 0b001111,b = 12,c = 012,d = 0x0f;
char c1 ='A',c2 = '\n',c3 = '\666',c4 = '\x9f',c5 = ‘abc’;//c5=’c’
2.枚举
enum day{one=6,two,three=112,four,five};//默认1,2,3递增,可改变值
enum day a = 0x1234; //枚举变量是整形变量
int b = four; //枚举的符号当作整形常量
3.浮点型
浮点型包括:单精度float、双精度double、扩展精度long double
float f1 = 3.1415926,f2 = -25.,f3 = .5678;
double d1 = 1.234e3, d2 = 123e-3;
4.指针
char *p = "abcd";//字符串的值是一个指针常量
printf("%d,%d,%s\n",p,"abcd",p+1);
运行结果:4210688,4210688,bcd
5.声明与初始化
char s;
int a,b,c=1,d;//多个声明与初始化
int n[10];//数组下标0开始,编译器不检查下标的合法性,可以使用a[10]
char *p = “Hello”;//*号跟随变量
int* p1,p2,p3;//只有p1为整形指针变量
6.typedef
typedef 放在声明前面,可以将标识符等同于该类型
typedef int *p;
p p1,p2;//p1 p2 都是int*
#define q int*
q q1,q2;//q2不是int*
7.常量与const
int const a=10;
const int b=20;
int *p1; //普通指针
int const *p2; //指向整形常量的指针
const int *p3; //同上
int *const p4; //指向整形的常量指针
int const *const p5;//指针和指向的值都不能修改
8.作用域
代码块作用域:{ }中的所有语句,内层标识符优先,从声明处到代码块结束有效
文件作用域:在代码块之外声明的标识符,从声明处到文件尾有效
9.链接属性
外部external:同一标识符的多次声明,所有源文件中只能有一个实体,如:全局变量、函数
内部internal:同一源文件内所有声明是同一实体,不同源文件的声明分属不同实体,如:static修饰的全局变量、函数(static可以将external属性修改为internal)
无none:多个声明被当作不同的实体 ,如:函数内的局部变量,函数的形参
10.存储类型
变量存储的地方:普通内存、运行时堆栈、寄存器
在代码块之外声明的变量存储于:普通内存(静态变量)
在代码块之内声明的变量存储于:堆栈(自动变量)(static改变为静态变量)
被声明为 register的变量存储于:寄存器
静态变量默认初始值0,自动变量默认初始值为该内存值
11.static关键字
static用于全局变量和函数,从external改为internal
static用于局部变量,从自动变量改为静态变量
三、语句表达式
1.空语句
只包含一个分号;
2.表达式语句
x = y + 3; ch = getchar(); y + 3; printf("Hello");
3.代码块
一对花括号{ }
4.if语句
c语言无布尔类型,0为假,其他都为真
else从属于最近上方的if
if和else后只能跟一条语句,或用花括号
5.while语句
do-while语句至少执行一次,表达式为假则结束while循环
6.for语句
for(表达式1;表达式2;表达式3)
表达式可以是任何类型,比如逗号表达式
表达式都可以省略,但分号不能省略,其中表达式2省 略时其值为真
for、while、do-while语句可以互相组合嵌套
使用continue后,会先执行表达式3
7.switch语句
switch中的表达式值会直接定位到相同的case处,执行后面的所有语句,遇到break才跳出switch语句,当无法定位则定位到default处,执行后面的所有语句,遇到break才跳出switch语句,case与default的先后顺序可以任意
switch表达式是int、char、枚举类型
case中必须是常量表达式,且不可重复
多个case子句可共用同一语句,case后面语句组可不用{ },switch语句可嵌套
8.goto语句
goto 标签
9.break与continue
break结束一层循环体,continue结束一次循环
四、操作符
1.运算符
算数运算符:+ - * / %
除了%其它都适用于整型和浮点型
移位运算符:<< >>
左移位:丢弃左边,右边补0
右移位:逻辑移位左边补0,算数移位左边补符号位
位操作符:& | ^
赋值运算符:= 和 ( + - * / % << >> & ^ | )复合赋值
单目操作符:! ++ -- & sizeof ~ + - (类型)
++a前缀操作先增后复制值,a++后缀操作先复制值后增
++a的结果是a值的副本,并不是变量本身,++a=10语句错误!
关系运算符:> >= < <= == !=
满足关系值为1,否则为0
逻辑操作符:&& ||
短路操作:&&左边假不检查右边得假,||左边真不检查右边得真
满足关系值为1,否则为0
条件操作符:? :
逗号操作符:,
从左向右执行,表达式的值为最后的表达式的值
下标引用、结构成员:[ ]、.、->
array[ ] 等价 *(array+下标)
2.布尔值
0是假,非0为真(C语言没有布尔类型)
3.左值与右值
a = b + 1;
a出现在赋值运算左边称为左值,b+1称为右值(字面值常量不是左值)
4.表达式求值
整形运算提升:char、short先转为int进行运算,结果截断
long double >double >float >unsigned long int >long int > unsigned int >int
不同类型运算前,根据优先级转换后进行运算
5.操作符属性
求值顺序由3个因素决定:操作符优先级、操作符结合性、操作符是否控制执行顺序
优先级:两个相邻的操作符哪个先执行取决于优先级
a*b + c*d +e*f(a*b + c*d先执行)
结合性:从左向右执行或从右向左
执行顺序:控制执行顺序(如, &&、||、? :),其中逗号从左向右控制,&&和||从左向右控制并且可以短路控制,? :根据判断控制
在一串操作符中,首先根据优先级执行,如果有控制顺序操作符则控制顺序,否则当优先级相同时根据结合性执行。
五、指针
1.内存与地址
float f = 3.14;
int *p = &f;// *p的值为1078523331
内存中每个单元都有对应的地址,其中存放的数据可以进行不同类型的解释
int a=10,*p=&a;
*(char*)p、*(short*)p、*(int*)p、*(float*)p、*(double*)p)
2.指针变量
int a=10,*p=&a, **q = &p;
指针变量的地址:&p等价q
指针变量的值:p等价&a等价*q
指针变量指向的值:*p等价a等价**q(*解引用或间接访问)
3.未初始化与非法指针
int *p; //静态指针变量初始化0,否则不初始化
*p = 10;
p的值未初始化,产生非法地址错误,或该地址未内存对齐错误
当指针无指向时,一般用NULL赋值
4.指针常量
*&a=10; //&a是一个指针常量
*(int*)10 = 10; //强制转换为指针常量
5.指针表达式
int a=10,*p=&a;
*p+1==a+1
*(p+1)==*(&a+1)
++p//先p+1再复制p的值
p++//先复制p的值再p+1
*++p//先p+1再*(p+1)
*p++//先复制p的值,再p+1,再*p
++*++p//先p+1,再*(p+1),再*(p+1)+1
++*p++//先复制p,再p+1,再*p,再*p+1
程序一:计算字符串长
char *str = "asc4353as3c25sacscaa";
int len = 0;
while(*str++!='\0')len++;
程序二:在字符串数组中查找字符Q
char *string[ ] = {"ascas","saasdsd","sads-Q-aasd",’zxcvzxcsac’,0};
char **str = string;
while(*str!=0)
{
while(**str!='\0')
if(*(*str)++ == ‘Q’)return 1;
str++;
}
6.指针运算
指针加法:char:1、short:2、int:4、double:8
指针减法:两个指针指向同一数组时,减法可得元素数量
关系运算:> >= < <=,可以比较指针得出位置关系
7.指针高级声明
int *f(); //返回值int*类型
int (*f)(); //f是函数指针
int *(*f)(); //f是函数指针,返回值int*类型
int *f[]; //f是数组,元素为int*类型
int (*f[])(); //f是数组,元素为函数指针
int *(*f[])(); //f是数组,元素为函数指针,返回值int*类型
int (*f())(); //f是函数,返回值为函数指针,该函数指针返回值int
int (*f(int,int(*)(int)))(); //对f函数的参数增加,参数1:int,参数2:int(*)(int)
//int(*)(int)是一个函数指针,作为f的回调函数
六、数组
1.一维数组
int num[ ] = {1,2,3,4,5}; //数组初始化,将自动计算长度
int num[5]={1,2,3,4,5,6}; //错误,不能超过数组长度
int num[5]={1,2,3,4}; //不足时,补0
char str[ ] = “hello”; //初始化字符数组
char *str = “hello”; //字符指针指向字符串
数组名:指针常量,表示第一个元素地址,sizeof和&时代表整个数组
下标访问a[1]与间接访问*(a+1)完全相同,但不能执行a++,因为a是常量
2.指针与一维数组
int a[5],*p=a;
指针访问数组:p[1]或*(p+1)
3.一维数组与函数传递
//字符串复制函数:
void strcpy(char buffer[],char const *string){
while((*buffer++ = *string++)!='\0');
}
形参char buffer[ ] 等价char *buffer, 函数无法获得数组长度,除非传递长度参数
4.多维数组
int a[3][3]={{1,2,3},4,5,6,7};//依次赋值,不足补0
int b[][3][2] ={1,2,3,4,5,6,7};//自动计算数组大小,只有第一维可省略,否则无法推断大小
二维数组a[3][3]的一些概念:
a、a+1、a+2的类型为指向包含10个元素的数组的指针
a[0]、a[1]、a[2]表示包含10个元素的数组等价于*a、*(a+1)、*(a+2)
a[0][0]、a[1][1]、a[2][2]表示元素等价于**a、*(*(a+1)+1)、*(*(a+2)+2)
5.指针与多维数组
int a[3][3]={{1,2,3,},4,5,6,7};
int *p,(*q)[3]; //q类型:3个整形元素的数组的指针
p = a[0]; //a[0]、a[1]、a[2]都指向一维数组,指针类型是:整形
q = a; //a、a+1、a+2都指向包含3个整形元素的数组的指针, 指针类型是:包含3个元素的整型数组。
指针需要知道自己指向的类型,否则无法在解引用时获取存储空间大小。
6.多维数组与函数传递
func(int *a); func(int a[]);
int a[3];func(a);
func(int (*a)[3]); func(int a[][3]);
int a[3][3];func(a);
func(int **a); //指向整型指针的指针,与指向整型数组的指针不同
7.指针数组与命令行参数
int *p[10]; //[ ]优先级高于* ,因此p[10]的10个元素都是整形指针
char *str[]={"Hello World","Hello xiaoming"};
//命令行参数:main(int argc, char *argv[ ]),argc表示参数数目,argv指向一组参数
//第一个参数是程序的名字,argv指向一个指针数组,数组每个元素指向一个字符串
8.字符串常量
编译器把字符串常量存储在一块内存,并存储一个指向第一个字符的指针,是一个常量指针,可以对其下标引用、间接访问、指针运算
"abc"、"abc"+1、*"abc"、"abc"[2]、*("abc"+2)
七、函数
1.函数声明和参数
int *func(int a,int b[],char *);//声明时形参名称可以没有
返回值类型:int *
函数名称:func
参数的个数和类型:整型、整形指针、字符指针
C语言函数的形参均以“传值调用”传递,形参拷贝实参的值,值包括地址
2.ADT和黑盒
抽象数据类型ADT,这个技巧被称为黑盒,提供功能和接口,内部实现被屏蔽
在一个文件中使用static将函数和全局变量由external转为internal,则无法在文件外部访问这些函数和全局变量
3.递归
递归函数直接或间接调用自身,形参存储在运行时堆栈上,根据调用的层次不断产生形参进栈和寄存器值的保存,当每一层调用返回时变量从堆栈中销毁
递归两个特点:终止条件、子问题
4.可变参数列表
函数原型中的参数数量和类型可变
类型va_list、宏va_start va_arg va_end 定义于stdarg.h头文件
void func(int num, …)
va_list a; //定义一个va_list类型的变量a
va_start(a,num)//将a设置为指向可变参部分的第一个参数
va_arg(a,int)//取一个参数,a指向该参数,类型为int
va_end(a)//结束处理a
两个问题:宏无法判断参数数量和每个参数的类型,没有第一个参数将无法使用宏
解决办法:通过参数传递参数数量,格式字符串传递每个参数的类型
5.函数指针
void func(int);
void (*p)(int) = &func; //&可省
func(10);
(*p)(10);
p(10); //这三种调用方式等价
回调函数:用户把函数指针作为参数传递给函数后,该函数将回调用户传入的函数
函数指针数组:
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
double(*opt[])(double, double) = { add,sub,mul,div };//定义函数指针数组
int result = opt[0](1, 2);//使用
八、结构体
1.结构体声明与定义
struct student{
int a;
char b;
}; //声明一个student类型
struct student{
int a;
char b;
}s[10],*p; //声明student类型,并创建s[10]和*p,都为student类型
struct student s[10],*p; //创建s[10]和*p,都为student类型
typedef struct{
int a;
char b;
} student;
Student s[10], *p; //创建s[10]和*p,都为student类型
2.结构体成员访问
结构体成员可以是:标量、数组、指针、结构
普通变量:成员通过(.)点运算符访问
指针变量:成员通过(->)运算符访问
结构体自引用:成员是自身结构时,无法确定大小,但可以是指向自身结构的指针
typedef struct{
int a;
stu *b;//错误,此时类型stu还未定义
}stu; //类型只能使用stu
typedef struct student{
int a;
struct student *p;
}stu; //类型可使用stu和struct student
结构体和数组一样,可以用{ }来初始化,或者用循环分别对成员初始化
3.结构体与指针
typedef struct student{
int a;
char c;
}stu;
stu s,*p=&s;
使用p->a访问结构体成员,p的值第一个成员的地址,*p代表整个结构体
结构体中的所有成员都 可以取地址&
4.结构体的存储分配
struct stu {
double f; //8
char s; //4
float f1; //4
int a; //16
long double lf; //16
char c; //16
}; //long double为16字节情况下
规则一:每个成员的起始地址,必须根据自身大小边界对齐
规则二:结构体总大小为最大成员类型长度的倍数
5.结构体与函数参数
当结构体较小时使用值传递,结构体大时使用指针传递
6.位段
struct num{
unsigned a : 7; //0~127
unsigned b : 6; //0~63
unsigned c : 19; //0~(2^19)-1
}; struct num x;
位段成员必须为int、signed int、unsigned int类型,冒号后面为成员占用的位数
7.联合
union stu{
int a;
char b;
float c;
}x ; union stu y;
定义时初始化,必须是第一个成员,且位于花括号内,整个联合体大小为最大成员的长度,包括数组长度
所有成员共用一块地址,内存中的值面对不同成员时仅仅是解释方式不同
8.动态内存分配
C语言头文件stdlib.h中的函数malloc和free用于动态分配内存
void *malloc(size_t size); //返回分配的内存起始地址,类型为void*
void free(void *pointer); //参数必须为malloc分配的地址或NULL
九、文件操作
1.基本概念
文本文件:即ASCII文件,1个字符对应1个字节
二进制文件:将内存中的数据原样输出到磁盘文件
缓冲文件系统:操作系统为每个正在使用的文件开辟输入输出缓冲区,为减少磁盘的使用次数,待输出缓冲区装满后写入磁盘,输入缓冲区满后读入程序数据区
文件指针:FILE结构体存储文件的信息,一般使用指针FILE *fp
系统默认标准流:stdin键盘、stdout显示器、stderr显示器
2.打开关闭流
FILE *fopen(char *filename,char *mode);//文件名,打开方式
成功返回该文件FILE结构体的地址,失败返回NULL
r:读取,必须存在(+可以写)
w:写入,存在则删除内容,不存在则新建文件(+可以读)
a:追加写入,存在则不删除内容,在尾部写入,不存在则新建文件 (+可以读)
b:打开二进制文件,默认打开文本文件
int *fclose(FILE *filepointer);//文件指针
3.文件读写
只用于标准输入输出函数:getchar()与putchar()、gets()与put()、scanf()与printf()
用于所有流的函数:
字符读写函数:fgetc()、getc()和fputc()、putc()
字符串读写函数:fgets()和fputs()
格式化读写函数:fscanf()和fprintf()
二进制读写函数:fread()和fwrite()
4.字符I/O
fgetc与fputc是真正的函数,getc、putc、getchar、putchar是通过#define定义的宏
int getchar(void);//stdin
int fgetc(FILE *stream);
int getc(FILE *stream);//都返回读到的字符值int类型 ,文件尾EOF也是int类型
int putchar(int character);//stdout
int fputc(int character,FILE *stream);
int putc(int character,FILE *stream);//将传入的int裁剪为unsigned char
5.未格式化的IO
行I/O可以使用两种方式:未格式化和格式化的,都用于操纵字符串
char *gets(char *buffer); //stdin ,无法确定长度的玩具函数
char *fgets(char *buffer,int buffer_size,FILE *stream); //换行符/超过缓冲区,停止读取
int puts(char const *buffer); //stdout
int fputs(char const *buffer,FILE *stream);
6.格式化的IO
int scanf(char const *format, ...); //输入源stdin
int fscanf(FILE *stream,char const *format, ...); //stream
int sscanf(char const *string,char const *format, ...);//string(内存中的字符串)
int printf(char const *format, ...); //stdout
int fprintf(FILE *stream,char const *format, ...);//stream
int sprintf(char *buffer,char const *format, ...);//送入buffer
7.二进制IO
二进制输出避免了数值转换为字符串的过程
size_t fread(void *buffer,size_t size,size_t count,FILE *stream);//buffer输入输出数据缓冲区
size_t fwrite(void *buffer,size_t size,size_t count,FILE *stream);//size是每块数据的大小,
8.定位函数
int fflush(FILE *stream);//立即将输出缓冲区的数据写入
long ftell(FILE *stream);//返回流当前的位置,距离文件起始位置的偏移量
void rewind(FILE *stream);//将读写指针设置回流的起始位置
int fseek(FILE *stream,long offset,int from);//将读写指针设置到距离from的offset偏移处
//起始SEEK_SET:0、当前SEEK_CUR:1、尾部SEEK_END:2
十、预处理器
1.#define
C程序的预处理过程:删除注释、插入#include包含的文件内容、定义和替换#define定义的符号、处理条件编译指令。
#define name stuff //将符号name替换为stuff
name带参数时被称为宏,对任何出现参数的地方应该加()
2.条件编译
以 #if 开始 #endif结束
条件编译1:选择代码的一部分是否参与编译
#if expression // expression假则忽略编译
#endif
条件编译2:在编译时选择不同的代码
#if expression1
#elif expression2
#else
#endif
条件编译3:判断定义是否存在
#ifdef name
#endif
#ifndef name
#endif
3.包含
编译器支持:函数库文件#include<filename>和本地文件#incldue” filename”
两种方式查找文件路径不同
头文件重复包含:
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
#endif
十一、标准函数库
部分函数:
//算数<stdlib.h>
int abs(int value);//返回参数的绝对值
//浮点数<math.h>
double exp(double x);//返回e值的x次幂
double log(double x);//返回以e为底x的对数
double long10(double x);//返回以10为底x的对数
double pow(double x,double y);//返回x的y次方
double sqrt(double x);//返回x的平方根
double abs(double x);//返回x的绝对值
//随机数<stdlib.h>
int rand(void);//返回0~RAND_MAX之间的伪随机数,可以取模再加偏移得到任意范围
void srand(unsigned int seed);//使用seed参数对随机数发生器初始化,避免相同的随机数
//时间<time.h>
clock_t clock(void);//返回程序执行的毫秒数
time_t time(time_t *value);//返回秒数,从1970.1.1开始计算
char *ctime(time_t const *value);//将value指向的秒数值,转换为时间字符串
struct tm *gmtime(time_t const *value);//将value指向的秒数值,转换为tm时间结构