《C程序设计语言》阅读笔记

导言

文件复制

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main()
{
int a; //任何整形(int)也可以存储字符型数据
a=getchar();
while(c!=EOF)
{ //没有输入时,getchar返回EOF(end of file),
//使用int型是为了存储文件结束符EOF,若用char则
//可能出现问题
putchar(c);
}
}

改进
while((c=getchar()!=EOF))

优先级上 != 大于 = ,因此:
c = getchar() != EOF
等价于语句
c = (getchar() != EOF)

EOF键盘输入为Ctrl + z

计数

字符计数

1
2
3
4
int n=0;
while(getchar() != EOF)
++n;
printf("%d",nc);

使用for:

1
2
3
4
int n;
for(n=0; getchar() != EOF; ++n)
;
printf("%d",n);

行计数

1
2
3
4
5
//统计换行符的个数“ \n ”
int c,n=0;
while((c = getchar()) != EOF)
if(c=='\n')
++n;

单词计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define IN 1
#define OUT 0
int main()
{
int c,nl=0,nw=0,nc=0,state=OUT;
while((c=getchar())!=EOF){
++nc;
if(c == '\n')
++nl;
if(c == ' ' || c == '\n' || c == '\t' )
state = OUT;
else if(state == OUT)
{
state=IN;
++nw;
}
}
printf("%d %d %d",nl,nw,nc);
}

数组

  • 有初始值的数组可以没有大小
  • 越界问题:
1
int a[5]; //不存在a[5], 它是第六个元素

数组的初始化

一维
  1. 对全部数组元素赋初值。
    int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

  2. 给数组中的⼀部分元素赋值,剩余的元素会⾃动赋值为 0。
    int a[10] = { 1, 2, 3, 4 }; 相当于 int a[10]={ 1, 2, 3, 4, 0, 0, 0, 0, 0, 0 } ;
    把数组全部元素设置为 0
    int a[10] = { 0 };

  3. 对数组全部元素赋初值时,可以省略数组的长度。
    int a[ ] = { 1, 2, 3, 4, 5 } ; 相当于 int a[5] = { 1, 2, 3, 4 ,5} ;

注意不能有:int a[5]={};或者 int a[5]={1,2,3,4,5,6};

二维
  1. 分⾏给⼆维数组赋初值
    int a[2][3] = {{1,2,3}, {4,5,6}};
    按⾏的顺序填充,内部{ }表示具体的某⼀⾏

  2. 按⾏排列顺序赋初值
    int b[2][3] = {1,2,3,4,5,6};
    按⾏的顺序依次填充

  3. 对部分元素赋初值
    int c1[3][4] = {{1},{5},{9}};
    int c2[3][4] = {{1},{0,6},{0,0,7}};
    int c3[3][4] = {{1},{},{9}};

  4. 在定义并赋初值时,可以省略第⼀维⼤⼩(不管⼏维数组都只能省略第⼀维⼤⼩)
    int d1[ ][4] = {1,2,3,4,5,6,7};
    int d2[ ][3] = {1,2,3,4,5,6,7};
    int d3[ ][5] = {1,2,3,4,5,6,7};

注意:有初始值的⼆维数组,不能省略第⼆维(列)的⼤小

memcpy 函数

格式:memcpy(目标地址,源地址,字节数);
例子图片Base64

数组地址

一维数组:

a a[0]的地址
a+1 a[1]的地址
&a[1] a[1]的地址

二维数组(a[i][j]):

a a[0][0]的地址
a+1 a[1][0]的地址(第二行)
a[i]+j a[i][j]的地址
&a[i][j] a[i][j]的地址

动态规划

函数

函数定义的一般形式为:

返回值类型 函数名(0 个或多个参数声明)
{
声明部分;
语句序列;
}
函数原型函数声明中参数名不要求相同。事实上,函数原型中的参数名是可选的,这样上面的函数原型也可以写成以下形式:
int power(int , int );
若为指针型应为:
int power(int* ,int* );
当把数组名用作参数时,传递给函数的值是数组起始元素的位置或地址——它并不复制数组元素本身。在被调用函数中,可以通过数组下标访问或修改数组元索的值。

四种存储类型

四种存储类型Base64

第一章习题

练习 1-22

编写一个程序,把较长的输入行“折”成短一些的两行或多行,折行的位置在输入行的第n 列之前的最后一个非空格之后。要保证程序能够智能地处理输入行很长以及在指定的列前没有空格或制表符时的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
//还需要改进多个空格连在一起的问题
#include<stdio.h>
#include<string.h>
#define Max 1024//最大输入字符数
#define zhe 10 //折断值
int getline(char* ,int );//从输入读取一行数据,maxsize是最大读入字符数目
void br(char* );//折断函数
void copy(char*, char*, int );

int main()
{
char mag[Max];
int len = 0;
while((len = getline(mag,Max))>0)
if(len>0)
br(mag);
return 0;
}

int getline(char line[ ],int maxsize)
{
int c, i;
for(i = 0; i < maxsize - 1 && (c=getchar()) != EOF && c != '\n'; ++i)//getchar()会多运行一次
line[i] = c;
if(c == '\n')
line[i++] = c;
line[i] = -1;
return i;

}

void br(char ge[ ])
{

int i, j;
int len = strlen(ge);
if(len <= zhe){
printf("%s\n", ge); // 如果字符串长度小于等于需要折断的长度,直接输出
return;
}
int hasBlank = 0;
for(i = zhe - 1; i >= 0; --i){
if(ge[i] == ' '){
hasBlank = 1;
continue;
}
if(ge[i] != ' ' && hasBlank){
if(i < zhe - 1)
ge[i + 1] = 0; // 找到空格前面的字符,将该字符后面的空格置为 '\0'
break;
}
}
int subSize = (len - zhe) > (len - i - 2) ? (len - zhe) : (len - i - 2);
char to[subSize]; // 将剩余字符拷贝至该数组
if(i < 0){
// 没有空格
for(j = 0; j < zhe; ++j)
putchar(ge[j]);
printf("\n");
copy(ge, to, zhe);
}else{
printf("%s\n", ge);
copy(ge, to, i + 2);
}
br(to);
}

void copy(char from[], char to[], int start)
{
int i = 0;
while(to[i] = from[start + i])
++i;
}

练习 1-23

编写一个删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许嵌套。

1

练习 1-24

编写一个程序,查找C语言程序中的基本语法错误,如圆括号、方括号、花括号不配对等。要正确处理引号(包括单引号和双引号)、转义字符序列与注释。(如果读者想把该程序编写成完全通用的程序,难度会比较大。)

