环境设置
c程序的源文件通常使用扩展名.c
c程序需要编译成机器语言,这样cpu可以按给定指令执行程序。
最常用的编译器是gcc(mac上xcode就可以)
程序结构
-
#include 预处理器指令,类似于import,主要用于告诉编译器,我们要引入什么。 -
.h 结尾的是头文件,头文件中一般是定义的结构体和变量 -
#include 引入头文件,告诉c编译器编译之前要引入stdio.h文件,在linux中去/usr/include目录中寻找 -
函数 -
变量 -
语句&表达式 -
注释 以/…/包裹的会被编译器忽略 - #include <stdio.h>
-
- /*主函数,代表程序从这里开始*/
- int main()
- {
- /* printf是引入的stdio.h中的函数*/
- printf("Hello, World! \n");
- /*终止main函数,并返回0*/
- return 0;
- }
复制代码
基本语法
分号;
';'分号是语句结束符,每个语句必须以分号结束,#include 和#include 不需要加;
注释:
和java一样
标识符
就是java里的变量名和java的变量规范差不多;
关键字
保留关键字,不能作为常量名、变量名等其他标识符 - 关键字 说明
- auto 声明自动变量
- break 跳出当前循环
- case 开关语句分支
- char 声明字符型变量或函数返回值类型
- const 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
- continue 结束当前循环,开始下一轮循环
- default 开关语句中的"其它"分支
- do 循环语句的循环体
- double 声明双精度浮点型变量或函数返回值类型
- else 条件语句否定分支(与 if 连用)
- enum 声明枚举类型
- extern 声明变量或函数是在其它文件或本文件的其他位置定义
- float 声明浮点型变量或函数返回值类型
- for 一种循环语句
- goto 无条件跳转语句
- if 条件语句
- int 声明整型变量或函数
- long 声明长整型变量或函数返回值类型
- register 声明寄存器变量
- return 子程序返回语句(可以带参数,也可不带参数)
- short 声明短整型变量或函数
- signed 声明有符号类型变量或函数
- sizeof 计算数据类型或变量长度(即所占字节数)
- static 声明静态变量
- struct 声明结构体类型
- switch 用于开关语句
- typedef 用以给数据类型取别名
- unsigned 声明无符号类型变量或函数
- union 声明共用体类型
- void 声明函数无返回值或无参数,声明无类型指针
- volatile 说明变量在程序执行中可被隐含地改变
- while 循环语句的循环条件
复制代码
基本运算
和java差不多,运算符,简写等 - a += b; 等价于 a = a+b;
- ++ -- 和java 也一样,在前先操作再使用,在后,先使用再操作
复制代码
不同点:C的取余运算必须是整数,java的取余可以是小数
转义字符
-
\n 换行 (也表示字符串的结束) -
\t 水平制表(相当于按tab) -
\b 退格,将当前位置移到前一列 -
\f 换页,将当前位置移到下页开头 -
\r 回车 -
\v 垂直制表 -
’ 单引号 -
" 双引号 -
\ 反斜杠
数据类型
-
char 字符型 以单引号’'包围 -
short 短整形 -
int 整形 -
long 长整形 -
float 单精度浮点型 -
double 双精度浮点型 -
void 无类型 -
字符串类型 以 双引号"" 包围
基本类型以及占用长度
32位编译器中各基本类型所占字节数
在64位编译器中各基本类型所占字节数
在16位编译器中各基本类型所占字节数
- typedef unsigned char uint8_t; //无符号8位数 1字节
- typedef signed char int8_t; //有符号8位数 1字节
- typedef unsigned int uint16_t; //无符号16位数 2字节
- typedef signed int int16_t; //有符号16位数 2字节
- typedef unsigned long uint32_t; //无符号32位数 4字节
- typedef signed long int32_t; //有符号32位数 4字节
- typedef float float32; //单精度浮点数 4字节
- typedef double float64; //双精度浮点数 8字节
复制代码
获取某个数据类型长度可以使用sizeof操作符 - #include <stdio.h>
- int main()
- {
- short x = 11;
- int y = 4567;
- int short_length = sizeof x;
- int int_length = sizeof(y);
- printf("short length = %d,int length = %d \n",short_length,int_length)
- return 0;
- }
复制代码
类型转化
自动类型转化
自动类型转化是编译器根据代码的上下文环境自动判断的结果,是静默的。
遵循以下规则:
强制类型转化 - (类型) 变量
- double d = 123.8;
- //转化的结果保存到临时变量x里
- int x = (int) d
复制代码
格式控制符
格式空字符,它指明了以何种形式输出数据,以%开头
-
%d 输出整数,是decimal的简写 -
%hd 输出short int 类型,是short decimal的简写 -
%ld 输出long int,是 long decimal的简写 -
%c 输出一个字符 -
%s 输出一个字符串 -
%f 以十进制形式输出 float 类型 -
%lf 以十进制形式输出 double 类型 -
%e 以指数形式输出 float 类型,输出结果中的 e 小写 -
%E 以指数形式输出 float 类型,输出结果中的 E 大写 -
%le 以指数形式输出 double 类型,输出结果中的 e 小写 -
%lE 以指数形式输出 double 类型,输出结果中的 E 大写 -
%g 默认最多保留六位有效数字,包括整数部分和小数部分;%f 和 %e 默认保留六位小数,只包括小数部分 -
%X 表示以十六进制输出 -
%#X表示以十六进制形式输出,并附带前缀0X - int x=123;
- int a = 'x';
- putchar(a) //字符的专用输出
- printf("%d %c", x,a);
复制代码
const
const类似于java中的final,定义了以后,它的值不能被改变,在整个作用域中都保持固定。 - //语法
- const type name = value
- //定义常量最大年龄为150
- const int maxAge = 150;
- //重新赋值会报错
- maxAge=88;
复制代码
const和指针 - //指针指向的数据是只读的
- const int *p1;
- int const *p2;
- //只读指针
- int * const p3;
复制代码
循环结构和选择结构
c语言中的if else用法和java中的用法一样
c语言中的逻辑运算符合java中的一样
-
&& 与运算,对应数学中的且 -
|| 或运算,对应数学中的 或 -
!非运算,对应数学中的 非 - switch(表达式){
- case 整型数值1: 语句 1;
- case 整型数值2: 语句 2;
- ......
- case 整型数值n: 语句 n;
- default: 语句 n+1;
- }
- 如:
- switch(a){
- case 1: printf("Monday\n");
- case 2: printf("Tuesday\n");
- case 3: printf("Wednesday\n");
- case 4: printf("Thursday\n");
- case 5: printf("Friday\n");
- case 6: printf("Saturday\n");
- case 7: printf("Sunday\n");
- default:printf("error\n");
- }
复制代码
c语言中的循环和java没区别 break和continue的用法也没啥区别 - // while 循环
- #include <stdio.h>
- int main(){
- int i, sum=0;
- i = 1; //语句①
- while(i<=100 /*语句②*/ ){
- sum+=i;
- i++; //语句③
- }
- printf("%d\n",sum);
- return 0;
- }
- //for循环
- #include <stdio.h>
- int main(){
- int i, sum=0;
- for(i=1/*语句①*/; i<=100/*语句②*/; i++/*语句③*/){
- sum+=i;
- }
- printf("%d\n",sum);
- return 0;
- }
复制代码
数组
c中的数组和java区别不大,需要注意的是c中的类型,没有java那么强。
未初始化的值就默认为对应类型的默认值。 - //数组初始化
- int arr[3] ={1,2,3}
- //不指定长度的初始化,直接打满
- int arr[] = {1,3,4}
复制代码
-
对于short、int、long,就是整数 0; -
对于char,就是字符 ‘\0’; -
对于float、double,就是小数 0.0。
字符串
c中没有字符串的概念的。用数组来承载字符串,c中使用数组或者指针来间接地存储字符串。
用来存放字符的数组称为字符数组。字符数组实际上是一些列字符的集合,也就是字符串。
c语言规定,可以将字符串直接赋值给字符数组 - char str[10] = {"yxkong"};
- char str[10] = "yxkong";
- char str[] = "yxkong"; //这种形式更加简洁,实际开发中常用
- int len = strlen(str)
复制代码
-
字符串只有在定义的时候,可以一次性赋值 -
一旦定义完,只能一个字符一个字符的赋值修改; -
在c语言中,字符串总是以"\0"作为结尾(\0是ASCII码中的第0个字符,英文也称为Null) -
c中处理字符串时会从前往后逐个扫描,发现\0就认为是字符串的结尾。 -
字符串的长度使用 strlen(str) -
字符串可以用printf()格式输出,也可以直接用puts()输出
字符串的操作
字符串连接函数 strcat() - //将y拼接到x
- // x 必须足够长,要不然会越界(相当于往x的数组中添加数据)
- // 拼接的过程中会删除x中的\0,最终返回x的地址
- strcat(x, y);
复制代码
字符串复制函数 strcpy() - // 将y复制到x
- // c会把y中的字符串拷贝到x中
- strcpy(x, y);
复制代码
字符串比较函数 strcmp()
比较的是ASCII码值 - // x和y相同返回0
- // x > y 返回>0的值
- // x < y 返回<0的值
- strcmp(x, y);
复制代码
函数
将常用的代码以固定的格式封装成一个独立的模块,只要知道这个模块的名字就可以重复利用,这个模块叫函数。
C语言自带的函数称为库函数。其他公司或个人开发的库称为第三方库
c语言的函数和正常情况下的java方法差不多(先定义再使用)
c中允许先声明,再使用,声明函数可以理解为java的中的接口 - #include <stdio.h>
- //声明一个函数add
- int add(int x,int y);
- int add(int,int); //和上面的效率一样
- int main(){
- int rst = add(5,6);
- printf("%d",result);
- return 0;
- }
- //声明函数add的定义(实现)
- int add(int x,int y){
- return x+y;
- }
复制代码
c语言可以直接在程序中定义代码块(这块和java区别很大) - #include <stdio.h>
- int main(){
- int x = 20;
- {
- int x = 30;
- //输出30
- printf("x=%d",x)
- }
- //输出20
- printf("x=%d",x)
- }
复制代码
变量作用域
c中的局部变量的作用域和java的局部变量一样
全局变量一般定义在函数外,它的默认作用域是整个程序,也就是所有的源文件,包括源文件.c和头文件.h文件。
给变量加上 static ,它的作用域就变成了当前文件。
建议全局变量全部大写,以_分隔 - #include <stdio.h>
- //全局变量
- int rst =0;
- //当前文件
- int static x=10;
- //声明函数add的定义(实现)
- int add(int x,int y){
- return x+y;
- }
- int main(){
- int y=6;
- rst = add(x,y);
- printf("%d",result);
- return 0;
- }
复制代码
预处理命令
在c语言编译和链接之前,还需要对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分代码等,这个过程叫做预处理。
include命令
“#include” 叫做文件包含命令,用来引入对应的头文件(.h文件),是预处理命令的一种
define命令
"#define"叫做宏定义命令,宏定义:用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串
- //不带参宏定义语法
- #define 宏名 字符串
- #include <stdio.h>
- #define M(3*x+5)
- int main(){
- int x = 5;
- int sum = 5*M; //等价于 sum = 5*(3*x+5)
- printf("sum = %d",sum)
- return 0;
- }
- //带参宏定义语法
- #define 宏名(形参列表) 字符串
- #include <stdio.h>
- #define MIN(x,y) ((x>y)?y:x)
- int main(){
- int x = 5,y=8;
- int min = MIN(x,y)
- printf("min = %d",min)
- return 0;
- }
复制代码
带参宏定义与函数的区别
-
宏展开后仅仅是字符串的替换,不会对表达式进行计算 -
宏在编译之前就被替换掉了,不会参与编译; -
函数是一段重复使用的代码,会被编译,会分配内存
条件编译 - #if 整型常量表达式1
- 程序段1
- #elif 整型常量表达式2
- 程序段2
- #elif 整型常量表达式3
- 程序段3
- #else
- 程序段4
- #endif
- #ifdef 宏名
- 程序段1(如果定义了指定的宏名,则执行这块)
- #else
- 程序段2 (没有定义指定的宏名,则执行这段)
- #endif
- #ifndef 宏名
- 程序段1
- #else
- 程序段2
- #endif
复制代码
指针
- 表示这是一个指针变量,datatype表示该指针所指向的数据的类型,定义指针变量时,必须带上*
- // 定义int 变量a,并初始化为100
- int a = 100;
- // 定义int 指针p_a 并把a的指针地址赋值给p_a
- int *p_a = &a;
复制代码- #include <stdio.h>
- int main(){
- int a = 22,b=55;
- //指针变量ptr指向a的地址
- int *ptr = &a;
- //通过指针变量获取数据
- printf("%d",*ptr);
- int c = 15;
- //通过指针变量修改内存上的地址
- *ptr = b;
- //通过指针变量获取内存上的数据(最后a,b,c 都为55)
- c = *ptr
-
-
- printf("%d",*ptr);
-
- //str 本身就表示数组的首地址,不需要加&
- char str[20] = "yxkong";
- printf("%#X, %#X\n", &a, str);
-
-
- return 0;
- }
复制代码
关于* 和&的问题
结构体
结构体可以理解为java中的类。
-
结构体的定义使用struct -
结构体指针的获取也是& - struct 结构名 对象名; //“struct 结构名”就是结构体的数据类型名
- //定义结构体
- struct Point{
- int x;
- int y;
- };
- //使用新定义的结构体Point的时候,必须struct Point 然后对象名
- struct Point oPoint1={100,100};
- struct Point oPoint2;
- struct Point{
- int x;
- int y;
- } p;
- //结构体变量赋值
- p.x = 15;
- p.y = 22;
- //获取结构体的指针
- struct Point *ptr = &p;
- //通过结构体指针获取结构体成员
- (*ptr).x
- //通过箭头直接获取结构体指针的成员变量
- *ptr->x
- //只使用两次的结构体
- struct {
- int x;
- int y;
- } pot1,pot2={34,26};
- pot1.x = 11;
复制代码
结构体数组 - //表示一个班级有5个学生
- struct stu{
- char *name; //姓名
- int num; //学号
- int age; //年龄
- char group; //所在小组
- float score; //成绩
- }class[5];
- //定义结构体数组,并初始化
- struct stu{
- char *name; //姓名
- int num; //学号
- int age; //年龄
- char group; //所在小组
- float score; //成绩
- }class[5] = {
- {"Li ping", 5, 18, 'C', 145.0},
- {"Zhang ping", 4, 19, 'A', 130.5},
- {"He fang", 1, 18, 'A', 148.5},
- {"Cheng ling", 2, 17, 'F', 139.0},
- {"Wang ming", 3, 17, 'B', 144.5}
- };
复制代码
使用typedef 定义结构体
typedef是c语言中给数据类型起新别名的。是为了编码方便,类似于语法糖。使用typedef后,简化了结构体的使用; - //语法
- typedef oldName newName
- //相当于typedef 给struct redisObject起了一个别名robj
- typedef struct redisObject{
- ....
- } robj;
- /**
- * 定义一个新的结构类型redisObject
- * 使用typedef 为结构体起了一个别名 叫robj
- * 之后就可以在程序中直接使用robj 了
- */
- typedef struct redisObject {
- unsigned type:4; //定义一个无符号变量 type,长度为4位
- unsigned encoding:4;
- unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
- * LFU data (least significant 8 bits frequency
- * and most significant 16 bits decreas time). */
- int refcount;
- void *ptr; //定义一个指针,指针是以*号开头
- } robj;
- robj *createObject(int type, void *ptr) {
- robj *o = zmalloc(sizeof(*o));
- o->type = type;
- o->encoding = OBJ_ENCODING_RAW;
- o->ptr = ptr;
- o->refcount = 1;
- /* Set the LRU to the current lruclock (minutes resolution), or
- * alternatively the LFU counter. */
- if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
- o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
- } else {
- o->lru = LRU_CLOCK();
- }
- return o;
- }
复制代码
当结构体中需要引用自己时 - typedef struct tagNode
- {
- char *pItem;
- struct tagNode *pNext; //引用自己的指针,这样编译器才能识别
- } *pNode;
复制代码
c语言枚举 - //语法
- enum typeName{ valueName1, valueName2, valueName3, ...... };
- //示例,枚举值默认从0开始,往后逐个加1
- enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
- //给枚举赋值
- enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
- //定义枚举变量
- enum week a, b, c;
- enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;
- //枚举赋值
- enum week a = Mon,b
复制代码
共用体union
-
共用体有时也称为联合或联合体 -
结构体的各个成员会占用不同的内存,相关之间没有影响 -
共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员 -
共用体占用的内存等于最长的成员占用的内存,共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值 -
由于成员在内存中对齐到一头,修改一个成员,其他的成员值随之改变 - //定义共用体
- union data{
- int n;
- char ch;
- double f;
- };
- //创建变量
- union data a, b, c;
- //定义并共用体并创建变量
- union data{
- int n;
- char ch;
- double f;
- } a, b, c;
复制代码
位域
有些数据在存储时并不需要占用一个完整的直接,只需要占用一个或几个二进制位即可。c语言提供了位域的数据结构。
-
指定某些成员变量所占用的二进制位数(Bit) -
通过 变量名:num 来表示,num不能超过变量类型的长度 -
不同的编译器位域的存储规则不一样,但都是在尽量压缩存储空间 -
当相邻成员的类型相同时,如果他们的位宽之和小于类型的大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止; -
如果他们位宽之和大于类型的大小,那么后欧美的成员将从新的存储单元开始,其偏移量为类型大小的整数倍; -
当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC会压缩存储,VC/VS不会 - struct bs{
- unsigned m; //m没有限制,占用4个字节内存
- unsigned n: 4; //:后面的数字用来限定成员变量占用的位数 占用4位
- unsigned char ch: 6; //占用6位
- };
复制代码
无名位域,无名位域成员没有名称,只给出数据类型和宽度,一般用来填充或者调整成员位置 - struct bs{
- int m: 12;
- int : 20; //该位域成员不能使用
- int n: 4;
- };
复制代码
上面的例子中,如果没有位宽为 20 的无名成员,m、n 将会挨着存储,sizeof(struct bs) 的结果为 4;有了这 20 位作为填充,m、n 将分开存储,sizeof(struct bs) 的结果为 8
学习c,推荐大家去http://c.biancheng.net |