赞
踩
C 标识符是用来标识变量、函数,或任何其他用户自定义项目的名称。
标准:一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
注意:C 标识符内不允许出现标点字符,比如 @、$ 和 %。
举例(有效标识符):
mohd zara abc move_name a_123
myname50 _temp j a23b9 retVal
关键字不能作为常量名、变量名或其他标识符名称
关键字 | 说明 |
---|---|
auto | 声明自动变量 |
break | 跳出当前循环 |
case | 开关语句分支 |
char | 声明字符型变量或函数返回值类型 |
const | 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变 |
continue | 结束当前循环,开始下一轮循环 |
default | 开关语句中的"其它"分支 |
do | 循环语句的循环体 |
double | 声明双精度浮点型变量或函数返回值类型 |
else | 条件语句否定分支(与 if 连用) |
enum | 声明枚举类型 |
extern | 声明变量或函数是在其它文件或本文件的其他位置定义 |
float | 声明浮点型变量或函数返回值类型 |
for | 一种循环语句 |
goto | 无条件跳转语句 |
if | 条件语句 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数返回值类型 |
register | 声明寄存器变量 |
return | 子程序返回语句(可以带参数,也可不带参数) |
short | 声明短整型变量或函数 |
signed | 声明有符号类型变量或函数 |
sizeof | 计算数据类型或变量长度(即所占字节数) |
static | 声明静态变量 |
struct | 声明结构体类型 |
switch | 用于开关语句 |
typedef | 用以给数据类型取别名 |
unsigned | 声明无符号类型变量或函数 |
union | 声明共用体类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
volatile | 说明变量在程序执行中可被隐含地改变 |
while | 循环语句的循环条件 |
在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
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 |
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
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 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。
操作字符串的函数:
函数 | 功能 |
---|---|
strcpy(s1, s2); | 复制字符串 s2 到字符串 s1。 |
strcat(s1, s2); | 连接字符串 s2 到字符串 s1 的末尾。 |
strlen(s1); | 返回字符串 s1 的长度。 |
strcmp(s1, s2); | 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 |
strchr(s1, ch); | 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
strstr(s1, s2); | 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
假设变量 A 的值为 10,变量 B 的值为 20
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
– | 自减运算符,整数值减少 1 | A-- 将得到 9 |
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
假设变量 A 的值为 1,变量 B 的值为 0
运算符 | 描述 | 实例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | 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 |
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 |
& | 返回变量的地址。 | &a; 将给出变量的实际地址。 |
* | 指向一个变量。 | *a; 将指向一个变量。 |
? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
转义序列 | 含义 |
---|---|
\ | \ 字符 |
’ | ’ 字符 |
" | " 字符 |
? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。
C 语言中有三个地方可以声明变量:
全局变量与局部变量在内存中的区别:
int scanf(const char *format, …) 函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。
int printf(const char *format, …) 函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。
format 可以是一个简单的常量字符串,但是您可以分别指定 %s、%d、%c、%f 等来输出或读取字符串、整数、字符或浮点数。
**说明:**scanf() 期待输入的格式与您给出的 %s 和 %d 相同,这意味着您必须提供有效的输入,比如 “string integer”,如果您提供的是 “string string” 或 “integer integer”,它会被认为是错误的输入。另外,在读取字符串时,只要遇到一个空格,scanf() 就会停止读取,所以 “this is test” 对 scanf() 来说是三个字符串。
类型 | 合格的输入 | 参数的类型 |
---|---|---|
%a、%A | 读入一个浮点值(仅 C99 有效)。 | float * |
%c | 单个字符:读取下一个字符。如果指定了一个不为 1 的宽度 width,函数会读取 width 个字符,并通过参数传递,把它们存储在数组中连续位置。在末尾不会追加空字符。 | char * |
%d | 十进制整数:数字前面的 + 或 - 号是可选的。 | int * |
%e、%E、%f、%F、%g、%G | 浮点数:包含了一个小数点、一个可选的前置符号 + 或 -、一个可选的后置字符 e 或 E,以及一个十进制数字。两个有效的实例 -732.103 和 7.12e4 | float * |
%i | 读入十进制,八进制,十六进制整数 。 | int * |
%o | 八进制整数。 | int * |
%s | 字符串。这将读取连续字符,直到遇到一个空格字符(空格字符可以是空白、换行和制表符)。 | char * |
%u | 无符号的十进制整数。 | unsigned int * |
%x、%X | 十六进制整数。 | int * |
%p | 读入一个指针 。 | |
%[] | 扫描字符集合 。 | |
%% | 读 % 符号。 |
格式字符 | 意义 |
---|---|
d | 以十进制形式输出带符号整数(正数不输出符号) |
o | 以八进制形式输出无符号整数(不输出前缀0) |
x,X | 以十六进制形式输出无符号整数(不输出前缀Ox) |
u | 以十进制形式输出无符号整数 |
f | 以小数形式输出单、双精度实数 |
e,E | 以指数形式输出单、双精度实数 |
g,G | 以%f或%e中较短的输出宽度输出单、双精度实数 |
c | 输出单个字符 |
s | 输出字符串 |
p | 输出指针地址 |
lu | 32位无符号整数 |
llu | 64位无符号整数 |
基本型,用 %f
不指定输出类型的长度,用系统根据情况决定,一般是实数中的整数部分全部输出,小数部分输出六位。
指定数据宽度和小数位数,用 %m.nf
如%20.15f的格式声明,指定输出的数据占 20 列,其中包括 15 位小数。
输出的数据相左对齐,用 %-m.nf
在 m.n 前加一个负号,其作用与 %m.nf 形式作用基本相同,但当数据长度不长过 m 时,数据向左靠,右端补空格。
int getchar(void) 函数从屏幕读取下一个可用的字符,并把它返回为一个整数。这个函数在同一个时间内只会读取一个单一的字符。可以在循环内使用这个方法,以便从屏幕上读取多个字符。
int putchar(int c) 函数把字符输出到屏幕上,并返回相同的字符。这个函数在同一个时间内只会输出一个单一的字符。可以在循环内使用这个方法,以便在屏幕上输出多个字符。
这两个函数会等待输入一些文本,当输入一个文本并按下回车键时,程序会继续并只会读取一个单一的字符。
char *gets(char *s) 函数从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
int puts(const char *s) 函数把字符串 s 和一个尾随的换行符写入到 stdout。
这两个函数会等待输入一些文本,当输入一个文本并按下回车键时,程序会继续并读取一整行直到该行结束。
#define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false。
if(boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
if(boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
else
{
/* 如果布尔表达式为假将执行的语句 */
}
一个 if 语句后可跟一个可选的 else if…else 语句,这可用于测试多种条件。
一个 switch 语句允许测试一个变量等于多个值时的情况。每个值称为一个 case,且被测试的变量会对每个 switch case 进行检查。
具体语法:
switch(expression){
case constant-expression :
statement(s);
break; /* 可选的 */
case constant-expression :
statement(s);
break; /* 可选的 */
/* 您可以有任意数量的 case 语句 */
default : /* 可选的 */
statement(s);
}
switch 语句必须遵循以下的规则:
用来替代 if…else 语句。它的一般形式如下:
Exp1 ? Exp2 : Exp3;
其中,Exp1、Exp2 和 Exp3 是表达式。? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个表达式的值。
当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
while(condition)
{
statement(s);
}
condition 可以是任意的表达式,当为任意非零值时都为 true。当条件为 true 时执行循环。 当条件为 false(零值) 时,退出循环,程序流将继续执行紧接着循环的下一条语句。在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。
while 循环的关键点是循环可能一次都不会执行。当条件为 false 时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句。
多次执行一个语句序列,简化管理循环变量的代码。
for ( init; condition; increment )
{
statement(s);
}
for循环控制流
不像 for 和 while 循环,它们是在循环头部测试循环条件。在 C 语言中,do…while 循环是在循环的尾部检查它的条件。
do…while 循环与 while 循环类似,但是 do…while 循环会确保至少执行一次循环。
do
{
statement(s);
}while( condition );
注意:条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。
如果条件为真,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为假为止。
终止循环或 switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。
用法:
告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
continue不是强制终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。
对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 while 和 do…while 循环,continue 语句重新执行条件判断语句。
将控制转移到被标记的语句。 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。
#include <stdio.h> double DoSqrt(double z) { double a=1; double b=0; double c=0; do { if(b*b<z) { b+=a; } else { c=b; b-=a; a/=10; } } while(a>0.000001); return (b+c)/2; } int main(){ double x, y; printf("请输入一个数字:"); scanf("%lf", &x); if(x<0){ printf("输入错误。"); } else { y=DoSqrt(x); printf("%g 的算数平方根为: %g.\n", x, y); } return 0; }
#include<stdio.h> #include<math.h> int main(){ int i,j; printf("100以内的素数有:\n"); for(i=2;i<100;i++){ for(j=2;j<sqrt(i);j++){ if(i%j==0){ break; } } if(j>sqrt(i)){ printf("%d,\t",i); } } }
数组是用来存储一系列数据,但它往往被认为是一系列固定大小的相同类型变量的顺序集合。
数组中的特定元素可以通过索引访问,第一个索引值为 0。
声明一个数组,需要指定元素的类型和元素的数量,如下所示:
type arrayName [ arraySize ];
arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C 数据类型。
// 初始化语句
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。如果省略掉了数组的大小,数组的大小则为初始化时元素的个数。
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
// 为数组中某个元素赋值
balance[4] = 50.0;
所有的数组都是以 0 作为它们第一个元素的索引,也被称为基索引,数组的最后一个索引是数组的总大小减去 1。
数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如:
double salary = balance[9];
可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
如果想要在函数中传递一个一维数组作为参数,必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,也可以传递一个多维数组作为形式参数。
void myFunction(int *param)
{
...
}
void myFunction(int param[10])
{
...
}
void myFunction(int param[])
{
...
}
就函数而言,数组的长度是无关紧要的,因为 C 不会对形式参数执行边界检查。
C 语言不允许返回一个完整的数组作为函数的参数。但是,可以通过指定不带索引的数组名来返回一个指向数组的指针。
如果想要从函数返回一个一维数组,必须声明一个返回指针的函数,如下:
int * myFunction()
{
...
}
可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。
数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:
double balance[50];
balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。
因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:
double *p;
double balance[10];
p = balance;
使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。
在我们没有明确数组的元素个数时,在程序中想知道数组单元个数可以使用 sizeof(a)/sizeof(a[0])。sizeof(a) 是得到数组 a 的大小,sizeof(a[0]) 是得到数组 a 中单个元素的大小(因此可以不必要是a[0],a[i]都行)
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
return_type function_name( parameter list )
{
body of the function
}
函数组成部分:
函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。在函数声明中,参数的名称并不重要,只有参数的类型是必需的。
return_type function_name( parameter list );
当在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,应该在调用函数的文件顶部声明函数并且在顶部引用定义函数的文件。
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有两种向函数传递参数的方式:
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。 |
引用调用 | 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。 |
默认情况下,C 语言使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。
递归指的是在函数的定义中使用函数自身的方法。
使用递归求给定数的阶乘
#include <stdio.h>
double factorial(unsigned int i)
{
if(i <= 1) {
return 1;
}
return i * factorial(i - 1);
}
int main()
{
int i = 15;
printf("%d 的阶乘为 %f\n", i, factorial(i));
return 0;
}
运行结果:
15 的阶乘为 1307674368000.000000
不使用递归求阶乘源码:C-study/function.cpp at main · Nate-yu/C-study (github.com)
#include <stdio.h> int fibonaci(int i) { if(i == 0) { return 0; } if(i == 1) { return 1; } return fibonaci(i-1) + fibonaci(i-2); } int main() { int i; for (i = 0; i < 10; i++) { printf("%d\t\n", fibonaci(i)); } return 0; }
运行结果:
0
1
1
2
3
5
8
13
21
34
指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
指针变量声明的一般形式为:
type *var_name;
type 是指针的基类型,它必须是一个有效的 C 数据类型,var_name 是指针变量的名称。用来声明指针的星号 ***** 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
实例代码:
#include <stdio.h> int main () { int var = 20; /* 实际变量的声明 */ int *ip; /* 指针变量的声明 */ ip = &var; /* 在指针变量中存储 var 的地址 */ printf("var 变量的地址: %p\n", &var ); /* 在指针变量中存储的地址 */ printf("ip 变量存储的地址: %p\n", ip ); /* 使用指针访问值 */ printf("*ip 变量的值: %d\n", *ip ); return 0; }
运行结果:
var 变量的地址: 0x7ffeeef168d8
ip 变量存储的地址: 0x7ffeeef168d8
*ip 变量的值: 20
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **var;
通过传递指针给函数,可以直接修改原参数(实参),而不是引用实参到形参。
int * myFunction()
{
...
}
C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量.
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
#define MAX_ARRAY_LENGTH 20
这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 替换为 20。使用 #define 定义常量来增强可读性。
#include <stdio.h>
#include "myheader.h"
这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
#undef FILE_SIZE
#define FILE_SIZE 42
这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
#ifdef DEBUG
/* Your debugging statements here */
#endif
这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果您向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。它定义了 DEBUG,您可以在编译期间随时开启或关闭调试。
宏 | 描述 |
---|---|
DATE | 当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。 |
TIME | 当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。 |
FILE | 这会包含当前文件名,一个字符串常量。 |
LINE | 这会包含当前行号,一个十进制常量。 |
STDC | 当编译器以 ANSI 标准编译时,则定义为 1。 |
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。
#include <stdio.h>
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
int main(void)
{
message_for(Carole, Debra);
return 0;
}
运行结果:
Carole and Debra: We love you!
宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。
#include <stdio.h>
#define tokenpaster(n) printf ("token" #n " = %d", token##n)
int main(void)
{
int token34 = 40;
tokenpaster(34);
return 0;
}
运行结果
token34 = 40
这个实例会从编译器产生下列的实际输出:
printf ("token34 = %d", token34);
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。
int square(int x) {
return x * x;
}
我们可以使用宏重写上面的代码,如下:
#define square(x) ((x) * (x))
在使用带有参数的宏之前,必须使用 #define 指令定义。参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格。例如:
#define MAX(x,y) ((x) > (y) ? (x) : (y))
struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct tag {
member-list
member-list
member-list
...
} variable-list ;
在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。
示例代码:
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c //同时又声明了结构体变量s1 //这个结构体并没有标明其标签 struct { int a; char b; double c; } s1; //此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c //结构体的标签被命名为SIMPLE,没有声明变量 struct SIMPLE { int a; char b; double c; }; //用SIMPLE标签的结构体,另外声明了变量t1、t2、t3 struct SIMPLE t1, t2[20], *t3; //也可以用typedef创建新类型 typedef struct { int a; char b; double c; } Simple2; //现在可以用Simple2作为类型声明新的结构体变量 Simple2 u1, u2[20], *u3;
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针。
对结构体变量可以在定义时指定初始值。
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
为了访问结构的成员,使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。可以使用 struct 关键字来定义结构类型的变量。
可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。
可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符,如下所示:
struct_pointer->title;
在链表中有一个头指针变量,这个指针变量保存一个地址,通过这个地址来找到这个链表,头指针节点指向第一个节点,在链表中每个节点包含两个部分:数据部分和指针部分。虽然结构体不能含有与本身类型相同的结构,但是可以含有之相同类型结构的指针,这种定义是链表的基础,链表中每一项都包含在何处能找到下一项的信息。而最后一个节点的指针指向必须为空NULL,从链表的原理来看不用担心链表的长度会超出范围这种问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4pt4sNrY-1661851366629)(https://gitee.com/nate-yu/img-repository/raw/master/img/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20220821170931.png)]
typedef struct Node
{
int data; //数据域
struct Node* next; //指针域(指向节点的指针)
}Node;
typedef struct Node *LinkList;
算法思路:
算法实现:
/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法,栈)*/
void CreateListHead(LinkList *L,int n) {
LinkList p;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; // 建立一个带头结点的单链表
for(int i = 0; i < n; i++) {
p = (LinkList)malloc(sizeof(Node)); // 生成新结点
p->data = rand()%100+1; // 随机生成100以内的数字
p->next = (*L)->next; // 将L的后继结点(头结点)赋值给p(新结点)的后继
(*L)->next = p; // 插入到表头
}
}
算法思路:
算法实现:
/*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法,队列)*/
void CreateListTail(LinkList *L, int n) {
LinkList p,tail;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
tail = *L; // tail为指向尾部的结点
for(int i = 0; i < n; i++) {
p = (Node *) malloc(sizeof(Node));
p->data = rand() % 100 +1;
tail->next = p; // 将表尾终端结点的指针指向新结点
tail = p; // 将当前的新结点定义为表尾终端结点
}
tail->next = NULL; // 循环结束后,将当前结点的指针域置空,表示当前链表结束
}
算法思路:
s->next = p->next; p->next = s;
算法实现:
/*在L中第i个结点位置之前(即在第i个位置插入数据)插入新的数据元素e,L的长度加1*/ bool ListInsert(LinkList *L, int i, int e) { int j; LinkList p,s; p = *L; // p指向头结点 j = 1; // j代表有数据的第一个结点的 while(p && j < i) { // 当j指向i时,p刚好指向i结点的前一个位置,遍历寻找i-1个结点 p = p->next; ++j; } if (!p || j > i) return false; s = (LinkList)malloc(sizeof(Node)); // 生成新结点 s->data = e; /*开始插入新结点*/ s->next = p->next; // 将p的后继结点赋值给s的后继 p->next = s; // 将s赋值给p的后继 return true; }
算法思路:
算法实现:
/*删除L的第i个结点,并用e返回其值,L的长度减1*/ bool ListDelete(LinkList *L, int i) { LinkList p,q; int j; p = *L; j = 1; while(p->next && j < i) { // 遍历寻找第i-1个结点 p = p->next; ++j; } if (!(p->next) || j > i) return false; /*开始删除第i个结点*/ q = p->next; p->next = q->next; // 将q的后继结点赋值给p的后继 free(q); return true; }
共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
为了定义共用体,您必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。
下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
union Data
{
int i;
float f;
char str[20];
} data;
现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
为了访问共用体的成员,我们使用成员访问运算符(.)。成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。您可以使用 union 关键字来定义共用体类型的变量。
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 |
假设变量 A 的值为 60(0011 1100B),变量 B 的值为 13(0000 1101B)
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与操作,按二进制位进行"与"运算。运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1; | (A & B) 将得到 12,即为 0000 1100 |
| | 按位或运算符,按二进制位进行"或"运算。运算规则:`0 | 0=0; 0 |
^ | 异或运算符,按二进制位进行"异或"运算。运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0; | (A ^ B) 将得到 49,即为 0011 0001 |
~ | 取反运算符,按二进制位进行"取反"运算。运算规则:~1=-2; ~0=-1; | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
<< | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 |
#include <stdio.h> int main() { unsigned int a = 60; /* 60 = 0011 1100 */ unsigned int b = 13; /* 13 = 0000 1101 */ int c = 0; c = a & b; /* 12 = 0000 1100 */ printf("Line 1 - c 的值是 %d\n", c ); c = a | b; /* 61 = 0011 1101 */ printf("Line 2 - c 的值是 %d\n", c ); c = a ^ b; /* 49 = 0011 0001 */ printf("Line 3 - c 的值是 %d\n", c ); c = ~a; /*-61 = 1100 0011 */ printf("Line 4 - c 的值是 %d\n", c ); c = a << 2; /* 240 = 1111 0000 */ printf("Line 5 - c 的值是 %d\n", c ); c = a >> 2; /* 15 = 0000 1111 */ printf("Line 6 - c 的值是 %d\n", c ); }
输出结果:
Line 1 - c 的值是 12
Line 2 - c 的值是 61
Line 3 - c 的值是 49
Line 4 - c 的值是 -61
Line 5 - c 的值是 240
Line 6 - c 的值是 15
^
来交换两个数的值unsigned int a=60; //0011 1100
unsigned int b=13; //0000 1101
a=a^b; //a=a^b=0011 0001
b=a^b; //b=a^b=0011 1100 相当于b1=(a^b)^b
a=a^b; //a=a^b=0000 1101 相当于a1=(a^b)^((a^b)^b)
// 仅用一行代码
a^=b^=a^=b;
&
运算判断一个整数是否是2的整数次幂int func(int num)
{
return ((num > 0) && ((num & (num - 1)) == 0));//2的n次幂大于0
}
可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。
FILE *fopen( const char *filename, const char *mode );
filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
把字符写入到流中的最简单的函数:
int fputc( int c, FILE *fp );
函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。
可以使用下面的函数来把一个以 null 结尾的字符串写入到流中:
int fputs( const char *s, FILE *fp );
函数 fputs() 把字符串 s 写入到 fp 所指向的输出流中。如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。
也可以使用 int fprintf(FILE *fp,const char *format, …) 函数把一个字符串写入到文件中。
从文件读取单个字符的最简单的函数:
int fgetc( FILE * fp );
fgetc() 函数从 fp 所指向的输入文件中读取一个字符。返回值是读取的字符,如果发生错误则返回 EOF。
下面的函数允许您从流中读取一个字符串:
char *fgets( char *buf, int n, FILE *fp );
函数 fgets() 从 fp 所指向的输入流中读取 n - 1 个字符。它会把读取的字符串复制到缓冲区 buf,并在最后追加一个 null 字符来终止字符串。如果这个函数在读取最后一个字符之前就遇到一个换行符 ‘\n’ 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。
也可以使用 int fscanf(FILE *fp, const char *format, …) 函数来从文件中读取字符串,但是在遇到第一个空格和换行符时,它会停止读取。
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写,通常是数组或结构体。
冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。
#include <stdio.h> void bubble_sort(int arr[], int len) { int i, j, temp; // i表示要循环len-1趟 for (i = 0; i < len - 1; i++) // 由于每一趟冒泡排序后会确定一个最大的数,所以每一趟的比较次数递减 for (j = 0; j < len - 1 - i; j++) if (arr[j] > arr[j + 1]) { temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } int main() { int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 }; int len = (int) sizeof(arr) / sizeof(*arr); bubble_sort(arr, len); int i; for (i = 0; i < len; i++) printf("%d ", arr[i]); return 0; }
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
void selection_sort(int a[], int len) { int i,j,temp; // i为当前位置 for (i = 0 ; i < len - 1 ; i++) { int min = i; // 记录最小值,第一个元素默认最小 for (j = i + 1; j < len; j++) // 访问未排序的元素 { if (a[j] < a[min]) // 找到目前最小值 { min = j; // 记录最小值的下标 } } if(min != i) // 当记录的最小值下标与当前下标不一样时,交换二者,使最小值位于当前位置 { temp=a[min]; // 交换两个变量 a[min]=a[i]; a[i]=temp; } /* swap(&a[min], &a[i]); */ // 使用自定义函数交換 } } /* void swap(int *a,int *b) // 交换两个变量 { int temp = *a; *a = *b; *b = temp; } */
插入排序(英语:Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到 O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
void insertion_sort(int arr[], int len){ // 当数组只有一个元素时,不进行排序 if(len < 2) return; int i; // 需要排序元素的计数器 int j; // 插入排序时,需要后移的元素的计数器 int temp; // 当前需要排序的元素的值 // 从第二个元素开始 for (i=1;i<len;i++){ // 待排序元素 temp = arr[i]; // 从已排序的数据的最右边开始,把大于当前待排序元素的元素后移,即从后往前比较 for (j=i-1;j>=0; j--) { if(arr[j] <= temp) break; arr[j+1] = arr[j]; } // 插入当前排序元素 arr[j+1] = temp; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。