c语言基础

2021/11/17 1011点热度 0人点赞 0条评论

环境设置

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的变量规范差不多;

  • C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称,

  • 标识符以字母或下划线_开头,后可跟数字

  • C标识符内不允许出现标点符号,如@、$和%

  • C区分大小写,大小写不同是两个标识符

关键字

保留关键字,不能作为常量名、变量名等其他标识符

关键字 说明
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()输出

字符串的操作

  • 用于输入输出的字符串函数,例如printf、puts、scanf、gets等,使用时要包含头文件stdio.h

  • 而使用其它字符串函数要包含头文件string.h。

字符串连接函数 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文件),是预处理命令的一种

  • include命令只能包含一个头文件,多个头文件引用需要多个#include命令

  • 多次引入同一个头文件,效果一样

  • 文件包含允许嵌套

  • 头文件中只能包含变量和函数的声明,不能包含定义(头文件的定义类似于java的接口和常量)

define命令

"#define"叫做宏定义命令,宏定义:用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串

  • 宏定义是用宏名来表示一个字符串

  • 宏定义不是说明或语句,在行末不必加分号,如果加上分号,替换的时候分号也会被带上;

  • 宏定义必须卸载函数之外,作用域为到源程序结束,可通过#undef终止;

//不带参宏定义语法
#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

指针

  • 指针用来指向内存中字节的内存地址

  • 通过&来获取变量的内存地址

  • 如果一个变量存储了一份数据的指针,我们叫指针变量

  • 通过 * 变量名来表示一个指针变量,定义指针变量,必须变量前加 *

  • 通过指针变量可以获取内存上的数据(比如数组)

  • 指针可以直接加地址,如果是数组,可能比较好定位

  • 如果是变量+n表示当前地址向下几个字节

datatype *name
数据类型  指针名称

* 表示这是一个指针变量,datatype表示该指针所指向的数据的类型,定义指针变量时,必须带上*

// 定义int 变量a,并初始化为100
int a = 100;
// 定义int 指针p_a 并把a的指针地址赋值给p_a
int *p_a = &a;

#include 
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;
}

关于* 和&的问题

int  a = 10;
int *pa = &a;
  • *&a 可以理解为 *(&a) ,&a 取地址, *取对应地址的值,最终等于a

  • &*pa 可以理解为&(*pa) *pa获取了a的数据,&又取地址,又变成了指针变量 pa

结构体

结构体可以理解为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

yxkong

这个人很懒,什么都没留下