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 -
>>
:向右移动,(正数左边补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;如果 s1 |
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字节,可能会返回新的地址。