C令牌
C程序又各种令牌组成,令牌可以是关键字、标识符、常量、字符串值,符号。
分号: 语句结束符
注释: //单行注释 ,/* 局部或多行*/ 多行或者局部注释
标识符:字母或下划线 _ 开头,后跟零个或多个字母、下划线和数字(0-9),区分大小写,不能是关键字
关键字:C语言保留,不能作常/变量或其他标识符,比如:int、char、if 、else、while 、for 、case 、switch 等
C数据类型
类型决定内存占用空间以及布局
类型
说明
基本类型(算数类型)
浮点和整型
枚举类型 (算数类型)
可以看做整型
void类型
表示空
派生类型
指针、数组、结构体、共用体、函数
整型
类型
占用内存
说明
char
1字节
字符型(可能有符号或无符号)
unsigned char
1字节
无符号字符型
signed char
1字节
有符号字符型
int
2或4字节(根据平台)
有符号整型
unsigned int
2或4字节(根据平台)
无符号整型
short
2字节
有符号短整型
unsigned short
2字节
无符号短整型
long
4或8字节(根据平台)
有符号长整型
unsigned long
4或8字节(根据平台)
无符号长整型
浮点型
类型
占用内存
说明
float
4字节
单精度浮点值,精度6位小数
double
8字节
双精度浮点值,精度15位小数
long double
16字节
19 位小数
void 类型
返回值位空、参数为空、void*表示任意类型的指针
sizeof:可使用sizeof获取数据类型的占用字节的大小,这个是静态的,编译时就确定了。
强制类型转换
(type) expression
算数类型隐式转换
表达式存在不同类型的数值,编译器会把它们会被转换为表达式中出现的最高层次的类型。
比如整型:
算数类型隐式转换
变量
变量定义:可操作存储区域的名称。
内存布局
类型
占用内存
无符号整型
均为原码*
有符号整型
1个符号位,其他为补码*
float
1位符号,8位指数,23位小数
double
1位符号,11位指数,52位小数
补充:
原码:符号位加上真值的绝对值,即用第一个二进制位表示符号(正数该位为0,负数该位为1),其余位表示值。
反码:正数的反码与其原码相同;负数的反码是对其原码逐位取反。
补码: 正数的补码就是其本身;负数的补码是在其反码的基础上+1。
变量定义:创建变量的存储类型和名称
type variable_list;
比如 int a, b, c;
type variable1 = value, variable2 = value;
=表示初始化,只定义不声明直接使用变量,变量的值是未定义的。
变量声明
告诉编译器有这个变量,并且指明变量的类型和名称。在编译时检查,是否有声明,链接时检查是否定义。
extern type variable; 声明变量
type variable; 定义就是声明一部分。
局部变量被定义时,系统不会对其初始化,必须开发者对其初始化后再使用。定义全局变量时,系统会自动对其初始化,
类型
初始值
int
0
char
'\0'
float
0
double
0
pointer
NULL
常量(字面量)
常量定义:固定量,定义初始化后不能修改。
整型常量: 1212、393U、298u、0xffe等。
前缀:表示进制 ,0八进制,0x十六进制, 不带十进制 (不区分大小写)
后缀: 表示类型, U无符号,L有符号 (不区分大小写)
浮点常量:2.5、2.5f、231e-5L
字符常量:'a'、'%'、 '\n'
字符串常量:"hello"
常量定义:
const type variable = value;
比如 const int a = 110;
#define variable value
比如 #define NAME "XiaoMing"
存储类
放置在类型前,表明变量或函数的声明周期和范围。
auto:局部变量,只能修饰局部变量,默认不带
{
int integer;
auto int auto_integer;
}
register:定义存储在寄存器中的变量,最大占用的位置是寄存器大小, 不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
{
register int miles;
}
static:静态变量或者全局变量,程序运行期间不销毁,定义的变量或者常量或者函数只能在同一个源码文件中访问。
static int value =10;
extern:声明一个全局变量的引用,对所有文件可见。
运算符
算术运算符:+、-、*、/、%、++、--
a/b :b不能是0
a%b: 必须都是整数
a++,a--:先运算,再返回运算后的值
++a, --a: 先返回值,再运算
关系运算符:返回的是布尔值真或假
==、!=、>、< 、>=、 <=
逻辑运算:返回的是布尔值真或假
&&、||、!
位运算符: & 、|、^、~、<>
&:按位与,只有都是1,才1,让更多的地方变成0。
|:按位或,只有都是0,才0,让更多的地方变为1。
^:按位异或,一样的位置是0,不一样的是1。
~:按位取反,1变成0,0变成1
<
>>:向右移动,(正数左边补0,负数左边补1)
如果整数乘除运算是2的幂,可以使用移位来实现
赋值运算符: = 、+=、<<=等
+=: 先运算在赋值
其他运算符
sizeof:返回变量占用内存的大小
&a:取变量的地址值
*p:表示地址p指向变量的值,解引用
?: :条件表达式
运算符优先级
后缀 > 一元 > 乘除 > 加减 > 移位 > 相等判断 > & > ^ > | > && > || > ?: > 赋值 > ,
比如: ++a++ 先a++ ,在运算++a
比如: !a&&b||a !>&&>||
数组
连续存储的一系列相同类型的变量。
数组定义
类型 数组名[大小];
int array[10];
数组初始化
int array[5] = {1, 2, 3, 4, 5};
int array[] = {1, 2, 3, 4, 5}; 数组大小可省略,编译器自动推导。
int array[5] = {1}; 部分初始化,array[0] ==1, 其他为0。
数组访问:
下标随机访问:array[3] = 0;
多维数组
数组的数组。
多维数组定义:
类型 数组名[大小][大小]···;
int array[5][5][5];
二维数组
类型 数组名[x][y];
int array[5][5]; x 表示行, y表示列。
int a[3][4] 表示如下:
~
~
~
~
a[0][0]
a[0][1]
a[0][2]
a[0][3]
a[1][0]
a[1][1]
a[1][2]
a[1][3]
a[2][0]
a[2][1]
a[2][2]
a[2][3]
初始化
下面的初始化是等价的
int array[3][4] = {{1,2,3,4}, {2,2,3,4}, {3,2,3,4}, {4,2,3,4}};
int array[3][4] = {1,2,3,4,2,2,3,4,3,2,3,4,4,2,3,4};
二维数组下标访问:
array[2][3] = 5;
指针
指针变量
这个变量的值是另一个变量的地址;
*类型 指针名 = &变量名;
指向变量的数据类型必须和指针的数据类型一致。
指针什么都不指向,指针变量赋值为NULL , int *ptr = NULL;
指针解引用 *ptr,ptr->value,表示指向变量的值,
指针的算数运算: ++、--、+、-
+:指针+1,表示指向下一个同等数据类型的变量的位置,比如 int *ptr = &value, ptr+1 表示下一个int位置,向后偏移4个位置。
-:指针-1,向前偏移一个位置。
ptr1-ptr2:表示两个指针偏移位置。
指针的比较运算: ==、< 、 >
指针可以比较大小,相等
数组与指针
数组名既是数组的指针又是第一个数组元素的指针。
数组名是一个返回第一个数组元素的常量指针。
int array[39] = {0};
int *p = array;
那么
p == array == &array[0]
p + 20 == &array[20]
*(p+n) == array[n]
指针数组
int *ptr[256]; 是个数组,里面的元素是指向int变量的指针。
指针的指针:
int **ptr_int_ptr = &ptr; ptr_int_ptr 指向一个int型的指针。
函数指针
函数的函数名表示函数的地址,是一个常量,不能修改。
函数指针是一个变量,可以把函数地址赋值给函数指针,指向一个函数。
typedef int (*fun_ptr) (int, int);
int add(int left, int right){
return left + right;
}
fun_ptr add_func = add;
if (add_func) add_func(1, 2);
字符串
以 '\0'(null) 结尾的字符数组。
char string[5] = {'1', ‘9', '0', '5', '\0'};
char string[] = "1905"; "1905" 表示字符串字面量
char *string = "1905";
常用标准库函数
函数声明
功能
strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
strcat(s1, s2);
连接字符串 s2 到字符串 s1 的末尾。
strlen(s1);
返回字符串 s1 的长度。
strcmp(s1, s2);
如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0。
strchr(s1, ch);
返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strstr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
条件语句
if (条件判断){语句;}
if (条件判断){语句;} else{ 语句;}
if (条件判断){语句;} else if{ 语句;} else if ··· else{语句}
switch(变量){case 常量: 语句;··· default: 语句}: case语句不加break会穿透。
循环语句
while(条件判断){语句;} 条件真,执行语句
for(变量定义赋值;条件判断;每次循环完执行的语句){ 语句;}
do{语句}while(条件) 先执行一次,再判断
break:跳出循环,
continue:跳出本次循环
枚举
可以为每个变量指定值,第一个值不指定值默认为0,后面的值是前面的值+1,可以指定不同值
enum STATE{
PLAY = 1,
PAUSE,
STOP
};
enum STATE state;
enum STATE{
PLAY = 1,
PAUSE,
STOP
}state;
state = PLAY;
typedef enum _STATE{
PLAY = 1,
PAUSE,
STOP
}STATE;
STATE state = PLAY;
typedef enum{
PLAY = 1,
PAUSE,
STOP
}STATE;
STATE state = PLAY;
结构体
结构体用来存储不同类型的数据项。
结构体声明
struct tag {
member-list
member-list
member-list
...
} variable-list ; tag、member-list、variable-list 至少出现2个。
struct Player
{
int state;
char *file_name;
};
定义声明结构体变量需要带上 struct : struct Player player;
typedef struct _Player
{
int state;
char *file_name;
}Player;
使用typedef定义类型,Player前面不需要使用struct:Player player;
struct NODE
{
char string[100];
struct NODE *next_node;
};
可以使用指向自己类型的指针
结构体初始化
Player player = {1, "a.mp4"};
可以指定结构体成员名初始化:
Player player = {.state=1, .file_name="a.mp4"};
直接使用memset:
Player player;
memset(&player, 0, sizeof(Player));
访问结构成员
Player player;
Player *pPlayer = &player;
player.state;
pPlayer->state = 1;
(*pPlayer).state = 1;
*pPlayer.state 是不正确的,因为 运算符.比*的优先级要高。
位域
位域的语句格式如下:
struct 位域结构名
{
类型说明符 位域名: 位域长度;
类型说明符 位域名: 位域长度;
类型说明符 空空空 : 位域长度;
...
};
struct bs{
int a:8;
int b:2;
int c:6;
}data;
声明 data 为 bs 变量,占两个字节。a 占 8 位, b 占 2 位, c 占 6 位。
这样使用位域变量data.a = 8; data.b = 3;
struct bs{
unsigned a:4;
unsigned :4; /* 空域 */
unsigned b:4;
unsigned c:4
}
位域名可以为空,表示空域
共用体
union在相同的内存位置存储不同的数据类型,定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。
union的大小是成员变量中内存占用最大的决定的(当然需要考虑内存对齐)。
union 使用下标访问。
union的语句格式如下:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
比如:
union Data
{
int i;
float f;
char str[20];
} data;
data.i = 9;
函数
一组一起可以执行的语句。
函数定义
返回值 函数名称(参数类型 参数名, 参数类型 参数名,···){
语句;
return 返回值;
}
函数声明
extern 返回值 函数名称(参数类型 参数名, 参数类型 参数名,···);
函数调用
带返回值:变量 = 函数名(参数,参数);
不带返回值:函数名(参数,参数);
参数形参:函数定义时规定的参数。运行进入函数时被创建,退出函数时被销毁。
实参:函数调用时外面传入的参数。
可变参数
int func(int num, ... ){
va_list valist;
va_start(valist, num);
for (i = 0; i < num; i++) {
int value = va_arg(valist, int);
}
va_end(valist);
}
va_start 得到第一个可变参数地址。
va_arg 获取可变参数的当前参数,返回指定类型并将指针指向下一参数。
va_end 置空可变参数列表,释放内存。
数组参数
int index(int array[], int i);
*int index(int array, int i);
int index(int array[5], int i); 这个5其实没啥作用。
返回数组:只能返回指针,无法返回数组大小,可以利用out参数传递。
作用域规则
局部变量:使用{}里面创建的变量,只在变量所在的{}里面生效,如果同名会屏蔽作用域外面的变量,优先使用它。
全局变量:在函数外部定义的变量,整个程序运行周期有效。
形式参数:函数内有效,和局部变量。
内存管理
c语言使用如下方法动态分配释放内存,分配的内存在堆上,使用完成后需要手动释放。使用前需要引入#include 头文件
void *calloc(int num, int size); 动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了num*size 个字节长度的内存空间,并且每个字节的值都是0。
void free(void *address); 释放 address 所指向的内存块,释放的是动态分配的内存空间。
void *malloc(int num); 在堆区分配一块指定大小的内存空间,这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
void *realloc(void *address, int newsize); 该函数重新分配内存,把内存变为newsize字节,可能会返回新的地址。