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) - int a[10] = { [0] = 2, [2] = 3, 6, };- 用[n]在初始化数据中给出定位
- 没有定位的数据接在前面的位置后面
- 其他位置的值补零
- 也可以不给出数组大小,让编译器算
- 特别适合初始数据稀疏的数组
 
- 用
数组的大小
sizeof(a)/sizeof(a[0])
- sizeof给出整个数组所占据的内容的大小,单位是字节
- sizeof(a[0])给出数组中单个元素的大小,相除就得到了数组的单元个数
- 数组作为函数的参数时 实际是指针(数组的地址),需要用另一个参数来传入数组的大小- 不能在[]中给出数组的大小
- 不能再利用sizeof来计算数组的元素个数
 
- 不能在
数组的赋值
- 数组变量本身不能被赋值
- 要把一个数组的所有元素交给另一个数组,必须采用遍历
字符类型
char是一种整数,也是一种特殊的类型——字符。
- ''也是一个字符
- printf和- scanf里用- %c来输入输出字符
逃逸字符
| 字符 | 意义 | 
|---|---|
| \b | 回退一格 | 
| \t | 到下一个表格位 | 
| \n | 换行 | 
| \r | 回车 | 
| \" | 双引号 | 
| \' | 单引号 | 
| \\ | 反斜杠本身 | 
函数
C语言的编译器自上而下,按顺序分析代码。
函数的先后顺序很重要。
C语言的函数可以将声明和定义分离,从而顺利通过编译,如:
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的指针。
- 一元运算符 - *
 间接寻址运算符。- *作用于指针时,将访问指针所指向的变量。
指针
指针就是保存地址的变量
int i;
int *p = &i;
int *p,q; //p为指针,值为所指向的地址;*p是int类型变量,为所指向地址上的值;指针应用场景
- 交换两个变量的值 - void swap(int *pa, int *pb) { int t = *pa; *pa = *pb; *pb = t; }
- 函数需要返回多个值,某些值就只能通过指针返回 
指针与const
- 指针是const(*在const前):表示一旦得到了某个变量的地址,不能再指向其他变量;不影响改变该地址上的值 - int *const q = &i; //q是const *q = 26; //所指变量的值可以更改 q++; //ERROR!
- 所指的类型是const(*在const后):表示不能通过指针去修改那个变量(不会使那个变量成为const) - 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里有很多处理字符串的函数
字符串变量
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所指的字符串做写入会导致严重的后果
 
- 由于这个常量存储的地方,实际上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)
 打印一个字符
字符串的输入输出
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后面的名字是可选的,称为结构标记。
- 有结构标记 - struct point { int x; int y; }; struct point pt = {320, 200}; //定义了一个struct point类型的变量pt struct point pt2 = {.y = 200, .x = 320};
- 无结构标记 - struct { int x; int y; } p1, p2; //p1、p2都是无标记结构,里面有x和y
结构与函数
结构的合法操作只有几种:作为一个整体赋值和赋值(包括向函数传递参数以及从函数返回值),通过&运算符取地址,访问其成员。
例:函数makepoint,带有两个整型参数,并返回一个point类型的结构
struct point makepoint(int x, int y) {
	struct point temp;
	temp.x = x;
	temp.y = y;
	return temp;
}结构指针的使用频度非常高,为了使用方便,C语言提供了另一种简写方式。假设p是一个指向结构的指针,可以用p->结构成员的形式(等价于(*p).结构成员),引用相应的结构成员。
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完全相同。
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- 复制字符串的操作示例: - 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前面;
- 示例:
 - 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。
- 不同的枚举常量可以取相同的整数值,但最好采用唯一值,有助于预防难以发现的逻辑错误。 
链表
构造结点
typedef struct _node {
    int value;
    struct _node *next;
} Node;构造链表
typedef struct {
    Node *head;
} List;全局变量
全局变量初始化
- 没有做初始化的全局变量会得到0值- 指针会得到NULL值
 
- 只能用编译时刻已知的值来初始化全局变量
- 它们的初始化发生在main函数之前
如果函数内部存在与全局变量同名的变量,则全局变量被隐藏
静态本地变量
- 在本地变量定义时加上static修饰符就成为静态本地变量,没有做初始化的静态变量会得到0值
- 当函数离开的时候,静态本地变量会继续存在并保持其值
- 静态本地变量的初始化只在第一次进入该函数时,以后进入函数会保持上次离开的值
静态本地变量是特殊的全局变量
编译预处理指令
- #开头的是编译预处理指令
#define 用来定义宏
- #define <名字> <值>(结尾没有分号,不是C语句)
- 名字必须是一个单词,值可以是各种东西
- 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值(文本替换)
宏
- 如果一个宏的值中有其他宏的名字,也会被替换
- 如果宏的值超过一行,最后一行之前的行末需要加\
- 宏的值后边的注释不会被当作宏的值的一部分
#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一次- 例: - #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$