1

类型、运算符与表达式

变量名

名字是由字母(’_’看作字母,但不能作为开头)和数字组成的序列,但其第一个字符必须为字母。

类似于 if、else、int、float 等关键字是保留给语言本身使用的,不能把它们用做变量名。
所有关健字中的字符都必须小写。

局部变量一般使用较短的变量名(尤其是循环控制变量),外部变量使用较长的名字。

数据类型及长度

数据类型 说明 存储空间(字节数) 实际存储
int 整型 4 整数
float 单精度浮点数 4 小数
double 双精度浮点数 8 高精度小数
char 字符型 1 ASCII字符

修饰符:
signed\unsigned\short\long (单独使用都为整数型)

类型 int char short long long long
字节数 4 1 2 4 8

常量

long 类型的常量以字母 l 或 L 结尾,如123456789L。后缀 ul 或 UL 表明是 unsigned long 类型。

带前缀 0 的整型常量表示它为八进制形式;前缀为 0x 或 0X,则表示它为十六进制形式。例如,十进制数 31 可以写成八进制形式 037,也可以写成十六进制形式 0x1f 或 0X1F。

*0X23B’FULL *🤣🤣🤣 ULL是后缀,“ ‘ ”是数字分隔符

ANSI C 语言中的全部转义字符序列如下所示:

转义字符
\a 响铃符 \\ 反斜杠
\b 回退符 \? 问号
\f 换页符 \‘ 单引号
\n 换行符 \“ 双引号
\r 回车符 \ooo 八进制数
\t 横向制表符 \xhh 十六进制数
\v 纵向制表符

‘ \ooo ‘ 中 ooo 代表 1~3 个八进制数字(0…7)。
‘ \xhh ‘ 中 hh 是一个或多个十六进制数字(0…9,a…f,A…F)

字符串常量

字符串常量就是字符数组。字符串的内部表示使用一个空字符’\0’作为串的结尾,因此。存储字符串的物理存储单元数比括在双引号中的字符数多一个。

标准库函数 strlen(s) 可以返回字符串参数 s 的长度,但长度不包括末尾的’\0’。

字符常量与仅包含一个字符的字符串之间的区别 :
‘x’与”x”是不同的。前者是一个整数,其值是字母 x 在机器字符集中对应的数值(内部表示值);后者是一个包含
一个字符(即字母 x)以及一个结束符’\0’的字符数组。

枚举变量

https://www.runoob.com/cprogramming/c-enum.html

枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量,它可以让数据更简洁,更易读。

枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。

定义一个枚举类型,需要使用 enum 关键字,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。

枚举语法定义格式为:

enum 枚举名 {枚举元素1,枚举元素2,……};

例:

1
2
3
4
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:

1
2
3
enum season {spring, summer=3, autumn, winter}; 
//没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

我们可以通过以下三种方式来定义枚举变量

1、先定义枚举类型,再定义枚举变量

1
2
3
4
5
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

2、定义枚举类型的同时定义枚举变量

1
2
3
4
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

3、省略枚举名称,直接定义枚举变量

1
2
3
4
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

以下实例将整数转换为枚举:

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>

int main()
{

enum day
{
saturday,
sunday,
monday,
tuesday,
wednesday,
thursday,
friday
} workday;

int a = 1;
enum day weekend;
weekend = ( enum day ) a; //类型转换
//weekend = a; //错误
printf("weekend:%d",weekend);
return 0;
}
```
### 声明
任何变量的声明都可以使用 const 限定符限定。该限定符指定变量的值不能被修改。
对数组而言,const 限定符指定数组所有元素的值都不能被修改:
```c
const double e = 2.71828182845905;
const char msg[] = "warning: ";

const 限定符也可配合数组参数使用,它表明函数不能修改数组元素的值:
int strlen(const char[]);

运算符

优先级:算术运算符 > 关系运算符 > 条件运算符 > 赋值运算符

类型转换

。一般来说,如果二元运算符(具有两个操作数的运算符称为二元运算符,比如+或*)的两个操作数具有不同的类型,那么在进行运算之前先要把“较低”的类型提升为“较高”的类型,运算的结果为较高的类型。

强制转换

(类型名) 表达式

在通常情况下,参数是通过函数原型声明的。这样,当函数被调用时,声明将对参数进
行自动强制转换。例如,对于 sqrt 的函数原型
double sqrt(double);
下列函数调用:
root = sqrt(2);
不需要使用强制类型转换运算符就可以自动将整数 2 强制转换为 double 类型的值 2.0。

1
2
3
4
5
6
7
8
9
#include<iostream>
int main()
{
double a=2.5,b=4.0,c=3.7;
std::cout<<(int)(a+b)/c<<std::endl;//1.62162
std::cout<<(int)a+b/c<<std::endl;//3.08108
std::cout<<(int)(a+b/c)<<std::endl;//3
std::cout<<(int)1.9;//整型 向下取整
}

自增自减

自增与自减运算符只能作用于
变量,类似于表达式(i+j)++是非法的。

按位操作符

符号 解释
& 按位与(AND)
| 按位或(OR)
^ 按位异或(XOR)
<< 左移
>> 右移
~ 按位求反(一元运算符 )

按位与运算符&经常用于屏蔽某些二进制位,例如:
n = n & 0177:
该语句将 n 中除 7 个低二进制位外的其它各位均置为 0。

移位运算符<<与>>分别用于将运算的左操作数左移与右移,移动的位数则由右操作数指定(右操作数的值必须是非负值)。因此,表达式 x << 2 将把 x 的值左移 2 位,右边空出的
2 位用 0 填补,该表达式等价于对左操作数乘以 4。
10 左移两位 变为 1000

在对 unsigned 类型的无符号值进行右移
位时,左边空出的部分将用 0 填补;当对 signed 类型的带符号值进行右移时,某些机器将
对左边空出的部分用符号位填补(即“算术移位”),而另一些机器则对左边空出的部分用 0
填补(即“逻辑移位”)。

按位与(&):

按位与操作是指对两个数的每一位进行比较,只有当两个数对应位都为1时,结果的对应位才为1,否则为0。
举例:5 & 3

5 的二进制表示为 101
3 的二进制表示为 011
执行按位与操作:

101 & 011

001

结果为 1,因为只有在第二位上两个数同时为1。

按位异或(^):

按位异或操作是指对两个数的每一位进行比较,如果两个数对应位不相同,则结果的对应位为1,如果相同则为0。
举例:5 ^ 3

