C语言的基本概念
变量名
传统的C语言用法中,变量名使用小写字母,符号常量名使用大写字母
类型限定符
类型限定符signed与unsigned可以用于限定char类型或任何整型。
signed
(默认)
如char类型signed char
,取值范围为-128~127unsigned
(无符号类型)
总是正值或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
3int 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语言的函数可以将声明和定义分离,从而顺利通过编译,如:
1 | void sum(int begin, int end); //声明,函数原型, 如果没有参数,括号内最好填写 void,更严谨 |
参数传递
- 调用函数时,给的值可以与参数类型不完全匹配,编译器自行转换类型,(Java则对类型转换要求严格)。
- C语言调用函数传参数时,是值传递。
指针与数组
一元运算符
&
获取变量的地址,它的操作数必须是变量,没有其他的运算包括其中。p = &c;
称p为“指向”c的指针。一元运算符
*
间接寻址运算符。*
作用于指针时,将访问指针所指向的变量。
指针
指针就是保存地址的变量
1 | int i; |
指针应用场景
交换两个变量的值
1
2
3
4
5void swap(int *pa, int *pb) {
int t = *pa;
*pa = *pb;
*pb = t;
}函数需要返回多个值,某些值就只能通过指针返回
指针与const
指针是const(在const前):表示一旦得到了某个变量的地址,不能再指向其他变量;不影响改变该地址上的值*
1
2
3int *const q = &i; //q是const
*q = 26; //所指变量的值可以更改
q++; //ERROR!所指的类型是const(在const后):表示不能通过指针去修改那个变量(*不会使那个变量成为const**)
1
2
3
4const 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 | char *str = "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)
打印一个字符
字符串的输入输出
1 | char string[8]; |
scanf读入一个单词(到空格、tab或回车为止)
program <infile
从输入文件infile
中读取字符。otherprogram | program
将程序otherprogram
的标准输出 通过管道,重定向到程序program的标准输入上。
位运算符
&
与|
或^
异或~
求反码>>
右移(高位出现的空位,原来高位是什么,就用什么补该空位;>>>
无符号右移,高位的空位用0补)
静态变量
外部
- 用
static
声明限定外部变量与函数,可以将对象的作用域限定为被编译源文件的剩余部分。 - 通过
static
限定外部对象,可以达到隐藏外部对象的目的。 - 如果把函数名声明为static类型,则该函数名除了对该函数的声明所在的文件可见外,其他文件都无法访问。
- 用
内部
static
类型的内部变量,不管其所在函数是否被调用,都会存在。(一直占据存储空间;自动变量:随着函数的 调用/退出 而 存在/消失)
寄存器变量
register
声明告诉编译器,它所声明的变量在程序中使用频率较高
结构(和java中的类 概念类似,只有变量没有函数)
关键字struct
引入结构声明。
结构类型
关键字struct
后面的名字是可选的,称为结构标记。
有结构标记
1
2
3
4
5
6struct point {
int x;
int y;
};
struct point pt = {320, 200}; //定义了一个struct point类型的变量pt
struct point pt2 = {.y = 200, .x = 320};无结构标记
1
2
3
4struct {
int x;
int y;
} p1, p2; //p1、p2都是无标记结构,里面有x和y
结构与函数
结构的合法操作只有几种:作为一个整体赋值和赋值(包括向函数传递参数以及从函数返回值),通过&
运算符取地址,访问其成员。
例:函数makepoint
,带有两个整型参数,并返回一个point类型的结构
1 | struct point makepoint(int x, int y) { |
结构指针的使用频度非常高,为了使用方便,C语言提供了另一种简写方式。假设p
是一个指向结构的指针,可以用p->结构成员
的形式(等价于(*p).结构成员
),引用相应的结构成员。
1 | struct point* getStruct(struct point *p) { |
C语言提供一个编译时的一元运算符sizeof,可以用来计算任一对象的长度。
sizeof 对象
和sizeof(类型名)
会返回一个整型值,等于指定对象或类型占用的存储空间字节数。
类型定义 (typedef)
typedef
用来建立新的 数据类型名。
例如,声明typedef int Length;
将Length定义为与int具有同等意义的名字,Length与类型int完全相同。
1 | typedef struct ADate {//ADate同样可以略去 |
动态分配内存
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的字符串长度,不包括结尾的0strcmp
比较字符串int strcmp (const char *s1, char *s2);
比较字符串的大小,返回两者 第一个不同的字符的差值strncmp
比较字符串int strncmp (const char *s1, const char *s2, size_t n)
比较字符串,n
为比较的字符数量,若前n个字符相同,返回0strcpy
复制char* strcpy(char *restrict dst, const char *restrict src);
把src的字符串复制给dstrestrict
表明src和dst不重叠,返回dst复制字符串的操作示例:
1
2char *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
3int 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 | typedef struct _node { |
构造链表
1 | typedef struct { |
全局变量
全局变量初始化
- 没有做初始化的全局变量会得到0值
- 指针会得到NULL值
- 只能用编译时刻已知的值来初始化全局变量
- 它们的初始化发生在main函数之前
如果函数内部存在与全局变量同名的变量,则全局变量被隐藏
静态本地变量
- 在本地变量定义时加上
static
修饰符就成为静态本地变量,没有做初始化的静态变量会得到0值 - 当函数离开的时候,静态本地变量会继续存在并保持其值
- 静态本地变量的初始化只在第一次进入该函数时,以后进入函数会保持上次离开的值
静态本地变量是特殊的全局变量
编译预处理指令
#
开头的是编译预处理指令
#define 用来定义宏
#define <名字> <值>
(结尾没有分号,不是C语句)- 名字必须是一个单词,值可以是各种东西
- 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值(文本替换)
宏
- 如果一个宏的值中有其他宏的名字,也会被替换
- 如果宏的值超过一行,最后一行之前的行末需要加
\
- 宏的值后边的注释不会被当作宏的值的一部分
1 |
预定义的宏
- _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
typedef struct _list {
Node* head;
Node* tail;
} List;
文件
格式化输出
%[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个位置,右边填入0x <<= n
等价于x *= $2^n$
>>
右移i >> j
:i中所有的位向右移j个位置。unsigned类型,左边填入0;signed类型,左边填入原来的最高位x >>= n
等价于x /= $2^n$