C语言的基本概念

变量名

传统的C语言用法中,变量名使用小写字母,符号常量名使用大写字母


类型限定符

类型限定符signed与unsigned可以用于限定char类型或任何整型

  • signed (默认)
    如char类型 signed char,取值范围为-128~127
  • unsigned无符号类型
    总是正值或0,例如char类型,unsigned char 取值范围为0~255

定义常量

  • 方法一: const 数据类型 变量名 = 常量;推荐

    例: const int AMOUNT=100;

    const也能配合数组参数使用,表明函数不能修改数组元素的值。

  • 方法二:#define 标识符 常量
    末尾没有分号
    例:#define LOWER 0


定义布尔类型

  • 需要在开头写如下代码:#include <stdbool.h>

布尔类型为bool (Java中为Boolean)


数组

例:int number[100]; (Java中为 int[] number = new int[100];)

(C99开始,可以用变量定义数组大小

二维数组的初始化

  • 列数必须给出,行数可以交给编译器来数

  • 每行一个{},用逗号分隔,

  • 最后的,可以存在,有古老的传统

  • 如果内容省略,表示补0

  • 也可以用定位(C99 ONLY)

    1
    2
    3
    int a[10] = {
    [0] = 2, [2] = 3, 6,
    };
    • [n]在初始化数据中给出定位
    • 没有定位的数据接在前面的位置后面
    • 其他位置的值补零
    • 也可以不给出数组大小,让编译器算
    • 特别适合初始数据稀疏的数组

数组的大小

sizeof(a)/sizeof(a[0])

  • sizeof给出整个数组所占据的内容的大小,单位是字节
  • sizeof(a[0])给出数组中单个元素的大小,相除就得到了数组的单元个数
  • 数组作为函数的参数时 实际是指针(数组的地址),需要用另一个参数来传入数组的大小
    • 不能在[]中给出数组的大小
    • 不能再利用sizeof来计算数组的元素个数

数组的赋值

  • 数组变量本身不能被赋值
  • 要把一个数组的所有元素交给另一个数组,必须采用遍历

字符类型

char是一种整数,也是一种特殊的类型——字符。

  • ''也是一个字符
  • printfscanf里用%c来输入输出字符

逃逸字符

字符 意义
\b 回退一格
\t 到下一个表格位
\n 换行
\r 回车
\" 双引号
\' 单引号
\\ 反斜杠本身

函数

C语言的编译器自上而下,按顺序分析代码

函数的先后顺序很重要。

C语言的函数可以将声明和定义分离,从而顺利通过编译,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void sum(int begin, int end); //声明,函数原型, 如果没有参数,括号内最好填写 void,更严谨

int main() { //main的参数部分也可以写 void
sum(1,10);
sum(20,30);

return 0;
}

// 定义
void sum(int begin, int end) {
int i = 0;
int sum = 0;
for (i = begin; i <= end; i++) {
sum += i;
}
printf("%d到%d的和是%d\n", begin, end, sum);
}

参数传递

  • 调用函数时,给的值可以与参数类型不完全匹配,编译器自行转换类型,(Java则对类型转换要求严格)。
  • C语言调用函数传参数时,是值传递

指针与数组

  • 一元运算符&
    获取变量的地址,它的操作数必须是变量,没有其他的运算包括其中。
    p = &c; 称p为“指向”c的指针。

  • 一元运算符*
    间接寻址运算符。*作用于指针时,将访问指针所指向的变量。

指针

指针就是保存地址的变量

1
2
3
4
int i;
int *p = &i;

int *p,q; //p为指针,值为所指向的地址;*p是int类型变量,为所指向地址上的值;

指针应用场景

  1. 交换两个变量的值

    1
    2
    3
    4
    5
    void swap(int *pa, int *pb) {
    int t = *pa;
    *pa = *pb;
    *pb = t;
    }
  2. 函数需要返回多个值,某些值就只能通过指针返回

指针与const

  1. 指针是const(在const前):表示一旦得到了某个变量的地址,不能再指向其他变量;不影响改变该地址上的值*

    1
    2
    3
    int *const q = &i; //q是const
    *q = 26; //所指变量的值可以更改
    q++; //ERROR!
  2. 所指的类型是const(在const后):表示不能通过指针去修改那个变量(*不会使那个变量成为const**)

    1
    2
    3
    4
    const int *p = &i; // int const *p 作用相同
    *p = 26; //ERROR! (*p)是const
    i = 26; //OK
    p = &j; //OK

数组参数

以下四种函数原型等价

  • int sum(int *arr, int n);
  • int sum(int *, int);
  • int sum(int arr[], int n);
  • int sum(int [], int)

数组变量是特殊的指针

  • 数组无需用&取地址

  • 数组的单元表达的是变量,需要用&取地址

  • []运算符可以对数组做,也可以对指针做
    p[0] <==> a[0]

  • *运算符可以对指针做,也可以对数组做

  • 数组变量是const的指针,所以不能被赋值
    int a[] <==> int *const a=...


字符串

C语言的字符串以字符数组的形态存在

  • 不能用运算符对字符串做运算
  • 通过数组的方式可以遍历字符串

以整数0结尾的一串字符为字符串。(0或\0是一样的,但是和'0'不同)

  • 0标志字符串的结束,但不是字符串的一部分
  • 字符串以数组的形式存在,以数组或指针(主)的形式访问
  • string.h里有很多处理字符串的函数

字符串变量

1
2
3
char *str = "Hello";
char word[] = "Hello";
char line[10] = "Hello";

字符串常量

例如:Hello,字符串会被编译器变成一个字符数组放在某处,这个数组的长度是5+1,结尾还有表示结束的0

例:char* s = "Hello, world!";

  • s是一个指针,初始化为指向一个字符串常量
    • 由于这个常量存储的地方,实际上s为const char* s。(历史原因,编译器接受不带const的写法)
    • 试图对s所指的字符串做写入会导致严重的后果
  • 如果需要修改字符串,应该用数组:char s[] = "Hello, world!";

选择指针还是数组形式处理字符串?

  • 数组(字符串就存放在当前位置,如果要构造一个字符串

    • 作为本地变量,空间被自动回收
  • 指针(不知道字符串的存储位置,如果要处理一个字符串

    • 处理参数
    • 动态分配空间

读入、输出数据

  • 需要在开头写如下代码:#include <stdio.h>

读取数据,需要在变量名前加上&,从而赋值给变量。

EOF(End Of File)

可以通过printf("%d", EOF);读取EOF的数值,一般的设备上值是-1。
EOF操作:

  • windows:Ctrl + Z
  • unix:Ctrl + D

浮点数的输入输出

  • 输入:scanf("%lf", ...);
  • 输出:
    • printf("%f", ...); //float和double类型,printf函数都使用%f进行说明
    • printf("%ld", ...); //对应long整型的参数

字符的输入/输出

  • 输入 getchar()
    从文本流中读入下一个输入字符,并将其作为结果值返回。
  • 输出 putchar(c)
    打印一个字符

字符串的输入输出

1
2
3
char string[8];
scanf("%s", string);
printf("%s", string);

scanf读入一个单词(到空格、tab或回车为止)


  • program <infile
    从输入文件infile中读取字符。
  • otherprogram | program
    将程序otherprogram的标准输出 通过管道,重定向到程序program的标准输入上。

位运算符

  • &
  • |
  • ^ 异或
  • ~ 求反码
  • >> 右移(高位出现的空位,原来高位是什么,就用什么补该空位;>>>无符号右移,高位的空位用0补

静态变量

  • 外部

    • static声明限定外部变量与函数,可以将对象的作用域限定为被编译源文件的剩余部分
    • 通过static限定外部对象,可以达到隐藏外部对象的目的。
    • 如果把函数名声明为static类型,则该函数名除了对该函数的声明所在的文件可见外,其他文件都无法访问。
  • 内部

    • static类型的内部变量,不管其所在函数是否被调用,都会存在。(一直占据存储空间;自动变量:随着函数的 调用/退出 而 存在/消失)

寄存器变量

register声明告诉编译器,它所声明的变量在程序中使用频率较高


结构(和java中的类 概念类似,只有变量没有函数)

关键字struct引入结构声明。

结构类型

关键字struct后面的名字是可选的,称为结构标记

  • 有结构标记

    1
    2
    3
    4
    5
    6
    struct point {
    int x;
    int y;
    };
    struct point pt = {320, 200}; //定义了一个struct point类型的变量pt
    struct point pt2 = {.y = 200, .x = 320};
  • 无结构标记

    1
    2
    3
    4
    struct {
    int x;
    int y;
    } p1, p2; //p1、p2都是无标记结构,里面有x和y

结构与函数

结构的合法操作只有几种:作为一个整体赋值和赋值(包括向函数传递参数以及从函数返回值),通过&运算符取地址,访问其成员。

例:函数makepoint,带有两个整型参数,并返回一个point类型的结构

1
2
3
4
5
6
struct point makepoint(int x, int y) {
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}

结构指针的使用频度非常高,为了使用方便,C语言提供了另一种简写方式。假设p是一个指向结构的指针,可以用p->结构成员的形式(等价于(*p).结构成员),引用相应的结构成员。

1
2
3
4
5
6
struct point* getStruct(struct point *p) {
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("%d %d", p->x, p->y);
return p;
}

C语言提供一个编译时的一元运算符sizeof,可以用来计算任一对象的长度

sizeof 对象sizeof(类型名)会返回一个整型值,等于指定对象或类型占用的存储空间字节数

类型定义 (typedef)

typedef用来建立新的 数据类型名
例如,声明typedef int Length;
将Length定义为与int具有同等意义的名字,Length与类型int完全相同。

1
2
3
4
5
6
7
8
typedef struct ADate {//ADate同样可以略去
int month;
int day;
int year;
} Date; //简化了复杂的名字

//也可正常构造结构后,按如下方式定义
typedef struct ADate Date;

动态分配内存

C99之前无法用变量作为数组定义的大小,当时如何解决该问题?

malloc函数:在需要时,向操作系统申请存储空间,需要#include <stdlib.h>

void* malloc(size_t size);

  • 向malloc申请的空间的大小以字节为单位
  • 返回的结果是void*,需要类型转换为需要的类型。例如int *a = (int*)malloc(n*sizeof(int))

因为程序中的某些地方可能不通过 malloc调用 申请空间,所以,malloc管理的空间不一定是连续的

释放空间

free(a);
malloc得到的空间一定要有free的习惯,只能free申请来的空间的首地址


常用函数

gets()

gets()函数包含于stdio.h头文件,会一直读取用户输入,直至换行为止;而scanf一直读至空格键


字符串函数

  • strlen 字符串长度
    size_t strlen (const char *s); 返回s的字符串长度,不包括结尾的0

  • strcmp 比较字符串
    int strcmp (const char *s1, char *s2); 比较字符串的大小,返回两者 第一个不同的字符的差值

  • strncmp 比较字符串

    int strncmp (const char *s1, const char *s2, size_t n) 比较字符串,n为比较的字符数量,若前n个字符相同,返回0

  • strcpy 复制
    char* strcpy(char *restrict dst, const char *restrict src); 把src的字符串复制给dst
    restrict表明src和dst不重叠,返回dst

    复制字符串的操作示例:

    1
    2
    char *dst = (char*)malloc(strlen(src)+1);
    strcpy(dst, src);

    memcpy复制

    void* memcpy (void*dest, const void *src, size_t n);

    strcpy相比,memcpy并不是遇到’\0’就结束,而是一定会拷贝完n个字节。

  • 字符串中找字符
    char* strchr(const char *s, int c);
    char* strrchr(const char *s, int c);
    返回NULL表示没有找到

  • 字符串中找字符串
    char* strstr(const char *s1, const char *s2);
    char* strcasestr(const char *s1, const char *s2); //忽略大小写 查找字符串

  • 连接字符串

    char *strcat(char *dest, const char *restrict src);将参数 src 字符串复制到参数 dest 所指的字符串尾部


标准库函数 qsort排序

C语言有qsort();C++有sort();Java有Arrays

qsort()声明在stdlib.h文件中。
void qsort(void *base,size_t nelem,size_t width,int (*cmp)(const void *,const void *));

  • base:
    要排序的数组

  • nmemb:
    数组中的元素数目

  • size:
    每个数组元素占用内存空间,可使用sizeof获得

  • cmp:
    比较两个数组元素的比较函数,返回值是int类型。比较函数的第一个参数值a与参数b,此函数需要自定义

    • 返回值 > 0, a 将被排在b后面;
    • 返回值 < 0, a 将被排在b前面;
    • 示例:
      1
      2
      3
      int cmp(const void *a, const void *b) { //升序
      return *(int *)a - *(long int *)b; //不能用'>'、'<'比较符(返回无负值)
      }

转换字符大小写

函数声明在<ctype.h>文件中:

int tolower(int c)

int toupper(int c)


枚举(enumeration)

  • 枚举是用关键字enum来声明的一种自定义的数据类型:

    enum 枚举类型名字 {标识符0, 标识符1, ..., 标识符n};

  • 枚举类型名字第一个字母最好大写,花括号中的标识符是常量符号,只使用大写字母,类型是int,值依次从0到n

    enum Color {RED, YELLOW, GREEN};

  • 主要应用:当需要一些可以排列起来的常量值时,定义枚举就是为了给这些常量值名字

  • 枚举类型可以跟上enum作为类型:void f(enum Color c);Color t = RED;

  • 声明枚举量的时候可以指定值:enum Color {RED = 1, YELLOW, GREEN = 5, BLUE};

    YELLOW的值为2,BLUE的值为6。

  • 不同的枚举常量可以取相同的整数值,但最好采用唯一值,有助于预防难以发现的逻辑错误。


链表

构造结点

1
2
3
4
typedef struct _node {
int value;
struct _node *next;
} Node;

构造链表

1
2
3
typedef struct {
Node *head;
} List;

全局变量

全局变量初始化

  • 没有做初始化的全局变量会得到0值
    • 指针会得到NULL值
  • 只能用编译时刻已知的值来初始化全局变量
  • 它们的初始化发生在main函数之前

如果函数内部存在与全局变量同名的变量,则全局变量被隐藏

静态本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量,没有做初始化的静态变量会得到0值
  • 当函数离开的时候,静态本地变量会继续存在并保持其值
  • 静态本地变量的初始化只在第一次进入该函数时,以后进入函数会保持上次离开的值

静态本地变量是特殊的全局变量


编译预处理指令

  • #开头的是编译预处理指令

#define 用来定义宏

  • #define <名字> <值>(结尾没有分号,不是C语句)
  • 名字必须是一个单词,值可以是各种东西
  • 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值(文本替换)

  • 如果一个宏的值中有其他宏的名字,也会被替换
  • 如果宏的值超过一行,最后一行之前的行末需要加\
  • 宏的值后边的注释不会被当作宏的值的一部分
1
2
3
4
#define PI 3.14159
#define PI2 2*PI
#define PRT printf("%f ", PI); \
printf("%f\n", PI2)

预定义的宏

  • _LINE_ :当前行号
  • _FILE_ :文件路径
  • _DATE_ :日期
  • _TIME_ :时间

带参数的宏的原则

  • 一切都要括号

    • 整个值要括号
    • 参数出现的每个地方都要括号

    例:#define RADTODEG(x) ((x)*57.29578)

  • 可以带多个参数

    #define MIN(a,b) ((a)>(b)?(b):(a))

  • 也可以组合(嵌套)使用其他宏

#include 头文件

将included的文件的全部内容原封不动d地插入到所在位置,因此也不一定要在.c文件最前面#include

  • #include "xxx.h" (要求编译器首先在当前目录寻找该文件,如果没有,到编译器指定目录去找)
  • #include <xxx.h> (让编译器只在指定目录寻找)

把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型。#include和宏一样在是编译预处理指令。

#include不是用来引入库的stdio.h中只有printf等函数的原型,用来保证调用时给出的参数值是正确的类型。printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中。现在的C语言编译器默认会引入所有的标准库。

头文件

  • 在使用和定义这个函数的地方都应该#include这个头文件
  • 一般的做法是任何.c文件都有对应同名的.h文件,把所有对外公开的函数原型和全局变量的声明放进去

不对外公开的函数

  • 在函数前加上static使其成为只能在所在编译单元中被使用的函数
  • 在全局变量前面加上static使其成为只能在所在编译单元中被使用的全局变量

变量的声明

  • int i;是变量的定义(产生代码)
  • extern int i;是变量的声明(不产生代码)

标准头文件结构

  • 运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #ifndef __LIST_HEAD__
    #define __LIST_HEAD__

    #include "node.h"

    typedef struct _list {
    Node* head;
    Node* tail;
    } List;

    #endif

文件

格式化输出

%[flags][width][.prec][hlL]type

  • Flag
Flag 含义
- 左对齐
+ 强制显示符号(正数会显示+)
(space) 正数留空
0 0填充
  • width
width或prec 含义
number 最小字符数(总长,包括小数点后的位数)
* 下一个参数是字符数,例:printf("%*d, 6, 123");
.number 小数点后的位数
.* 下一个参数是小数点后的位数
  • hlL
hlL(类型修饰) 含义
hh 单个字节
h short
l long
ll long long
L long double
  • type
type 用于
i 或 d int
u unsigned int
o 八进制
x 十六进制
X 字母大写的十六进制
f 或 F float
e 或 E 指数
g float
G float
a 或 A 十六进制浮点数
c char
s 字符串
p 指针
n 读入/写出的个数

格式化输入

%[flag]type

  • flag
flag 含义
* 跳过
数字 最大字符数
hh char
h short
l long, double
ll long long
L long double
  • type
type 含义
d int
i 整数,也可以是16进制、8进制
u unsigned int
o 8进制
x 16进制
a, e, f, g float
c char
s 字符串
[…] 允许的字符(例:*[^,]是到,之前的所有字符都跳过)
p 指针

位运算

按位运算

  • &

    相同位上都为1,结果为1;否则结果为0

  • |

    相同位上至少一个是1,结果为1;否则结果为0

  • ~ 取反

    把1位变0,0位变1

  • ^ 异或

    如果两个位相等,结果为0;两个位不相等,结果为1。

    对一个变量用同一个值异或两次,变量不变(可用于加密)

移位运算

  • << 左移

    • i << j:i中所有的位向左移动j个位置,右边填入0

    • x <<= n 等价于x *= $2^n$

  • >> 右移

    • i >> j:i中所有的位向右移j个位置。unsigned类型,左边填入0;signed类型,左边填入原来的最高位
    • x >>= n 等价于x /= $2^n$