5 的二进制表示为 101
3 的二进制表示为 011
执行按位异或操作:

101 ^ 011

110

结果为 6,因为在第一位和第三位上两个数的位不同。

按位或(|):

按位或操作是指对两个数的每一位进行比较,只要两个数对应位至少有一个为1,结果的对应位就为1。
举例:5 | 3

5 的二进制表示为 101
3 的二进制表示为 011
执行按位或操作:

101 | 011

111

结果为 7,因为只要有一个位置上为1,结果的对应位就为1

赋值运算符与表达式

赋值语句具有值,且可以用在表达式中。下面是最常见的一个
例子:
while ((c = getchar()) !=EOF)
其它赋值运算符(如+=、-=等)也可以用在表达式中,尽管这种用法比较少见。

条件表达式

(三元运算符)

运算优先级及求值次序

(如上图)
同大多数语言一样,C 语言没有指定同一运算符中多个操作数的计算顺序(&&、||、?:
和,运算符除外)。例如,在形如
x = f() + g();
的语句中,f()可以在 g()之前计算,也可以在 g()之后计算。因此,如果函数 f 或 g 改变
了另一个函数所使用的变量,那么 x 的结果可能会依赖于这两个函数的计算顺序。为了保证
特定的计算顺序,可以把中间结果保存在临时变量中。
类似地,C 语言也没有指定函数各参数的求值顺序。因此,下列语句

1
printf("%d %d\n", ++n, power(2, n)); // 错 

在不同的编译器中可能会产生不同的结果,这取决于 n 的自增运算在 power 调用之前还是之
后执行。解决的办法是把该语句改写成下列形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
++n; 
printf("%d %d\n", n, power(2, n));
```

## 控制流
### 语句与程序块

语句:表达式 **;**
### if-else 语句
每个 else 与最近的前一个没有 else 配对的 if 进行匹配。


歧义性在下面这种情况下尤为有害:
```c
if (n > 0)
for (i = 0; i < n; i++)
if (s[i] > 0) {
printf("...");
return i;
}
else /* WRONG */
printf("error -- n is negative\n");

程序的缩进结构明确地表明了设计意图,但编译器无法获得这一信息,它会将 else 部分与内层的 if 配对。这种错误很难发现,因此我们建议在有 if 语句嵌套的情况下使用花括号。

else-if 语句

switch 语句

break 语句将导致程序的执行立即从 switch 语句中退出。在 switch 语句中,case的作用只是一个标号,因此,某个分支中的代码执行完后,程序将进入下一分支继续执行,除非在程序中显式地跳转。跳出 switch 语句最常用的方法是使用 break 语句与 return 语句。

while和for

1
2
3
for 循环语句;   
for (表达式 1; 表达式 2; 表达式 3)
语句

它等价于下列 while 语句:
表达式 1;

1
2
3
4
while (表达式 2) { 
语句
表达式 3;
}

但当 while 或 for 循环语句中包含 continue 语句时,上述二者之间就不一定等价了。

逗号运算符 “ , ” 也是 C 语言优先级最低的运算符,在 for 语句中经常会用到它。被逗
号分隔的一对表达式将按照从左到右的顺序进行求值,表达式右边的操作数的类型和值即为
其结果的类型和值。

do-while 循环

while 与 for 这两种循环在循环体执行前对终止条件进行测
试。与此相反,C 语言中的第三种循环——do-while 循环则在循环体执行后测试终止条件,
这样循环体至少被执行一次
do-while 循环的语法形式如下:

1
2
3
do 
语句
while (表达式);

在这一结构中,先执行循环体中的语句部分,然后再求表达式的值。如果表达式的值为真,则再次执行语句,依此类推。当表达式的值变为假,则循环终止。

break 语句与 continue 语句

(区别)

goto

(不要用)

函数与程序结构

函数基本知识

如果函数定义中省略了返回值类型,则默
认为 int 类型。

返回非整型值的函数

外部变量

自动变量只能在函数内部使用,从其所在的函数被调用时变量开始存在,在函数退出时变量也将消失。而外部变量是永久存在的,它们的值在一次函数调用到下一次函数调用之间保持不变。

用逆波兰表示法做一个计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*while (下一个运算符或操作数不是文件结束指示符) 
if (是数)
将该数压入到栈中
else if (是运算符)
弹出所需数目的操作数
执行运算
将结果压入到栈中
else if (是换行符)
弹出并打印栈顶的值
else
出错
*/
#include<stdio.h>
double pop(void);//弹出
void push(double);//压入栈
int getop(char );//读取






















寄存器变量

register 声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将 register 变量放在机器的寄存器中,这样可以使程序更小、执行速度更快。但编译器可以忽略此选项。
register 声明的形式如下所示:
register int x;
register char c;
register 声明只适用于自动变量以及函数的形式参数。下面是后一种情况的例子:
f (register unsigned m, register long n)
{
register int i;

}

无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。

可变参数

(留一下)

C预处理器

文件包含 (include)

两种表示 :
#include “ “
or
#include < >

如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用尖括号<与>括起来的,则将根据相应的规则查找该文件,这个规则同具体的实现有关 (编译器?)

宏替换

#define 名字 替换文本

宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。例如,下列宏定义定义了一个宏 max:
#define max(A, B) ((A) > (B) ? (A) : (B))
使用宏 max 看起来很像是函数词用,但宏调用直接将替换文本插入到代码中。形式参数(在此为 A 或 B)的每次出现都将被替换成对应的实际参数。因此,语句:
x = max(p+q, r+s);
将被替换为下列形式:
x = ((p+q) > (r+s) ? (p+q) : (r+s));
如果对各种类型的参数的处理是一致的,则可以将同一个宏定义应用于任何数据类型,而无需针对不同的数据类型需要定义不同的 max 函数。

注意加不加括号的问题

可以通过#undef 指令取消名字的宏定义

形式参数不能用带引号的字符串替换。 但是,如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。例如,可以将它与字符串连接运算结合起来编写一个调试打印宏:
#define dprint(expr) printf(#expr " = %g\n", expr)
使用语句
dprint(x/y)
调用该宏时,该宏将被扩展为:
printf("x/y" " = &g\n", x/y);
其中的字符串被连接起来了,这样,该宏调用的效果等价于
printf("x/y = &g\n", x/y);
在实际参数中,每个双引号 “ 将被替换为 \“ ,反斜杠 \ 将被替换为 \\ ,因此替换后的字符串是合法的字符串常量。

