变量名
传统的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是一种整数,也是一种特殊的类型——字符。
''
也是一个字符
printf
和scanf
里用%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);
int main() { 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语言调用函数传参数时,是值传递。
指针与数组
指针
指针就是保存地址的变量
1 2 3 4
| int i; int *p = &i;
int *p,q;
|
指针应用场景
交换两个变量的值
1 2 3 4 5
| void swap(int *pa, int *pb) { int t = *pa; *pa = *pb; *pb = t; }
|
函数需要返回多个值,某些值就只能通过指针返回
指针与const
指针是const(在const前):表示一旦得到了某个变量的地址,不能再指向其他变量;不影响改变该地址上的值*
1 2 3
| int *const q = &i; *q = 26; q++;
|
所指的类型是const(在const后):表示不能通过指针去修改那个变量(*不会使那个变量成为const**)
1 2 3 4
| const int *p = &i; *p = 26; i = 26; p = &j;
|
数组参数
以下四种函数原型等价
int sum(int *arr, int n);
int sum(int *, int);
int sum(int arr[], int n);
int sum(int [], int)
数组变量是特殊的指针
字符串
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 pt2 = {.y = 200, .x = 320};
|
无结构标记
1 2 3 4
| struct { int x; int y; } p1, p2;
|
结构与函数
结构的合法操作只有几种:作为一个整体赋值和赋值(包括向函数传递参数以及从函数返回值),通过&
运算符取地址,访问其成员。
例:函数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 { 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 *));
转换字符大小写
函数声明在<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值
- 只能用编译时刻已知的值来初始化全局变量
- 它们的初始化发生在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_ :时间
带参数的宏的原则
#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;
是变量的声明(不产生代码)
标准头文件结构
文件
格式化输出
%[flags][width][.prec][hlL]type
Flag |
含义 |
- |
左对齐 |
+ |
强制显示符号(正数会显示+) |
(space) |
正数留空 |
0 |
0填充 |
width或prec |
含义 |
number |
最小字符数(总长,包括小数点后的位数) |
* |
下一个参数是字符数,例:printf("%*d, 6, 123"); |
.number |
小数点后的位数 |
.* |
下一个参数是小数点后的位数 |
hlL(类型修饰) |
含义 |
hh |
单个字节 |
h |
short |
l |
long |
ll |
long long |
L |
long double |
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 |
含义 |
* |
跳过 |
数字 |
最大字符数 |
hh |
char |
h |
short |
l |
long, double |
ll |
long long |
L |
long double |
type |
含义 |
d |
int |
i |
整数,也可以是16进制、8进制 |
u |
unsigned int |
o |
8进制 |
x |
16进制 |
a, e, f, g |
float |
c |
char |
s |
字符串 |
[…] |
允许的字符(例:*[^,] 是到, 之前的所有字符都跳过) |
p |
指针 |
位运算
按位运算
移位运算
<<
左移
>>
右移
i >> j
:i中所有的位向右移j个位置。unsigned类型,左边填入0;signed类型,左边填入原来的最高位
x >>= n
等价于x /= $2^n$