C 语言是一种通用的高级语言,是用来与机器交流的一种语言,可移植性和执行效率都较高。
以 Hello World
程序为例,C程序结构主要包含以下部分:
#include // 系统路径下去找
#include "xxx.h" // 当前路径下去找
int main() // main实际上是一个地址0x0013161 别名 标识符
{
/* Hello World 注释 */
printf("Hello, World! \n");
if (a)
{
return -1;
}
return 0;
}
#include
是预处理指令,告诉编译器在编译前要包含 stdio.h
文件。int main()
是主函数,也是程序的入口,程序从这里开始执行。/* Hello World 注释 */
会被编译器忽略,因为注释是写给人看的。printf("Hello, World! \n")
是一个函数,可以在屏幕上打印Hello World
。return 0
表示程序运行结束并返回0
, 表示程序执行成功(成功只有返回0这一种情况,而错误则有很多种情况)。分号是语句结束符,相当于语文中的句号,表示一条语句结束,这样可以区分语句。
// 这是单行注释
/*
这是多行注释
*/
注释是写给人看的,编译器看到 //
或者 /**/
这样的,会自动忽略。
标识符就是用来标识变量、函数的,相当于为变量或者函数起一个名字。
命名规则是:以字母A-Z或a-z或下划线_
开始,后面跟字母或下划线_
或数字(0-9)。(命名不能跟关键字冲突!)
注意,命名中不能出现标点字符,比如@
、$
、%
,而且C语言命名是区分大小写的!(大小写敏感)
常用的命名法则——驼峰命名法,采取大小写字母混用的命名方式。// 自说明变量
int myStudentsCount;
。// count 计数public class DataBaseUser;
。关键字是C语言保留字(表示已被占用了),不能作为常量名、变量名或其他标识符名称。
关键字 | 说明 |
---|---|
auto | 声明自动变量(不常用) |
goto | 无条件跳转语句(不常用) |
volatile | 说明变量在程序执行中可被隐含地改变(不常用) |
register | 声明寄存器变量(不常用) |
union | 声明共用体类型(不常用) |
enum | 声明枚举类型 |
signed | 声明有符号类型变量或函数 |
unsigned | 声明无符号类型变量或函数 |
break | 跳出当前循环 |
case | 开关(switch)语句分支 |
char | 声明字符型变量或函数返回值类型 |
const | 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变 |
continue | 结束当前循环,开始下一轮循环 |
default | 开关语句中的"其它"分支 |
do | 循环语句的循环体 |
double | 声明双精度浮点型变量或函数返回值类型 |
else | 条件语句否定分支(与 if 连用) |
extern | 声明变量或函数是在其它文件或本文件的其他位置定义 |
float | 声明浮点型变量或函数返回值类型 |
for | 一种循环语句 |
if | 条件语句 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数返回值类型 |
return | 子程序返回语句(可以带参数,也可不带参数) |
short | 声明短整型变量或函数 |
sizeof | 计算数据类型或变量长度(即所占字节数) |
static | 声明静态变量 |
struct | 声明结构体类型 |
switch | 用于开关语句 |
typedef | 用以给数据类型取别名 #define N 10 |
void | 声明函数无返回值或无参数,声明无类型指针 |
while | 循环语句的循环条件 |
空格是编译器用来区分各个元素的,比如int a = 0;
,int
和 a
之间就至少有一个空格,用于区分。
当然,为了代码的可读性,通常我们会适当的多加些空格,这样看起来不会很累。
比如int a = 0;
就比 int a=0;
看起来舒服点,字符间不会显得很密集。
sizeof
可以得到对象或类型的存储大小。
int a[5];// sizeof(a) = 20 sizeof(a[0]) = 4
1GB = 1024MB
1MB = 1024KB
1KB = 1024Byte
1Byte = 8bit
1111 1111 = 255
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
关于存储,在计算机中是以二进制形式存储的,也就是说计算机只认识0
或者 1
。
以char为例,1字节(byte)=8位(bit),存储大小对应的是2的8次方,即256个位置,刚好存放256个数。
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
类型 | 描述 |
---|---|
函数返回为空 | C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status); |
函数参数为空 | C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void); |
指针指向 void | 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。 |
变量定义:变量类型 变量名1, 变量名2, 变量名3;
int i, j, k;
注意,命名不能取保留的关键字,必须以下划线或大小写字母开头!
变量声明:一般使用extern关键字来声明,声明表示该变量已经定义,告诉别人该变量已经定义。
// main.c
int add(int a, int b);// 声明
int a; // 全局
int main(void)
{
add(1, 2);
return 0;
}
// 定义
int add(int a, int b)
{
return (a + b);
}
// 说明i已经在其他C文件被定义了,在这里声明是为了使用它
extern int i;
一般定义了,也就声明了,同一个变量,只能定义一次,但可以在外部文件被声明多次。
// a.c
extern int a;// 外部声明
...
// 有效语句
int a = 10;
int b = a;
// 无效语句
10 = 20;
10 = a;
常量,即固定不变的值,定义后不能修改。
整数常量一般为十进制、八进制或十六进制。
前缀用来指定进制,0x或0X表示十六进制,0表示八进制,无前缀则默认为十进制。
后缀用来指定无符号或长整数,U表示无符号整数(unsigned),L表示长整数(long),后缀大小写随意,但一般为大写。
85 /* 十进制 */
0213 /* 八进制 */
0x4b /* 十六进制 */
30 /* 整数 */
30U /* 无符号整数 */
30L /* 长整数 */
30UL /* 无符号长整数 */
浮点常量由整数部分、小数点、小数部分和指数部分组成,可以用小数形式或指数形式来表示浮点常量。
3.14159 /* 合法的 */
314159E-5L /* 合法的 */
510E /* 非法的:不完整的指数 */
210f /* 非法的:没有小数或指数 */
.e55 /* 非法的:缺少整数或分数 */
E代表10为底数,-5
为指数,后缀为L代表 long double
,后缀为F代表 float
。
字符常量可以为单个的普通字符(‘x’)、一个转义序列(‘\t’),或一个通用字符(‘\u02C0’)。
printf("123\r\n456");
123
456
printf("123\r456");
123
456
转义序列 | 含义 |
---|---|
\ | \ 字符 |
’ | ’ 字符 |
" | " 字符 |
? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
字符串常量是由""
括起来的,一个字符串可以包含普通的字符、转义序列和通用的字符。
也可以使用空格做分隔符,把很长的字符串常量进行分行。
// 下面三种定义都是一样的效果
"hello,world"
"hello,\
world"
"hello," "w" "orld"
#define PI 3.14
#define WIDTH 10
#define LENGTH 20
const int i = 10;// const修饰,表示不能被改
str = "123";// 重要
int func(const char *str)
{
// 只给看看 123
str = 456
str -> 123
...
}
以A=10,B=20为例:
printf("A++ = %d\n", A++);// 10
printf("A = %d\n", A); // 11
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
– | 自减运算符,整数值减少 1 | A-- 将得到 9 |
注意,如果打印A++,那么打印值仍为10,A++表示使用后再加1,而如果是打印++A,那么打印值为11,表示先加1,然后再使用。
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
以A=0,B=1为例:
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
&
、|
、^
(与、或、异或)的真值表如下:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
int c = 4; // 4 --- 0000 0100
c = c << 2;// --- 0001 0000
c = c & 2;// 0000 0100
// 0000 0010
// 0000 0000
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量的地址。 | &a; 将给出变量的实际地址。 |
* | 指向一个变量。 | *a; 将指向一个变量。 |
? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
C 语言中,判断决定了程序会执行哪一部分,条件为真时应该执行什么,为假时又应该执行什么。
语句 | 描述 |
---|---|
if 语句 | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
if…else 语句 | 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
嵌套 if 语句 | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
switch 语句 | 一个 switch 语句允许测试一个变量等于多个值时的情况。 |
嵌套 switch 语句 | 您可以在一个 switch 语句内使用另一个 switch 语句。 |
switch (s1)
{
case 1:
switch (s2)
{
case 4:break;
default:break;
}
break;
case 2:break;
case 3:break;
default:break;
}
循环类型 | 描述 |
---|---|
while 循环 | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
for 循环 | 多次执行一个语句序列,简化管理循环变量的代码。 |
do…while 循环 | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
控制语句 | 描述 |
---|---|
break 语句 | 终止循环或 switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。 |
continue 语句 | 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。 |
goto 语句 | 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。 |
// 函数返回类型 + 函数名 + 函数参数
int function(int para)
{
/* 函数主体 */
...
rertun 0;
}
// 函数返回类型 + 函数名 + 函数参数 + ;
int function(int para);
// 函数名 + 函数实参
function(10);
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。 |
传址调用 | 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。 |
int x = 3, y = 4;
// 传值 交换失败!
swap1(x, y);
int swap1(int a, int b)
{
int t;
t = a;
a = b;
b = t;
}
// 传址 交换成功!
swap2(&x, &y);
int swap2(int *p1, int *p2)
{
int t;
t = *p1;
*p1 = *p2;
*p2 = t;
}
一些数据元素的组合,按照顺序存储在内存中,通过下标索引来访问数据元素。(集合)
int a;
int a[5] = {1,2,3,4,5};
a[0] -- a[4]
int a[5] = {1, 2, 3, 4, 5};
for (int i = 0;i < 5;i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
/*
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
*/
int *p = NULL; // 定义一个指针变量 p,可存放地址
int a = 3; // 定义一个整型变量 a,存放的值为3
p = &a; // 将 a 的地址赋给 p
printf("*p = %d\n", *p);// *p 为解引用,找到 p 存放的 a 地址然后打印里面的值
/*
实例讲解:
int *p 给一个抽屉取名为p,我们叫它抽屉p,它存的的是int *类型,也就是地址
int a = 3 给一个抽屉取名为a,我们叫它抽屉a,它存的是int类型,也就是整数类型
p = &a 往抽屉p里放了一把抽屉a的钥匙
*p 打开抽屉p取东西,取出来的是抽屉a的钥匙,所以打印*p,实际上就是打印抽屉a里存的值
*/
int *p = NULL; // 没有NULL,p就是一个野指针
p = (int *)malloc(sizeof(int));
if (NULL == p)
{
return -1;
}
printf("p = 0x%p\n", p); // p = 0x0
赋为 NULL 值的指针被称为空指针,它指向地址为0的内存,而在操作系统中,地址为0的内存是保留的,是不允许被访问的。
二重指针其实就是嵌套了一层。
int *p1 = NULL; // 给一个抽屉取名为p1,里面放的是int *类型,是地址
int **p2 = NULL; // 给一个抽屉取名为p2,里面放的是int **类型,也是地址
int a = 3; // 给一个抽屉取名为a,里面放的是int类型,也就是整数类型
p1 = &a; // 往抽屉p1里放抽屉a的钥匙
p2 = &p1; // 往抽屉p2里放抽屉p1的钥匙
printf("*p2 = %p\n", *p2); // 打开抽屉p2取东西,取出来的是抽屉p1的钥匙,是地址
printf("**p2 = %d\n", **p2);// 打开抽屉p2,拿到抽屉p1的钥匙后,再去打开抽屉p1,取出来的就是a,是整数
函数指针,从字面意思上来看,就是存储函数地址的指针变量。
int add(int x, int y)
{
return (x + y);
}
int main(void)
{
int (*pFunc)(int, int) = add;
int sum = pFunc(1, 2);
printf("sum = %d\n", sum); // sum = 3
return 0;
}
回调函数其实是函数指针的一个应用,当你调用函数A时,需要把函数B当作A的参数传进去,那么此时,函数B就叫回调函数。
#include
#include
// 往数组里填充随机值
void populate_array(int *array, int arraySize, int (*getNextValue)(void))
{
for (int i = 0; i < arraySize; i++)
array[i] = getNextValue();// 假设我有个函数,已经实现了,而且就存在函数指针里
}
// 获取随机值 回调函数
// 回调函数可以自由修改,比如设置随机值范围
// 回过头去调用的函数
// rand()%100 + 1 [1, 100]
int getNextRandomValue(void)
{
return rand();// 返回一个随机值,系统函数
}
int main(void)
{
int myarray[10];
// 往数组里填充 10 个随即值
// &a[0] --- 数组首元素地址
// myarray --- 数组地址
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
char str1[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; // 输出 hello
char str2[] = "hello"; // 输出 hello
函数 | 目的 |
---|---|
strcpy(s1, s2); | 复制字符串 s2 到字符串 s1。 |
strcat(s1, s2); | 连接字符串 s2 到字符串 s1 的末尾。 |
strlen(s1); | 返回字符串 s1 的长度。 |
strcmp(s1, s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1 |
strchr(s1, ch); | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
strstr(s1, s2); | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
double getAverage(int num, ...)
{
va_list args; // 定义一个va_list变量 args
double sum = 0.0;
int i;
va_start(args, num); // 初始化参数列表 num代表传入参数总数
for (i = 0;i < num;i++)
{
sum += va_arg(args, int); // va_arg可以访问参数列表里的每一个参数
}
va_end(args); // 清理分配给va_list变量的内存
return sum/num;
}
// ./test.exe 10 20
// argc = 3
// argv[0] = ./test.exe
// argv[1] = 10
// argv[2] = 20
int main( int argc, char *argv[] )
{
for (int i = 0;i < argc;i++)
{
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
函数 | 描述 |
---|---|
void *calloc(int num, int size); | 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。 |
void free(void *address); | 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 |
void *malloc(int num); | 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。 |
void *realloc(void *address, int newsize); | 该函数重新分配内存,把内存扩展到 newsize。 |
char *p = (char *)malloc(100*sizeof(char)); // 动态分配内存
if (NULL == p) // 判断分配结果
{
printf("malloc error!\n");
return -1;
}
memset(p, 0, 100); // 清理分配的内存
... // 使用分配的内存,如复制字符串之类的
free(p); // 释放内存
// 声明于函数或块内部的变量
int main(void)
{
int a; // 局部变量声明
a = 10; // 局部变量初始化
return 0;
}
int g; // 定义与函数外部(一般在程序顶部),任意函数都能访问
int main(void)
{
int a = 10, b = 20;
g = a + b;
printf("g = %d\n", g);
return 0;
}
注意:在函数内,如果全局变量名和局部变量名相同,会使用局部变量值(就近原则)。
// 枚举也是一种数据类型,字面意思就是把一个个可能用到的数据都列出来
// 比如定义一星期
// 1. 采用宏定义方式
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
// 2. 采用枚举方式(更简洁)
enum DAY
{
MON = 1, // 默认从0开始的,这里让 MON = 1
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
enum DAY day;
day = WED;
printf("day = %d\n", day); // day = 3
// 结构体变量初始化
struct EmployeeInfo
{
int id; // 职工编号
char name[20]; // 职工姓名
}employee = {123, "Jack"};
// 访问结构体成员 使用 . 来访问
printf("id:%d, name:%s\n", employee.id, employee.name);
// 指向结构的指针
void printEmployee(struct EmployeeInfo *employee)
{
printf("id: %d, name:%s\n", employee->id, employee->name);
}
int main(void)
{
struct EmployeeInfo em1;
em1.id = 123;
strcpy(em1.name, "Jack");
printEmployee(&em1); // 通过传地址来输出信息
return 0;
}
递归就是函数自己调用自己。
// 斐波那契数列 1 1 2 3 5 8 13 21 34
int fibonaci(int i)
{
if (0 == i)
{
return 0;
}
if (1 == i)
{
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
for (int i = 1;i < 10;i++)
{
printf("%d\n", fibonaci(i));
}
void swap(int *p1, int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void bubble_sort(int arr[], int len)
{
int i, j;
for (i = 0; i < len - 1; i++) // 比较趟数,最多len-1趟
{
for (j = 0; j < len - 1 - i; j++) // 每趟比较次数
{
if (arr[j] > arr[j + 1]) // 需要交换
{
swap(&arr[j], &arr[j + 1]);
}
}
}
}
void swap(int *p1, int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void select_sort(int arr[], int len)
{
int i, j, min;
for (i = 0 ; i < len - 1 ; i++)
{
min = i; // 记录最小值,第一个元素默认最小
for (j = i + 1; j < len; j++) // 访问未排序的元素
{
if (a[j] < a[min]) // 找到目前最小值
{
min = j; // 记录最小值
}
}
if(min != i) // 需要交换
{
swap(&a[min], &a[i]);
}
}
}