宏定义中也可以使用程序块结构:
例:
练习 4-14 定义宏 swap (t, x, y)以交换 t 类型的两个参数。(使用程序块结构会对你有所帮助。)

1
2
3
4
5
#define swap(t,x,y) {  t z;
t=x;
x=y;
y=t;
}

条件包含

还可以使用条件语句对预处理本身进行控制,这种条件语句的值是在预处理执行的过程中进行计算。这种方式为在编译过程中根据计算所得的条件值选择性地包含不同代码提供了一种手段。

#if 语句对其中的常量整型表达式(其中不能包含 sizeof、类型转换运算符或 enum 常量)进行求值,若该表达式的值 不等于 0则包含其后的各行 ,直到遇到 #endif、#elif 或 #else语句为止(预处理器语句#elif 类似于 else if)。
在 #if 语句中可以使用表达式 defined(名字),该表达式的值遵循下列规则:当名字已经定义时,其值为 1;否则,其值为 0。

例如,为了保证 hdr.h 文件的内容只被包含一次,可以将该文件的内容包含在下列形式的条件语句中:

1
2
3
4
#if !defined(HDR) 
#define HDR
/* hdr.h 文件的内容放在这里 */
#endif

第一次包含头文件 hdr.h 时,将定义名字 HDR;此后再次包含该头文件时,会发现该名字已经定义,这样将直接跳转到#endif 处。类似的方式也可以用来避免多次重复包含同一文件。
如果多个头文件能够一致地使用这种方式,那么,每个头文件都可以将它所依赖的任何头文件包含进来,用户不必考虑和处理头文件之间的各种依赖关系。

下面的这段预处理代码首先测试系统变量 SYSTEM,然后根据该变量的值确定包含哪个版本的头文件:

1
2
3
4
5
6
7
8
9
10
11
#if SYSTEM == SYSV 
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif

#include HDR

系统变量SYSTEM:系统变量 SYSTEM 是指计算机操作系统中的一个环境变量,用于标识当前正在运行的操作系统类型。它通常用于编写跨平台的程序,以便在不同的操作系统上执行不同的操作。
在不同的操作系统中,SYSTEM 的值可能会有所不同。例如,在Windows操作系统中,SYSTEM 的值可能是 “Windows_NT”;而在Linux操作系统中,SYSTEM 的值可能是 “Linux”。

指针

指针与地址

指针是一种保存变量地址的变量。

p = &c;
将把 c 的地址赋值给变量 p,我们称 p 为“指向”c 的指针。

一元运算符*是间接寻址或间接引用运算符。当它作用于指针时,将访问指针所指向的对象。

1
2
3
4
5
6
int x = 1, y = 2, z[10]; 
int *ip; /* ip is a pointer to int */ //指针ip的声明
ip = &x; /* ip now points to x */
y = *ip; /* y is now 1 */
*ip = 0; /* x is now 0 */
ip = &z[0]; /* ip now points to z[0] */

一元运算符和&的优先级比算术运算符的优先级高,因此,赋值语句
y = *ip + 1
将把
ip 指向的对象的值取出并加 1,然后再将结果赋值给 y,而下列赋值语句:
*ip += 1
则将 ip 指向的对象的值加 1,它等同于
++*ip

(*ip)++

语句的执行结果。语句(*ip)++中的圆括号是必需的,否则,该表达式将对 ip 进行加 1 运算,而不是对 ip 指向的对象进行加 1 运算,这是因为,类似于*和++这样的一元运算符遵循从右至左的结合顺序。

最后说明一点,由于指针也是变量,所以在程序中可以直接使用,而不必通过间接引用
的方法使用。例如,如果 iq 是另一个指向整型的指针,那么语句
iq = ip
将把 ip 中的值拷贝到 iq 中,这样,指针 iq 也将指向 ip 指向的对象。

1
2
3
int a[100];
int *p=a;//正确的
int b = p;//错误的(编译器警告,类型不匹配)

指针变量的初始化和赋值

在声明指针变量时对其赋地址值,让指针变量指向⼀个对象(变量、数组成员、结构体变量、函数)

1
2
3
int x;
int *p;
p=&x;

等价于:

1
2
int x;
int *p=&x;//注意这并不是 *p=&x ,而是定义p变量并赋值&x

指针⽤作函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*传递地址给函数,交换两个内部变量值*/
void swap(int *p,int *q)//形参是指针类型,接受外部地址
{
int t;
t=*p;*p=*q;*q=t;
//或者这样:
/*
int *t;
t=p;p=q;q=t;
*/
}
int main()
{
int x=3,y=5;
swap(&x,&y);//实参传递内部**地址**,x和y发生了交换

}

指针用作函数返回值

(注意类型的对应)
错误示范

1
2
3
4
5
6
int *f(void)
{
int i;//自动局部变量
.......
return &i;//执行后i自动销毁
}//f函数返回时,局部变量i自动销毁,返回i是无效地址

用指针访问一维数组

(上面有)

指向⼀维数组的指针⽤作函数参数

在调⽤函数时,将⼀维数组的地址传给函数,函数内就可以间接访问外⾯的数组。

  1. 向函数传递的实参可以是以下两种,效果⼀样:
    ① 实参是数组单元的地址,或者数组名。
    ② 实参是指针变量,指针变量存储了数组单元的地址。
  2. 函数在接收外部传递的地址时,形参可以是以下两种:
    ① 形参是数组,此数组将以传递来的数组地址作为⾸地址。
    ② 形参是指针变量,指针⽤于存储外部传递来的数组地址,通过指针也能访问外部数组。
    上述实参和形参可以两两组合出四种不同的写法:
    实参和形参都是数组:
    1
    2
    3
    4
    5
    6
    7
    8
    void f (int b[]) {
    b[0]++; …
    }
    int main() {
    int a[]={1,2,3,4,5};
    f (a);
    f (&a[2]);
    }

实参是指针,形参是数组:

1
2
3
4
5
6
7
8
void f (int b[]) {
b[0]++; …
}
int main() {
int a[]={1,2,3,4,5};
int *p = a;
f (p);
}

实参是数组,形参是指针:

1
2
3
4
5
6
7
void f (int *b) {
b[0]++; …
}
int main() {
int a[]={1,2,3,4,5};
f (a);
}

实参是指针,形参是指针:

1
2
3
4
5
6
7
8
void f(int *b){
b[0]++; ...
}
int main() {
int a[]={1,2,3,4,5};
int *p=a;
f (p);
}

指向⼆维数组的指针⽤作函数参数

a[i][j]:

C程序设计(第五版)p244

二维数组(a[i][j]):

a a[0][0]的地址
a+1 a[1][0]的地址(第二行)
a[i]+j a[i][j]的地址
&a[i][j] a[i][j]的地址

二维数组是按行存放的,因此a[3][4]中,a[12]就是a[2][3]

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
int main()
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4],i,j;//包含四个整型元素的一维数组,用于储存每一行的四个元素的地址
//注意不可写成*p[4],因为[]的运算级别高
p=a;
printf("please enter row and colum:");
scanf("%d,%d",i,j);
printf("a[%d][%d]=%d\n",i,j,*(*(p+j)+j));
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void f(int (*p)[3])//这个函数只能输出3列的二维数组
//(*p)[3] 中 (*p)是*(*p)的地址
{
printf("%d %d %d %d %d",
*(*p), //a[0][0]
*(*p+1), //a[0][1]
*(*p+1)+2, //a[0][1]+2
*(*(p+1)+2), //a[1][2]
*(*p)+1); //a[0][0]+1

}
int main()
{
int a[2][3]={10,20,30,40,50,60};
f(a);
}

茴香豆的“茴”有几种写法:

1
2
3
4
5
6
7
8
9
#include "stdio.h"
int main()
{
int a[4][3]={{1,2,3},{4,5,6},{7,8,9},{123}};
printf("%d",*(*(a+2)+1));
printf("%d",(*(a+2))[1]);//输出8 此处[1]是行地址的偏移1([]是取值操作)
printf("%d",*(a+2)[1]);//输出123 此处[1]是列的地址偏移1([]是取值操作
printf("%d",a[2][1]);
}

字符指针与函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#include <stdio.h>

// 函数原型
void modifyString(char *str);

int main() {
char myString[] = "Hello, World!";

printf("Before calling function: %s", myString);

// 调用函数修改字符串
modifyString(myString);

printf("After calling function: %s", myString);

return 0;
}

// 函数定义
void modifyString(char *str) {
while (*str != '\0') {
*str = 'X'; // 将字符修改为 'X'
str++; // 指针移动到下一个字符
}
}

回调函数

回调函数是指作为参数传递给其他函数的函数,这样在适当的时候可以由其他函数来调用。在C语言中,回调函数通常通过函数指针来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>

// 回调函数,用于比较整数大小
//*(int*)a 和 *(int*)b:这里的 (int*) 是将 void 指针强制转换为整型指针,然后 * 取出指针所指向的整数值。通过这种方式,我们可以访问指针所指向的整数值
int compareInt(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}

int main() {
int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
int n = sizeof(arr) / sizeof(arr[0]);
int i;

printf("Before sorting: ");
for (i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");

// 使用 qsort 函数进行排序,并传入回调函数
qsort(arr, n, sizeof(int), compareInt);

printf("After sorting: ");
for (i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");

return 0;
}

qsort()

C 标准库 - <stdlib.h>

描述
对数组进行排序。

声明
下面是 qsort() 函数的声明。

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

参数

base – 指向要排序的数组的第一个元素的指针。
nitems – 由 base 指向的数组中元素的个数。
size – 数组中每个元素的大小,以字节为单位。
compar – 用来比较两个元素的函数

指针数组和多重指针

指针数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main() {
int num1 = 10, num2 = 20, num3 = 30;
int *ptrArr[3]; // 声明一个包含三个指针的指针数组

// 将指针指向不同的变量
ptrArr[0] = &num1;
ptrArr[1] = &num2;
ptrArr[2] = &num3;

// 通过指针数组访问和修改变量的值
printf("Value at ptrArr[0]: %d", *ptrArr[0]);
printf("Value at ptrArr[1]: %d", *ptrArr[1]);
printf("Value at ptrArr[2]: %d", *ptrArr[2]);

*ptrArr[1] = 50; // 修改 num2 的值

printf("Updated value at ptrArr[1]: %d", *ptrArr[1]);
printf("Updated value of num2: %d", num2);

return 0;
}

指向指针的指针

如上代码中的ptrArr就是一个指向指针的指针
例子:
char **p;

结构体

结构变量定义和初始化

混合定义:

1
2
3
4
5
struct{
int id;
char name[20];
char gender;
}a,b,c;

单独定义:

1
2
3
4
5
6
struct{
int id;
char name[20];
char gender;
};
struct stu a,b,c;//struct stu看作一个整体用于声明变量a,b,c

混合定义及初始化:

1
2
3
4
5
6
7
struct{
int id;
char name[20];
char gender;
}a={123,"Tom",'M'},
b={456,"Jerry",'M'},
c={789,"Micky",'F'};

单独定义及初始化:

1
2
3
4
5
6
7
struct{
int id;
char name[20];
char gender;
}struct stu a={123,"Tom",'M'},
b={456,"Jerry",'M'},
c={789,"Micky",'F'};

嵌套定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct date
{
int year;
int month;
int day;
};
struct stu
{
int id;
char name[20];
char gender;
struct date birthday;
};
struct stu s1;

或者:

1
2
3
4
5
6
7
8
9
10
11
struct stu{
int id;
char name[20];
char gender;
struct date{
int year;
int month;
int day;
}birthday;
};
struct stu s1;

初始化可以:
struct stu s2={2001,"Jerry",'F',{2020,12,25}};
也可以:
struct stu s2={2001,"Jerry",'F',2020,12,25};

结构体变量的引用

s1.id s1.birthday.month

以下 4 个表达式是等价的:
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x

对结构体变量的成员可以像普通变量一样进行各种运算(根据其定义可以进行的)

同类的结构体变量可以相互赋值:s1 = s2

结构与函数

可以通过 3种可能的方法传递结构:一是分别传递各个结构成员,二是传递整个结构,三是传递指向结构的指针

函数 makepoint,它带有两个整型参数,并返回一个 point 类型的结构:

1
2
3
4
5
6
7
8
/* makepoint: make a point from x and y components */ 
struct point makepoint(int x, int y)
{
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}

注意,参数名和结构成员同名不会引起冲突。事实上,使用重名可以强调两者之间的关系。

结构数组

声明

考虑编写这样一个程序,它用来统计输入中各个 C 语言关键字出现的次数。采用结构数组编写:

1
2
3
4
struct key { 
char *word;
int count;
} keytab[NKEYS];

它声明了一个结构类型 key,并定义了该类型的结构数组 keytab,同时为其分配存储空间。数组 keytab 的每个元素都是一个结构。上述声明也可以写成下列形式:

1
2
3
4
5
struct key { 
char *word;
int count;
};
struct key keytab[NKEYS];

初始化

因为结构 keytab 包含一个固定的名字集合,所以,最好将它声明为外部变量,这样,只需要初始化一次,所有的地方都可以使用。这种结构的初始化方法同前面所述的初始化方法类似——在定义的后面通过一个用圆括号括起来的初值表进行初始化,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct key { 
char *word;
int count;
} keytab[] = {
"auto", 0,
"break", 0,
"case", 0,
"char", 0,
"const", 0,
"continue", 0,
"default", 0,
/* ... */
"unsigned", 0,
"void", 0,
"volatile", 0,
"while", 0
}; //因为要使用 **折半查找** ,因此关键字要升序排列

与结构成员相对应,初值也要按照成对的方式列出。更精确的做法是,将每一行(即每个结构)的初值都括在花括号内,如下所示:

1
2
3
4
{ "auto", 0 }, 
{ "break", 0 },
{ "case", 0 },
...

但是,如果初值是简单变量或字符串,并且其中的任何值都不为空,则内层的花括号可以省略。通常情况下,如果初值存在并且方括号[ ]中没有数值,编译程序将计算数组 keytab 中的项数

结构指针

定义

指向结构变量的指针就是结构指针。指针类型要和所指结构变量类型相同(struct xxx )

1
2
3
struct stu a={1001,"Tom",'M'};
struct stu *p;//or: struct stu *p=&a;
p=&a;//p指向a

指针访问结构变量成员

  1. 间接寻址运算符* 。 *p相当于所指变量a本身
    (*p).gender=’F’;
    (p).birthday.year=2020;
    (*p).x 中的圆括号是必需的,因为结构成员运算符“.”的优先级比“”的优先级高。
  2. 指向运算符 ->
    p->gender=’F’;
    p->birthday.year=2020;

结构体数组+指针 类比二维数组

示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
struct Student
{
int num;
char name[20];
char sex;
int age;
};
struct Student stu[3]={
{10101,"Li Lin",'M',18},
{10102,"Zhang Fang",'M',19},
{10103,"Wang Ming",'F',20}
};//固定的 最好全局
int main()
{
struct Student *p;//定义结构体指针变量
//p变量的类型是“struct Student (*)”
//因此p应该指向struct Student 类型的对象
//错误示范:p=stu[1].name (stu[1].name 是stu[1]元素中成员name首字母的地址)
//若需要强制转换,则:P=(struct Student *)stu[1].name;
printf("NO. Name sex age\n");
for(p=stu;p<stu+3;p++)//初始化:**p=stu**
//此时,p指向stu[0](即stu[0]的num的地址),
//(++p)->num 区别 (p++)->num
printf("%5d %-20s &2c %4d\n",p->num,p->name,p->sex,p->age);
return 0;
}

用typedef声明新类型名

例:
typedef int Interger;

typedef的几种复杂用法

  1. 代表结构体类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef struct
    {
    int month;
    int day;
    int year;
    }Date;
    //以上声明了一个新**类型名**Date,代表上面的结构体类型。
    //因此可用新的类型名Date定义变量
    Date birthday;
    Date *p;
  2. 代表数组类型
    1
    2
    Date int Num[100];
    Num a;
  3. 代表指针类型
    1
    2
    typedef char *String
    String p,s[10];
  4. 指向函数的指针类型
    1
    2
    typedef int (* Pointer)();
    Pointer p1,p2;

总结

按定义变量的方式,把变量名换上新类型名,并在前面加typedef,就声明了新类型名代表原来的类型

如:

  1. int a[100];//按定义变量的方式
  2. int Num[100];//把变量名“a”换上新类型名“Num”
  3. typedef int Num[100];//在前面加上typedef
  4. Num a;//用新类型名“Num”来定义变量

typedef与define的区别

typedef并不是简单的字符串替换

链表

静态链表

在C语言中,链表是一种常见的数据结构,用来存储一系列的元素。链表由节点组成,每个节点包括数据和一个指向下一个节点的指针。下面是一个简单的单向链表的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构
struct Node {
int data;
struct Node* next;
};

// 在链表末尾插入新节点
//接下来,我们定义了一个函数 append,用于在链表的末尾插入新节点。函数的参数包括一个指向指针的指针 head_ref 和要插入的新数据 new_data。这里使用了**双重指针的原因**是我们需要修改传入的指针变量,以反映新节点的插入。
//相当于 struct Node** head_ref; *head_ref=&new
void append(struct Node** head_ref, int new_data) {
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
struct Node* last = *head_ref;

new_node->data = new_data;
new_node->next = NULL;

if (*head_ref == NULL) {
*head_ref = new_node;
return;
}

while (last->next != NULL) {
last = last->next;
}

last->next = new_node;
}

// 打印链表内容
void printList(struct Node* node) {
while (node != NULL) {
printf("%d\n ", node->data);
node = node->next;
}
}

// 主函数
int main() {
struct Node* new = NULL;

append(&new, 6);
append(&new, 3);
append(&new, 9);
append(&new, 5);

printf("Linked list: ");
printList(new);

return 0;
}

在这个例子中,我们定义了一个Node结构来表示链表的节点,然后实现了在链表末尾插入新节点的函数和打印链表内容的函数。在主函数中,我们创建了一个链表并向其中插入了一些数据,然后打印了整个链表的内容。

这只是一个简单的单向链表示例,实际上链表还有很多其他操作,比如删除节点、插入节点等。

动态链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct Student
{
int num;
char name[1024];//这实际上是一个指向字符数组的指针
struct Student *next;
};
int n;
struct Student *Creat(void)//此函数返回结构体Student类型
{
struct Student *head,*p1,*p2;
n=0;
p1=p2=(struct Student *)malloc(len);
scanf("%d %s",&p1->num,p1->name);
head=NULL;
while(p1->num!=0)
{
n+=1;
if(n==1) head=p1;
else p2->next=p1;
p2=p1;
p1=(struct Student *)malloc(len);
scanf("%d %s",&p1->num,p1->name);
}
p2->next=NULL;
return head;//返回链表头的地址
}

联合/共用体

共用体(union)是一种特殊的数据结构,它允许在相同的内存位置存储不同的数据类型。共用体的所有成员共享同一块内存空间,因此共用体的大小等于其最大成员的大小。
由于共用体的所有成员共享同一块内存,修改一个成员的值会影响到其他成员的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>

// 定义一个共用体
union MyUnion {
int i;
float f;
char c;
};

int main() {
union MyUnion u;

// 向共用体中写入数据
u.i = 10;
printf("Integer value: %d", u.i);

u.f = 3.14;
printf("Float value: %f", u.f);

u.c = 'A';
printf("Char value: %c", u.c);

return 0;
}

位字段/位域

输入输出

标准输入/输出

(输入输出的重定向)

getchar()

这是最简单的输入机制,从标准输入(一般是键盘)一次读取一个字符,遇到文件结尾返回EOF(stdio.h中定义为-1)

在许多环境中,可以使用符号<来实现输入重定向,它将把键盘输入替换为文件输入:如果程序 prog 中使用了函数 getchar,则命令行
prog < infile
将把程序 prog 的输出从标准输出设备重定向到文件中。如果系统支持管道,那么命令行
prog | anotherprog
将把程序 prog 的输出从标准输出通过管道重定向到程序 anotherprog 的标准输入中。

printf()

声明:
int printf(const char *format, ...)
fomat标签属性是:
%[flags][width][.precision][length]specifier
%[标志][宽度][.精度][类型长度]类型

  • 类型:
类型 输出样式
%d 十进制整数
%o 八进制整数(0开头)
%x 十六进制整数(0x或oX开头)
%f 实数(float/double)
%c 字符
%s 字符串
%% 输出一个 ‘%’
  • 标志:
标志 描述
- 在给定的字段宽度内左对齐,默认是右对齐
+ 有符号的值若为正,则前面带’+’;若为负,则前面带’-‘
空格 有符号的值若为正,则前面带’ ‘;若为负,则前面带’-‘
0 对所有的数字哥=格式,用前导零而不是用空格填充字段宽度

在转换说明中,宽度或精度可以用星号*表示,这时,宽度或精度的值通过转换下一参数
(必须为 int 类型)来计算。例如,为了从字符串 s 中打印最多 max 个字符,可以使用下列
语句:
printf("%.*s", max, s);

完整版:

1
2
3
4
int width = 10;//宽度
int precision = 5;//精度
int number = 123;
printf("%*.*d", width, precision, number);

宽度:要输出的字符的最小数目。如果输出的值短于该数,结果会用空格填充。如果输出的值长于该数,结果不会被截断。

精度:对于整数说明符(d、i、o、u、x、X):precision 指定了要写入的数字的最小位数。如果写入的值短于该数,结果会用前导零来填充。如果写入的值长于该数,结果不会被截断。精度为 0 意味着不写入任何字符。

关于宽度精度问题的实例:
我们在每个字段的左边和右边加上冒号,这样可以清晰地表示出字段的宽度。

1
2
3
4
5
6
7
8
:%s:         :hello, world: 
:%10s: :hello, world:
:%.10s: :hello, wor:
:%-10s: :hello, world:
:%.15s: :hello, world:
:%-15s: :hello, world :
:%15.10s: : hello, wor:
:%-15.10s: :hello, wor :

变长参数表

(另写博客说明)

scanf()

转换格式:%[*][宽度][类型长度]类型

注意,scanf 和 sscanf 函数的所有参数都必须是指针。

格式 数据类型
%d int/short
%ld long
%f float
%lf double
%c char
%s char*/char [ ]

例子:

1.

1
2
3
4
5
  scanf("%2d%3d%d",&a,&b,&c);
// 输⼊: 123456789 102030
// %2d 读2个数字到a,即a=12
// %3d 读3个数字到b,即b=345
// %d 直⾄下⼀个**空⽩**前的数字被读到c,c=6789
  1. 1
    2
    3
    4
    5
    scanf(“%2d%d%f”, &a, &b, &c);
    // 输⼊: 9876 54.3 22.33
    // %2d 读2个数字到a,即 a=98
    // %d 下⼀个空⽩前的数字被读到b,即 b=76
    // %d 下⼀个空⽩前的数字被读到c,即 c=54.3
    若⽆其它输⼊函数处理后⾯22.33,建议使⽤
    **fflush(stdin);**
    把它们从输⼊缓冲区清空。

文件操作

使用⽅式 意义 使用⽅式 意义
rt 只读打开⼀个⽂本⽂件,只允许读数据 rt+ 读写打开⼀个⽂本⽂件,允许读和写
wt 只写建立⼀个⽂本⽂件,只允许写数据 wt+ 读写建立⼀个⽂本⽂件,允许读和写
at 追加打开⼀个⽂本⽂件,并在⽂件末尾写数据 at+ 读写打开⼀个⽂本⽂件,允许读或在末尾追加
rb 只读打开⼀个⼆进制⽂件,只允许读数据 rb+ 读写打开⼀个⼆进制⽂件,允许读和写
wb 只写打开⼀个⼆进制⽂件,只允许写数据 wb+ 读写建立⼀个⼆进制⽂件,允许读和写
ab 追加打开⼀个⼆进制⽂件,并在⽂件末尾写数据 ab+ 读写打开⼀个⼆进制⽂件,允许读或⽂件末尾追加

使⽤⽂件⽅式说明

  1. 使⽤⽅式,由 6个字符拼成 read, write, append, text, binary, **+**。
  2. ⽤”r”打开⽂件时,⽂件必须存在,且只能从⽂件读出。
  3. 如果没有”b”字符,⽂件以⽂本⽅式打开,因此上⾯的 ”t”可以省略不写。
  4. ⽤”w”打开⽂件时,只能向⽂件写⼊。若⽂件不存在则会新建⼀个;若⽂件已经存在则会先删除⽂件,再重建⼀个
  5. 要向⼀个已经存在的⽂件追加新的信息,只能⽤”a”⽅式打开,但⽂件必须已经存在,否则会出错。
1
2
3
4
5
6
7
8
9
10
FILE *fp1,*fp2 ;//定义⽂件指针 fp1、fp2
//打开当前目录下的⽂件“test1.txt”,只允许“读”操作,并使fp1指向该⽂件
fp1 = fopen (“test1.txt”, “r”);
//打开c盘下my目录下的⽂件“test2.txt”,只允许“写”操作,并使fp2指向该⽂件。'\\'是转义字符 or 使用'/'
if ( (fp2 = fopen (“c:\\my\\test2.txt”, ”w”)) ==NULL) {
printf(“打开⽂件失败”);
.....
fclose(fp1);
fclose(fp2);
//使用完文件后要调⽤fclose函数关闭⽂件,否则会有数据丢失的风险

一些常用函数的介绍

字符串操作函数

在<string.h>中定义

函数 作用
strcat(s, t) 将 t 指向的字符串连接到 s 指向的字符串的末尾
strncat(s, t, n) 将 t 指向的字符串中前 n 个字符连接到 s 指向的字符串的末尾
strcmp(s, t) 根据 s 指向的字符串小于(s<t)、等于(s==t)或大于(s>t)t指向的字符串的不同情况,分别返回负整数、0 或正整数
strncmp(s, t, n) 同 strcmp 相同,但只在前 n 个字符中比较
strcpy(s, t) 将 t 指向的字符串复制到 s 指向的位置
strncpy(s, t, n) 将 t 指向的字符串中前 n 个字符复制到 s 指向的位置
strlen(s) 返回 s 指向的字符串的长度
strchr(s, c) 在 s 指向的字符串中查找 c,若找到,则返回指向它第一次出现的位置的指针,否则返回 NULL
strrchr(s, c) 在 s 指向的字符串中查找 c,若找到,则返回指向它最后一次出现的位置的指针,否则返回 NULL
strcmp
1
2
3
4
5
6
7
8
9
10
11
#include "stdio.h"
#include "string.h"
int main()
{
char a[]="abcd",b[]="abcde",c[]="abcf";
printf("%d\n",strcmp(a,b));//1
printf("%d\n",strcmp(b,a));//-1
printf("%d\n",strcmp(a,c));//1
printf("%d\n",strcmp(a,"abcc"));//-1
printf("%d\n",strcmp(a,"abccf"));//1
}

字符类别测试和转换函数

在<ctype.h>中定义

函数 作用
isalpha(c) 若 c 是字母,则返回一个非 0 值,否则返回 0
isupper(c) 若 c 是大写字母,则返回一个非 0 值,否则返回 0
islower(c) 若 c 是小写字母,则返回一个非 0 值,否则返回 0
isdigit(c) 若 c 是数字,则返回一个非 0 值,否则返回 0
isalnum(c) 若 isalpha(c)或 isdigit(c),则返回一个非 0 值,否则返回 0
isspace(c) 若 c 是空格、横向制表符、换行符、回车符,换页符或纵向制表符,则返回一个非 0 值
toupper(c) 返回 c 的大写形式
tolower(c) 返回 c 的小写形式

ungetc 函数

int ungetc(int c, FILE *fp)
该函数将字符 c 写回到文件 fp 中。如果执行成功,则返回 c,否则返回 EOF。每个文件只能
接收一个写回字符。ungetc 函数可以和任何一个输入函数一起使用,比如 scanf、getc 或
getchar。

命令执行函数

<stdlib.h>中定义
函数 system(char* s)执行包含在字符申 s 中的命令,然后继续执行当前程序。s 的
内容在很大程度上与所用的操作系统有关。下面来看一个 UNIX 操作系统环境的小例子。语

system("date");
将执行程序 date,它在标准输出上打印当天的日期和时间。system 函数返回一个整型的状
态值,其值来自于执行的命令,并同具体系统有关。

一些使用例子:

1.

1
2
3
4
5
6
7
8
9
10
#include <stdlib.h>
#include <stdio.h>

int main() {
char filename[256] = "file.txt";
char command[512];
sprintf(command, "notepad %s", filename);
system(command);
return 0;
}
  1. system("pause");

存储管理函数

<stdlib.h>中定义

malloc() && realloc()

下面是 malloc() 函数的声明。
void *malloc(size_t size)
其中:
size – 内存块的大小,以字节为单位。
size_t – 这是无符号整数类型,它是 sizeof 关键字的结果。表示size是该类型变量

返回值:
该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL

下面是 realloc() 函数的声明。
void *realloc(void *ptr, size_t size)
参数
ptr – 指针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
size – 内存块的新的大小,以字节为单位。如果大小为 0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。

返回值:
该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
char *str;

/* 最初的内存分配 */
str = (char *) malloc(15);
/*str 是一个指向 char 类型的指针,通过 malloc 函数分配了 15 个字节的内存空间,并将该内存空间的地址赋给了 str。*/
strcpy(str, "runoob");//"runoob\0"实际七个字符,14个字节
printf("String = %s, Address = %u\n", str, str);

/* 重新分配内存 */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %u\n", str, str);

free(str); //C 库函数 void free(void *ptr)
//释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。

return(0);
}
calloc()

下面是 calloc() 函数的声明。
void *calloc(size_t nitems, size_t size)
其中:
nitems – 要被分配的元素个数。
size – 元素的大小。
返回值:
该函数返回一个指针,指向已分配的内存。如果请求失败,则返回 NULL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdlib.h>

int main()
{
int i, n;
int *a;

printf("要输入的元素个数:");
scanf("%d",&n);

a = (int*)calloc(n, sizeof(int));
printf("输入 %d 个数字:\n",n);
for( i=0 ; i < n ; i++ )
{
scanf("%d",&a[i]);
}

printf("输入的数字为:");
for( i=0 ; i < n ; i++ ) {
printf("%d ",a[i]);
}
free (a); // 释放内存
return(0);
}

数学函数

在<math.h>中定义

函数 作用
sin(x) x 的正弦函数,其中
cos(x) x 的余弦函数,其中 x 用弧度表示
atan2(y, x) y/x 的反正切函数,其中,x 和 y 用弧度表示
exp(x) 指数函数 e
x
log(x) x 的自然对数(以 e 为底),其中,x>0
log10(x) x 的常用对数(以 10 为底),其中,x>0
pow(x, y) 计算 x^y的值
sqrt(x) x 的平方根(x≥0)
fabs(x) x 的绝对值

时间函数

函数 rand()生成介于 0 和 RAND_MAX 之间的伪随机整数序列。
函数 srand(unsigned)设置 rand 函数的种子数。

1
2
strand(time(0));
int a=rand()/rg;

顺序读写文件

采用 fgets() fputs()

char *fgets(char * str,int n,FILE *fp);

char *fputs(char * str,FILE fp);

用格式化的方法读写文件

fprintf(文件指针,格式字符串,输出表列);

fscanf(文件指针,格式字符串,输入表列)
例子:
fprintf(fp,"%d,%6.2f",i,f);

用二进制的方法读写

fread() fwrite()
fread(buffer,size,count,fp);
fwrite(buffer,size,count,fp);
解释:
buffer – 地址(起始)
size – 要读写的字节数
count – 要读写多少个数据项(每个数据项长度为size)
fp – FILE类型指针

随机读写数据文件