2020年11月12日大更新:根据课堂内容,一些微信公众号,ACM竞赛进行补充,字数(算上示例代码)已达11k

1
printf("hello Wolrd")

资料来源:
runoob.com
谭浩强的《C程序设计》
如有出错还请大家评论指正

错误(segment error)

allocated 拨…(给); 划…(归); 分配…(给);

modifiable 可修改的

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内

大学考前突击

既然是考试,该突击就要突击,大部分平时都用不上

D3bUtU.md.jpg

D3bahF.md.jpg

C的编译与链接

摘自CSDN

编译器

编译器的作用是将保存着我们所写代码的源文件,转换成一种称为目标文件的二进制文件。源文件是我们能看懂的,是给我们用的。而二进制文件是计算机能看懂的文件,是给计算机用的。计算机根据二进制文件中的内容决定该做什么事情,不该做什么事情。

在这个转换过程中,编译器先对源文件中的内容进行扫描,根据C语言的语法要求,逐个检查源文件中出现的每一个字母或者文字。

如果这些文字符合C语言的语法要求,那么它就根据这些字母和文字的含义将其转换成计算机可以识别的二进制代码,并将其按照一定的格式保存在二进制文件中。如果某些地方的字母或者文字不符合C语言的语法要求,那么编译器将报告所有不符合的地方,不再生成二进制文件,只有改正所有不符合语法要求的地方,让编译器重新对改正的源文件进行转化,才可以生成二进制文件。

编译器对源文件的转换过程在计算机中有个专业的名字,叫”编译”。编译器也因此得名,表示编译的工具。

链接器

一般所写的程序最终是要运行在某个操作系统上的。因此,即使是一个很简单的程序也需要操作系统来处理很多事情,才能使程序正常运行。操作系统往往会提供一些被称为开发库的二进制文件,编译器产生的目标二进制文件只有和这些库二进制文件结合才能生成一个可执行程序,才能使我们写的程序正常地运行于某个操作系统之上。

另外,有的时候我们可能会开发一些专业的或者功能很复杂的软件,这类软件要从头做,往往很麻烦。这个时候,就得看看其他公司或者业界有没有提供此类功能实现,可以买过来使用。往往买过来的也是一堆库二进制文件,只有把这些库二进制文件和编译器产生的目标二进制文件结合起来才能产生需要的可执行程序。

链接器所做的工作就是将所有的二进制文件链接起来融合成一个可执行程序,不管这些二进制文件是目标二进制文件还是库二进制文件。链接器将二进制文件融合的这一过程,在计算机中也有一个专业的名字–”链接”,链接器也因此得名,表示链接的工具

基本输入与输出

论scanf的安全性

微软那边用scanf_s解决了,但这种情况(弄出stack overflow)百试不厌

详情可以看这个

https://www.freebuf.com/articles/system/253225.html

长见识了

scanf

1
2
3
scanf("%d,%d",&a,&b);
scanf("%s",str) /*字符组不能加&地址符*/
printf("Max is %d",a);

Scanf输入的是地址,必须有&(取地址符),如果是指向数据的指针则不用加

负号也算一位

%f 默认是输出6位

%3f 输出3位,不足前面补0

%.3 小数点后输出三位,不足后面补0

1e6 10的6次方

没有输出二进制的占位符

指数是实型

占位符

%p 指针地址 %o 八进制 %x 十六进制

%c 输出单个字符 %ld 长整型 %nd输出几位的整型

%s 输出字符串

输入时的时候要注意

%f 单精度(float)

%lf 双精度(double) 提示:5.0/2.0

%lld long long int

%b 布尔型(cpp中使用)

Attention!!!

回车也算字符!!!!

一般用getchar()吸掉

要是写上%3d,输出的数字将占3个空

C语言各数据类型所占字节数

1字节是8位

  • char存储大小1字节,值范围-128~127;
  • unsigned char存储大小1字节,值范围0~255;
  • short存储大小2字节,值范围-32768~32767;
  • unsigned short存储大小2字节,值范围0~65535;
  • int——

​ 16位系统存储大小2字节,值范围-3276832767,
​ 32、64位系统存储大小4字节,值范围-2147483648
2147483647;

10位数

  • unsigned int——

​ 16位系统存储大小2字节,值范围065535,
​ 32、64位系统存储大小4字节,值范围0
4294967295;

  • long——

​ 16、32位系统存储大小4字节,值范围-21474836482147483647,
​ 64位系统存储大小8字节,值范围-9223372036854775808
9223372036854775807;

VC中long int 和 int是没有区别的,两种类型均用4个字节存放数据。

  • unsigned long——

​ 16、32位系统存储大小4字节,值范围04294967295,
​ 64位系统存储大小8字节,值范围0
18446744073709551615;

  • long long的最大值:9223372036854775807
    long long的最小值:-9223372036854775808

    共19位数

  • float存储大小4字节,值范围1.17549435110^-38~3.40282346610^38;

  • double存储大小8字节,值范围2.225073858507201410^-308~1.797693134862315810^308;

  • long long存储大小8字节,值范围-9223372036854775808~9223372036854775807;

  • unsigned long long存储大小8字节,值范围0~18446744073709551615;

  • long double——

​ 16位系统存储大小8字节,值范围2.2250710^-308~1.7976910^308,
​ 32位系统存储大小12字节(有效位10字节,为了对齐实际分配12字节),值范围3.410^-4932 到 1.110^4932,
​ 64位系统存储大小16字节(有效位10字节,为了对齐实际分配16字节),值范围3.410^-4932 到 1.110^4932;

  • 指针——

​ 16位系统存储大小2字节,
​ 32位系统存储大小4字节,
​ 64位系统存储大小8字节。

C51 sfr 1字节

bit 1个位

\n是换行 \r回车 \t应该是空格

摘自知乎

C的强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如,如果您想存储一个 long 类型的值到一个简单的整型中,您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型,如下所示:

1
(type_name) expression
1
2
float a=3.124;
a=(int)a;
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <math.h>
int main()
{
float a=3.124;
int b=0;
b=(int)a;
printf("%d",b);
return 0;
}

小写转大写

由于大写字母与小写字母之间的差值为 32,因此小写字母转换为大写字母的方法就是将小写字母的 ASCII 码值减去 32,便可得到与之对应的大写字母。

正规方法使用 toupper(), tolower() 函数

需要ctype.h

Tip

scanf(“%s”,s); //采用bai这种方法不能输入空格,空格作为字符串结束符。
gets(s); //采用这种方法可以输入空格,回车作为字符串结束符。

Exit

需要stdlib.h

转义字符

\b 名义上是回车,实际上是光标右移

\t tab

基本运算

乘方有时可用^,但正确做法应该是pow()

取模运算

运算法则如下

  1. (a + b) % p = (a % p + b % p) % p (1)
  2. (a - b) % p = (a % p - b % p ) % p (2)
  3. (a * b) % p = (a % p * b % p) % p (3)

乘方运算

pow(),在math.h,平方运算

要不然就是*N次,手动写和循环都行

1
2
3
for(int i=1;i<=power;i++){
result=result*base;
}

Tip:小数存储

十进制(转二进制),小数点后面乘2,取整数位的0和1

即可能会存在精度损失

i++

1
x=i++等价于x=i,i=i+1,懂?

i++是后缀递增的意思,i++是先进行表达式运算,再进行自增运算。把i++的运算过程拆分开,等效于i=i+1可以看到运算结果是一致的

++i是前缀递增的意思,++i是先进行自增或者自减运算,再进行表达式运算。运算结果可以发现,仅从i的值来看,++i和i++最终的i值是一样的,都是i自增加了1

常见逻辑

顺序,选择,循环

注释

1
/*  Hello */
1
// Hello

字符处理

字符存储

做大小写混合转换时要注意,配合 while ch!=’\n’ 进行判断

ASCII,大写A为65,小写为97

做题时注意,小写的ascii码要更大

为什么是32不是26?

原因:26个小写和26个大写之间插入了6个字符

常规处理

如果你有一长串字符,却用

1
2
scanf("%c",&a);
or a=gecthar;

就可以实现单个输出与转换

即做题时,可以这样直接输出,就省去了使用字符数组的必要

但要实现存储,需要使用下面的方案

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 <string.h>

int main ()
{
char str1[12] = "Hello";
char str2[12] = "World";
char str3[12];
int len ;

/* 复制 str1 到 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\n", str3 );

/* 连接 str1 和 str2 */
strcat( str1, str2);
printf("strcat( str1, str2): %s\n", str1 );

/* 连接后,str1 的总长度 */
len = strlen(str1);
printf("strlen(str1) : %d\n", len );

return 0;
}

字符数组

数组名就是指针名!!

\0为占位符

字符数组初始化:

1
char a[]={'a'}

getchar()获取单个字符

1
2
char c;
c=getchar();

一般用循环不断输入单个字符

1
2
3
char str[20]={0};  //如果没有定义长度,scanf会出错
scanf("%s",&str);
printf("%s",str);

单个字符用单引号,字符串用双引号

使用下列函数时,最好添加头文件<string.h>

gets() 输入字符 (GCC会报警,不能使用指针

puts() 输出字符

strcat(字符数组1,字符数组2) //连接两个字符数组

strcpy(字符数组1,字符串/字符数组2)

在VC上会出错

MSDN知乎的解决方案

建议使用下面替代

1
strcpy_s(str, sizeof(str), str1)

有理由怀疑这是C++的语法,不知道能不能在GCC上用

好像是C11标准

strcmp () 两个字符串进行比较(比较字符是否完全相同)

strlen() 比较字符长度(不包括占位符)

strlwr 转小写(lowercase)

stlupr 转大写 (uppercase)

strlwr和strupr不是标准C库函数,只能在VC中使用。linux gcc环境下需要自行定义这个函数。

可以存在字符二维数组

可以根据空格数判断词数

*注意,形参只能传递指针,不能将数组的所有元素一次性全部传递,除了标准的char p外,还可以将形参写成char c[]。

C的数组

1
2
type arrayName [ x ][ y ];   /声明一个 x 行 y 列的二维整型数组/
a[3][4] 就是34

只有codeblock在数组初始化不足时会自动补0???

二维数组行可以少,但列不行

数组初始化

1
memset(a,0,sizeof(a));

使a数组中的全部元素全部初始化为0

需要头文件string.h

根据空间大小,从前往后初始化

二维数组也可以

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int a[2][2];
memset(a,0,sizeof(a));
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
printf("%d",a[i][j]);
}
}
}

C的常量

#define 是宏定义,它不能定义常量,但宏定义可以实现在字面意义上和其它定义常量相同的功能,本质的区别就在于 #define 不为宏名分配内存,而 const 也不为常量分配内存,怎么回事呢,其实 const 并不是去定义一个常量,而是去改变一个变量的存储类,把该变量所占的内存变为只读!

const 定义的是变量不是常量,只是这个变量的值不允许改变是常变量!带有类型。编译运行的时候起作用存在类型检查。

define 定义的是不带类型的常数,只进行简单的字符替换。在预编译的时候起作用,不存在类型检查。

C的存储类

使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量

当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int age();
int main(){
for(int i=1;i<=5;i++){
printf("%d!=%d\n",i,age(i));
}
return 0;
}
int age(int t){
static int f=1; //static在局部变量的用处
f*=t;
return (f);
}

register 寄存器变量

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

这货比内存快很多,好不好?

register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数

在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度

因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销

随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略,它仅仅是暗示而不是命令

只有局部自动与行参可作为寄存器变量

使用extern引用另一个文件的全局变量

函数定义时省略extern,则默认为外部函数

C的运算

int除完还是整数,即使最终结果是输出到float的变量

除以0的故事

整数除0会报错,但是浮点数不会(会返回inf infinety)

java也是这样,我觉得原因应该一样

因为java的float和double使用了IEEE 754标准。

这个标准规定:浮点数除以0等于正无穷或负无穷。

正无穷大的定义居然是1.0f/0.0f负无穷大的定义为**-1.0f/0.0f**,非数的定义为0.0f/0.0f

(排除错误时使用,浮点型的情况编译器不会警告)

无限不循环小数

机器的运算精度有上限,高精度计算需要其他科学方法

C循环

我认为,在知道循环次数的条件下,使用for循环

while循环在初始不知道循环的情况下使用

不要干些傻事,循环里声明的变量,如果没有初始化,不会因为循环导致变量清0

默认使用i,j,k作为循环变量进行控制

for循环

1
2
3
4
for ( init; condition; increment )
{
statement(s);
}

init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。

建议多少到多少时使用

1
for(int=0;i<3;i++)

这个就是循环三次

0,1,2

while循环

1
2
3
4
while(condition)
{
statement(s);
}

do-while循环

do 先进入 while 非0,继续循环

即不管是否满足条件,都要先执行一便循环体

brake

跳出循环,亦可以跳出switch

(考试时被坑了,谁没事干用switch做判断)

continue

终止本次循环,经行下一次循环

C判断

if的语法

1
2
3
4
5
6
7
8
if(boolean_expression)
{
/* 如果布尔表达式为真将执行的语句 */
}
else
{
/* 如果布尔表达式为假将执行的语句 */
}

虽说可以用缩进代替大括号,不推荐这么做

if(i–)

if判断完后,马上–,然后再执行框内

switch的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch(grade)
{
case 'A' :
printf("很棒!\n" );
break;
case 'B' :
case 'C' :
printf("做得好\n" );
break;
case 'D' :
printf("您通过了\n" );
break;
case 'F' :
printf("最好再试一下\n" );
break;
default :
printf("无效的成绩\n" );
}
printf("您的成绩是 %c\n", grade );

就是垃圾玩意

每个判断后面都要加break

可以连写,比如caseA与caseB共通条件

这TM问题很严重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int main()
{
int c;
while((c=getchar())!='\n')
switch(c-'2')
{
case 0:
case 1:putchar(c+4);
case 2:putchar(c+4);break;
case 3:putchar(c+3);
default:putchar(c+2);break;
}
}
//这输出的都是什么好东西
//要是进0,012全部执行

do while 循环

1
2
3
4
5
do
{
statement(s);

}while( condition );

**如果条件为真,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)**。这个过程会不断重复,直到给定条件变为假为止。

else

else与最近的if匹配

? : 运算符(三元运算符)

我们已经在前面的章节中讲解了 **条件运算符 ? :**,可以用来替代 if…else 语句。它的一般形式如下:

1
Exp1 ? Exp2 : Exp3;

其中,Exp1、Exp2 和 Exp3 是表达式。请注意,冒号的使用和位置。

? 表达式的值是由 Exp1 决定的。如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值。

Exp1、Exp2 和 Exp3 都必须要有返回值

逻辑运算符

下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1,变量 B 的值为 0,则:

运算符 描述 实例
&& 称为逻辑与运算符。如果两个操作数同时都非零,则条件为真。(也就是数学中的且) (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A && B) 为真。

C的函数

不推荐隐式声明

无参函数,如print

有参函数,主函数与被调用函数之间存在参数传递

有参函数分为两类

形式参数:定义的变量/值

1
2
3
age(int a){
/*定义方式*/
}

实际参数:传进来的变量/值

预先声明不需要确定数据类型

return后面可以是表达式

1
return(x>y?x:y);

void函数体里面也可以有return,但return后面不需要接任何东西

基础概念

函数的调用就是stack的使用,参数的进栈跟出栈,还有就是保留原来程序指针位置,这个就是函数的调用
当函数调用完毕,他会返回到原来的那个运行指针处
所以要保留原来的程序指针位置

原来的程序指针位置,也就是地址也会被放进stack当中

(要讲啥,忘了)

C的递归

递归指的是在函数的定义中使用函数自身的方法

c允许自己调用自己

函数传递是用过ax寄存器实现

在函数里写上判断条件,符合就返回1,不符合就继续递归

非常重要!!!

因为递归只能用函数实现

以n!为例

n=1为1

n!= 1时继续递归

C的指针

这图话里有话

DsXNDJ.md.jpg

1
2
ip = &var;  /* 在指针变量中存储 var 的地址 */
int *ptr = NULL /*空指针*/
1
2
if(ptr)     /* 如果 p 非空,则完成 */
if(!ptr) /* 如果 p 为空,则完成 */

&表示取地址,*表示取数据

*表示指针变量,但指针变量名为ptr

一个指针变量只能储存一个地址

编译器对地址运算不做越界检查,程序员要小心谨慎地移动指针,时刻避免越界

指针变量是有空间的,存放的是另外一个变量的地址

&为取地址

*p为指针变量指向的变量的值(规范表述)

int一个是4位,char一个是8位

1
2
3
4
5
6
int a[5]={1,2,3,4,5};
int *i;
i=a;
for(int c=0;c<5;c++){
printf("%d \n",*(i+c));
}

i=a等效于i=&a[0]

C语言规定:数组名代表数组的首地址

(注意一下,这个例子里,不能直接对a操作,只能对传完的指针进行指针运算) Quite Wrong

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[4];
memset(a,0,sizeof(a));
for(int i=0;i<4;i++)
{
printf("%d ",*(a+i));
}
}

数组名做函数参数时,把实参数组的地址传给了形参

实现改变形参的同时改变实参

Attention:实参变量与形参变量的数据传递是单向的值传递

“调用函数不能改变实参指针变量的值

但可以改变实参指针变量所指变量的值

数组中,(米字号)a等效于a[0],*(a+1)等效于a[1]

C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、–、+、-

指针可以递增(可用来输出数组)

可以有数组指针

1
*(p++)与*(++p)的区别
1
*p++等价于*(p++)

前者先输出p,再p+1

后者先p+1,再输出p

1
2
3
4
5
6
7
8
9
int main ()
{
int a=5;
int *i;
i=&a;
(*i)+=1;
printf("%d",a);
return 0;
}

指针变量i具有a的地址,*i等效于a

指针做循环条件时,不能用scanf(理解有误)

数组的本质就是指针

二维数组指针

在这里,*a取的不是值,是指针

1
*a与a[0]地址相等,即第一行,这是一个指针(不等价,因为步进值不同)

a表示第一行,第一列(a00)

a+1表示第二行,第一列(a10)

对于一个a32数组来说,a+3会输出第二行,第一列的位置,仅C语言可以(属于越界行为,不推荐,如果你使用其他语言很有可能出错【)

a0+1第一行,第二列(a01)

*p[4] 是二维数组

**p类似于二维数组(指针变量)

对比于一维数组a[5]

a+1是地址,a[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
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int a[2][2];
int *c;
c=a[0];
a[0][0]=1;a[0][1]=2;a[1][0]=3;;a[1][1]=4;
/*
for(int i=0;i<2;i++)
{
for(int j=0;j<2;j++)
{
printf("%d ",a[i][j]);
}
}
*/
for(int i=0;i<2;i++)
{
c=a[i];
for(int j=0;j<2;j++)
{
printf("%d ",*c);
c++;
}
}

}

注释内和注释外完全等价

这部分其实很烧脑子

字符数组指针

字符串常量字符数组处理

所以%s既能输出字符串,又能输出字符数组指针

输出指针,说明字符串是地址

1
2
3
可以char *string=“I love China”
等价于char *string
string='I love China'

以上做法只是把I love China的首地址赋给了指针变量string

使用%s进行输出

字符变量不能直接赋值

例如

1
2
3
char string
string='I love China'是行不通的
string赋得的是首地址

实参与行参可以是数组名字符指针变量

1
2
3
4
5
6
7
8
9
# include<stdio.h>
int test();
int main(){
char str[]="I love China";
char *a;
a=str;
a+=7;
printf("%s",a);
}

可以通过控制指针得到想要的字符串

以此类推,也可以控制指针变量输出单个字符

函数指针

1
2
3
int max()
int (*p)()
p=max

这就是一个函数指针

其常用的用途是把函数当作参数传递到其他函数

函数为实参,指针为形参

将数组传递给函数

以数组元素循环为例进行演示

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
#include <stdio.h>
#include <stdlib.h>
#define MAXN 100
int ArrayShift(int a[], int n, int m);

int main()
{
int a[MAXN],n,m;
int i;
scanf("%d%d",&n,&m);
for(i=0;i<n;i++) scanf("%d",&a[i]);
ArrayShift(a,n,m);

for(i=0;i<n;i++)
{
if(i!=0)printf(" ");
printf("%d",a[i]);
}
}

int ArrayShift(int a[],int n,int m)
{
while (m--)//移动的次数
{
int temp = a[n - 1];//保留最后一位
int i;
for(i = n - 1; i >= 1; i--)
{
a[i] = a[i - 1];
}
a[0] = temp;
}
}

返回指针的函数

1
int *p(a,b)

即取函数p的指针

指向指针的指针

1
char **p

指针数组

1
int (*p)[3]

要注意,这个指针数组虽然只用3个元素,但由于指向的是地址,地址是没有个数的,所以理论上可以输出所有数

常量指针与指针常量

https://blog.csdn.net/chenjin_zhong/article/details/6097903

常量指针与指针常量是C语言中两个比较容易混淆的概念:

(1) const char* p;

(2) char* const p=a;

(3) char* p=”abc”;

(1)式定义了一个常量指针,即指向一个常量的指针,指向的内容是常量,不可修改,放在常量区的,但指针本身可以修改,即p=’b’,是非法的,p是p指向的常量的第一个字符,是个常量,不能改变的。p=&q这是可以的,指针可以指向不同的地址。

以下程序非法

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

int main()
{
int a[]={1,2,3};
const int *t=a;//const 去掉即合法
(*t)=(*t)+1;
printf("%d",*t);
}

(2)式定义了一个指针常量,即指针本身是个常量,不可修改,但指针指向的内容可以修改,一开始定义时让它指向数组a. *p=’b’这是可以的,但p=&b是非法的。

以下程序非法

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

int main()
{
int a[]={1,2,3};
int* const t=a;
t++;//非法区域
printf("%d",*t);
}

记忆: const 常量 * 指针,当const 在之前就是常量指针,而const在之后,就是指针常量.

const charp 即charp是个常量,所以内容是常量。

char* const p; 即指针p是个常量。

(3) charp定义的是一个指针变量p,指向字符串abc的首地址。这里特别要注意,在C语言中,(3)式定义的是一个常量字符串,它被放在静态存储区的常量区进行存储,而p是一个指针变量,放在栈上。如果p=’b’,在编译时能通过,但在运行时就会出现错误,因为你试图去改变常量区的内容。

下面程序非法

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

int main()
{
char* t="ANC";
*t ='b';//非法来源
printf("%s",t);
}

常量指针指的是内容不可变,地址可以改变,即指针可以指向别的地址(保护数据不被修改)。而指针常量指的是指针本身不可变,而内容可以修改(固定位置)

避免在参数传递时造成更改,相当实用

Main的形参(重要彩蛋)

指针数组的一个重要应用是作为main的形参

命令行的参数都是字符串,字符串首地址构成指针数组

1
main(int argc,char *argv[]){}

argc指命令行中参数的个数

argv 为字符串指针组

可能涉及强制转换

字符串转数字需要stdlib.h

下面是一个简易的加法计算

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<stdlib.h>
int main(int argc,char *argv[]){
int b=0;
while(argc>1)
{++argv;
b=atoi(*argv)+b;
printf("%d\n",b);
--argc;
}
return 0;
}

Tips

若两个指针在同一数组,指针变量可以相加减,也可以进行比较

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
void main(){
int matrix[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int *a; //以一维数组的形式,遍历二维数组
int *b[3]; //存的是地址,指向的是整形数据
int (*b)[3]; //存的是地址,指向的是地址,一共有三个指针变量,类似于二维数组

a=matrix;
//一维数组指针可以遍历二维数组指针

printf("%x,%x,%x\n",a, &matrix[0][0], &matrix[0][1]);
printf("%d,%d,%d\n",*(*a), *(*(a+1)), *(a+3));

}

还是以输出数组为例

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

void main(){
int matrix[3][3]={{10,2,3},{4,5,6},{7,8,9}};
int *a=NULL;
int *c=NULL;
a=matrix[0]+4;
for(int i=0;i<5;i++){
int b=0;
b=*(a+i);
*(a+i)=*(a-i);
*(a-i)=b;
}
a-=4;
c=a;
for(;a<(c+9);a++){
printf("%d \n",*a);
}
}
1
2
3
4
5
6
7
8
9
10
#include<stdio.h>

void main(){
int matrix[3][3]={{10,2,3},{4,5,6},{7,8,9}};
int (*a)[3];
a=matrix;
for(int i=0;i<3;i++){
printf("%d \n",*(*(a+i)));
// 等效于printf("%d \n",*a[i]),进行列的输出;
}
1
2
3
4
5
6
7
8
void main(){
int matrix[3][3]={{10,2,3},{4,5,6},{7,8,9}};
int (*a)[3];
a=matrix;
for(int i=0;i<3;i++){
printf("%d \n",*(a[0]+i));//进行行的输出(会警告)
}
}
1
2
3
4
5
6
7
8
void main(){
int matrix[3][3]={{10,2,3},{4,5,6},{7,8,9}};
int **p;
for(int i=0;i<3;i++){
p=matrix[0]+i;
printf("%d \n",*p); //进行行的输出(会警告)
}
}

C的枚举

1
enum 枚举名(可省略) {枚举元素1,枚举元素2,……} 枚举变量;

枚举变量就是输入常量,输出int;

例子

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

等价于

1
2
3
4
5
6
7
#define MON  1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7

这样是不行的

1
#define 1  MON

结构体

一个简易样例

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
struct test
{
int x,y;
}a;

int main(){
a.x=1;
a.y=9;
}
1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
struct test
{
int x,y;
};

int main(){
struct test a;
a.x=1;
a.y=9;
}

结构体是一种变量

. 是分量运算符,运算符中优先级最高

进行两次声明,第一次声明结构体里有什么

第二次按照模板构建对象

结构体内可以利用先前已定义的结构体

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>
#define A struct jiegouti //使A与struct student等价
struct date
{
int year;
int month;
int date;
};
struct jiegouti
{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
struct date birthday;
};
A student1,student2;


int main(){
student2.birthday.month=9;
printf("%d",student2.birthday.month);
}

指向结构体变量的指针

1
struct student *p;  //结构一定要一样

结构体变量可直接赋值给另一个具有相同结构的结构体变量

一种初始化方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
#define A struct jiegouti
struct jiegouti
{
int num;
char name[20];
char sex;
char addr[30];
}a={89031,"Li lin",'M',"123 Beijing Road"};//简单方式


int main(){
printf("%ld %s %c %s ",a.num,a.name,a.sex,a.addr);
}

另一种初始化方法(走错片场,仅cpp可用)

1
2
3
4
5
struct node{
int value;
node *l,*r;
node(int value =0,node *l = NULL,node *r = NULL):value(value),l(l),r(r){} //完成初始化
};

允许存在结构体数组,通过循环逐个输出数据。

指针只要取结构体数组的地址就可以代替原有的数组

1
(*p).sum 等价于 p->num

指向结构体数组的指针

下面这个例子,通过搬动指针进行输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
struct student{
int num;
char name[20];
char sex;
int age;
}stu[3]={{10101,"Li Lin",'M',18},{10102,"Zhang Fun",'M',19},{10104,"Wang Min",'F',20}};

int main(){
struct student *p;
for(p=stu;p<stu+3;p++){
printf("%5d%-20s%2c%4d\n",p->num,p->name,p->sex,p->age);
}
}

Tip:结构体中(.)和(->)的区别

相同点:两个都是二元操作符,其右操作符是成员的名称。
不同点:点操作符左边的操作数是一个“结果为结构”的表达式;箭头操作符左边的操作数是一个指向结构的指针。

https://blog.csdn.net/alidada_blog/article/details/80353343

另外一点体会:如果一个函数的传入参数是结构体,且需要该结构体作为返回值的时候,必须采用指针传递的方式,其中对结构体赋值必须使用箭头运算符。(!)

链表

概念

物理存储单元上非连续非顺序结构,节点间靠指针联系

每个节点包含数据和指针

链表的插入和删除都是常数时间

程序局部性原理:Cpu会把相邻存储单元的数据导入L1,L2,L3缓存中

数组可使用,但链表不行。

适用于

大内存空间分配

元素频繁删除和插入

头节点可以是第一个节点,但建议是哨兵节点(虚拟节点)。

链表的翻转操作

时间复杂度和空间复杂度??

太长了,不看了

http://mp.weixin.qq.com/s?__biz=MzI2NjI5MzU2Nw==&mid=2247495549&idx=2&sn=7b9391f3ea023c904ee2de0d7a171d5d&chksm=ea92f72ddde57e3b297502256d13d6eb0ce7ed14fb7b367ca84e7804c5254c0fe2e3893f7d70&mpshare=1&scene=23&srcid=1026OwQv1RXoM4yQ4tAxpjVr&sharer_sharetime=1603796194401&sharer_shareid=e37afb434548e8cf438b6feb1a808418#rd

实现

首先,C语言中链表只能用指针解决

1
2
3
4
5
struct student{
int num;
float score;
struct *student
}

即数据加指针

建立链表需要<malloc.h>

建立一个链表需要3个指针

一个用于保存链头,一个创建新空间,另一个将新空间写入数据体

新建立的指针都需要需要初始化

sizeof函数

输出类型字符大小,是一个长整形量

1
2
sizeof(int);
sizeof(struct student);

malloc函数

需要malloc.h,输入一次就申请一次。

栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等

堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收

该函数用于申请堆空间,程序结束后不释放

需要用free函数主动释放

如果没有申请到,就会返回NULL

1
int *newArr = (int *)malloc(SIZE * sizeof(int));
1
free(newarr)

typedef函数

定义类型,用于简化编程

typedef 是由编译器执行解释的,**#define** 语句是由预编译器进行处理的。

typedef定义语句要用分号结束

1
2
typedef int[20] TEST
//TEST定义就等价于int[20]

在下面数据体构成链表

实例

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
#include<stdio.h>
#include<malloc.h>

typedef struct stduent
{
long num;
float score;
struct student *next;
} STU;


#define LEN sizeof(STU)

int n=0;

STU *creat() //创建链表
{
STU *head;
STU *p1,*p2;

p1=(STU *)malloc(LEN);
p2=p1;
//以下过程为初始化
p1->num=5;
p1->score=20;
p1->next=NULL;
//scanf("%ld,%f",&p1->num,&p1->score);
head=p1;


while(p1->num!=0){
/* n=n+1;
if (n==1)head=p1;
else p2->next=p1;
p2=p1; */
p1=(STU *)malloc(LEN); //申请新空间
p2->next=p1;
p2 =p1;
scanf("%ld,%f",&p1->num,&p1->score);
p1->next=NULL;
}
//p2->next=NULL;
//free(p1);
return head;

}

int main(){
STU * p = creat();
while (p != NULL)
{
printf("%ld, %f\n",p->num, p->score);
p = p->next;
}

//printf("%d", sizeof(STU));

}

注意指针类型,定义时记得是struct student

删除所有节点

直接删除所有节点

1
2
3
4
5
6
7
8
9
10
11
void release(STU* p){
while (p != NULL)
{
STU *o=NULL;
o = p->next;
free(p);
p=o;
n++;
}
printf("n=%d", n);
}

边输出边删除所有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
p=head;
STU *d=NULL;
for(int i=0;i<4;i++){
p = p->next;
}
d=p->next;
printf("%d, %d, %d\n", d->num, p->num);
free(p);
p=head;
for(int i=0;i<3;i++){
p = p->next;
}
printf("%d\n",p->num);
p->next=d;

删除特定节点

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
STU* delete(STU* link, long value){
STU* link1=NULL;
STU* linktem=link;
if(linktem->score!=value){
do{
link1=linktem;
linktem=linktem->next;
if(linktem->next==NULL)
break;
}
while(linktem->score!=value);
link1->next=linktem->next;

printf("Testing: %ld, %f\n",linktem->num, linktem->score);
free(linktem);
return(link);
}
else
{
linktem=linktem->next;
free(link);
link=linktem;
return(link);
}
}

关键:要解决死循环,首个项满足的情况

头节点不要轻易删改,用临时变量暂时替代

下面一个清晰思路,可以清晰看清楚是头节点还是尾节点(目前仅仅是一个框架)

1
2
3
4
5
6
7
8
9
10
11
12
13
while(link1 != NULL){
if(1 && linktem == link1){ //header


}else if(1 && link1->next == NULL){ //trail

}else{ //medium

}
linktem = link1;
link1 = link1 -> next;

}

在特定节点后面增加节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void add(STU* link, long value){
STU* link1=NULL;
STU* linktem=link;
if(linktem->score!=value){
do{
linktem=linktem->next;
if(linktem->next==NULL)
break;
}
while(linktem->score!=value);

if(linktem->next!=NULL){
link1=(STU *)malloc(LEN);
link1->num=5;
link1->score=1;
link1->next=linktem->next;
linktem->next=link1;
}

}
}

没有解决头节点问题,最好是用上面的那个框架

共用体

同一个内存段可以存放多种类型,但一瞬间只有一个成员其作用

内存单位以最大成员为准

不能初始化

共用体变量不能用作函数参数,可以使用指向共用体的指针

1
2
3
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
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
struct{
int num;
char name[10];
char sex;
char job;
union
{
int class;
char position[10];
}category;
}person[2];

void main(){
int n,i;
for(i=0;i<2;i++)
{
scanf("%d %s %c %c",&person[i].num,person[i].name,&person[i].job);
if(person[i].job=='s')
scanf("%d",&person[i].category.class);
else if (person[i].job=='f')
{
scanf("%s",&person[i].category.position);
}
else printf("input error!");
}
printf("\n");
for(i=0;i<2;i++){
if(person[i].job=='s'){
printf("%-6d %-10s %-3c %-3c %-6d \n",); //需补齐
}
else printf("%-6d %-10s %-3c %-3c %-6d \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
#include <stdio.h>
#include <string.h>

union Data
{
int i;
float f;
char str[20];
};

int main( )
{
union Data data;

data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");

printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);

return 0;
}

在VC上会出错

MSDN

知乎

建议使用下面替代

1
strcpy_s(str, sizeof(str), str1)

C的预处理

所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。下面列出了所有重要的预处理器指令:

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

预定义宏

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

main()
{
printf("File :%s\n", __FILE__);
printf("Date :%s\n", __DATE__);
printf("Time :%s\n", __TIME__);
printf("Line :%d\n", __LINE__);
}

相当好用的预定义

1
#define CHECK(x,y)(x<Wx && x>=0 && y>=0 && y<Hy)

Wx是全局变量,这也是可行的!(DFS中用于判断是否越界)

宏定义

(Macro),是一种批量处理的称谓。计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器编译器在遇到宏时会自动进行这一模式替换。对于编译语言,宏展开在编译时发生,进行宏展开的工具常被称为宏展开器。宏这一术语也常常被用于许多类似的环境中,它们是源自宏展开的概念,这包括键盘宏和宏语言。绝大多数情况下,“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。——百度百科

预处理命令可以改变程序设计环境,提高编程效率,它们并不是 C 语言本身的组成部分

如果程序中有大量相同变量数据要重新赋值,使用宏就不用一个一个地去修改。

替换过程是在编译前,只是单纯的做字符替换,所以不占存储空间

宏替换相当于实现了一个函数调用的功能,而事实上,与函数调用相比,宏调用更能提高

原因是:宏替换只占编译时间,不占运行时间

宏延续运算符(\)

一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)

字符串常量化运算符(#)

在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。

1
2
3
4
#define dprint(i) printf(#i)
不完全等效于
#define dprint(i) printf("%d",i)
输出的不在是数字,而是字符串

标记粘贴运算符(##)

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记

缝合怪!!!!

放一个实例

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

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void)
{
int token34 = 40;

tokenpaster(34);
return 0;
}

宏可以有参数!

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#define MAX(a,b) (a>b)?a:b
#define YES printf("YES")
main()
{
int x, y, max;
x = 5;
y = 10;
max = MAX(x, y);
printf("max=%d\n", max);
YES;
/*宏调用*/
}

字符串( “ “ )中永远不包含宏

文件包含

使用尖括号表示在包含文件目录中去查找(包含目录是由系统的环境变 量进行设置的,一般为系统头文件的默认存放目录,比如 Linux 系统在/usr/include 目录下),而不在源文件的存放目录中查找

使用双引号则表示首先在当前的源文件目录中查找, 若未找到才到包含目录中去查找

C的位运算

表示数值有3种方法:原码,反码,补码

**原码中的+0与-0的表示不相同,所以不利于计算机计算

反码不多用

Tips

以0开始的是8进制数

以0x开始的是16进制

无符号型只能表示正数

存在a&=b的情况

补码

complement number 补码数

正数:其原码,反码,补码相同

负数:先取反(最高位不变),再加1

一个8位,储存值范围在-128-127

得到十进制

第一步,各位取反

第二步,取十进制

第三步,加上负号,减1

计算机用补码形式存放数据

补码相加,得到相加数据的补码

位运算符

& 按位与

| 按位或

^ 按位异或 (相同为零,不同为或)

~ 取反运算符(是波浪线)(按位取反)

<< 左移运算符

1
>> 右移运算符

位移到超过位就会废弃

例子

例子很少

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main(){
unsigned a,b,c,d;
a=0331;
b=a>>4;
c=~(~0<<4);
d=b&c;
printf("%o \n %o \n",a,d);
} //取整数a从右端开始的4-7位

通过位段将字节拆分存储数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
struct DATA
{
unsigned a:2;
unsigned b:3;
unsigned c:4;
int i;
}data;

int main(){
data.a=2;
data.b=7;
data.c=9;
printf("%d",data.a);
}

C的文件读写

文件分为ASCII文件(文本文件)与二进制文件

C文件对文件的存取是以字节为单位的

有些C对FILE的定义:

1
2
3
4
5
6
7
typedef struct
{int -fd;
int -cleft;
int -mode
char *-nextc;
char *-buff;
}FILE;

fopen与fclose

您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:

FILE一定要大写

1
2
3
FILE *fp;
fp=fopen("/tmp/test.txt", "w+");
文件名,使用文件的方式
1
FILE *fp = NULL;

fp是指向FILE类型结构体的指针边练,使fp指向某一个文件的结构体变量,使文件指针变量找到与它相关的文件

输出失败会返回EOF

EOF是stdio.h中定义的符号常量,值为-1

r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。(相当于覆写)
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。

add 追加的意思

一个用于复制文件的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
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
FILE *in,*out;
char ch;
if(argc!=3)
{
printf("You forget to enter a file name");
exit(0);
}
if((in=fopen(argv[1],"r"))==NULL)
{
printf("Cannot open infile\n");
exit(0);
}//常用判断

if((out=fopen(argv[2],"w"))==NULL)
{
printf("Cannot open outfile\n");
exit(0);
}
while(!feof(in))//只要feof函数不是零,就一直复制
fputc(fgetc(in),out);
fclose(in);
fclose(out);
}

exit函数是stdlib里面确定的,用于退出整个程序

主要区别如下

  1. return返回函数值,是关键字; exit 是一个函数。

  2. return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。

  3. return是函数的退出(返回);exit是进程的退出。

  4. return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。

exit(0):正常运行程序并退出程序。
exit(1):非正常运行导致退出程序;

return():返回函数,若在主函数中,则会退出函数并返回一值。

1
fclose(fp);

需要养成关闭文件的习惯。

fgetc与fputc

1
fputc(ch,fp)
1
ch=fgetc(fp);

ch是字符常量

下面是写入并读取存入字符

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

int main(){
FILE *fp=NULL;
char ch='a';
fp=fopen("Hello.txt","w+");
if(fp==NULL)
{
printf("Cannot open infile\n");
return 0;
}
fputc('C',fp);
rewind(fp);
ch=fgetc(fp);
printf("%c",ch);
fclose(fp);
}

如果需要进行大规模读写,不推荐这种方式

feof

eof是文件结束标志

(End of file)

feof判断文件是否真的结束

1
int feof(FILE *stream)

设置了与流关联的文件结束标识符时,该函数返回一个非零值,否则返回0

文件结束,feof(fp)的值为1,否则为0;

fread与fwrite

读写数据块

下面这个案例写入一个字符a

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(){
FILE *fp=NULL;
fp=fopen("Hello.txt","w+");
if(fp==NULL)
{
printf("Cannot open in file\n");
return 0;
}
char f='a';
fwrite(&f,sizeof(char),1,fp);
//需要存储的变量的指针,空间,读写次数,文件指针
fclose(fp);
}

结构体,int型只能保存成二进制

貌似只有char类型才能保存成文本文件

读取次数一般写成1,数组或大空间的建议多次循环

fprint与fscanf

1
fscanf(fp, "%s", buff);

输入时要将ASCII转化为二进制

输出时有需要将二进制转化为ASCII

便于理解,但是效率太低

rewind函数

使位置指针返回文件的开头

无返回值

1
rewind(fp);

fseek函数

一般用于二进制文件,文本文件转化会发生位置偏差

1
2
3
4
5
fseek(fp,100L,0);
//位置指针移到离文件头100个字节
fseek(fp,50L,1);
//位置指针移到离当前位置的50个字节
fseek(fp,-10L,2);

负号前移,正号后移

就算不输入L,默认是L

SEEK_SET 文件的开头(0)
SEEK_CUR 文件指针的当前位置(1)
SEEK_END 文件的末尾(2)

ftell函数

1
printf("%ld",ftell(fp));

输出的long int型,表示字节数

ferror与clearerr

文件操作出错,ferror(fp)为1,

使用clearerr,使ferror与ferror值变为0

C的库

math.h

you need to compile with the link flag -lm, like this:

1
gcc fib.c -lm -o fibo

This will tell gcc to link your code against the math lib. Just be sure to put the flag after the objects you want to link.

sqrt()

开根号

fabs()

取绝对值

pow()

平方

exp()

e为底

log10()

就是lg,ln是log()

sin()与cos()

使用弧度制

默认均为double

比如sin30

1
sin(30*pi/180);

acos()

1
float PI = acos(-1.0);

C 库函数 double acos(double x) 返回以弧度表示的 x 的反余弦。

limits.h

INT_MAX

INT_MIN

stdlib.h

qsort()

以指针为调用条件,感觉不妙

1
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
  • base – 指向要排序的数组的第一个元素的指针。
  • nitems – 由 base 指向的数组中元素的个数。
  • size – 数组中每个元素的大小,以字节为单位。
  • compar – 用来比较两个元素的函数。

两个形参必须是 const void * 型!!

如果 compar 返回值小于 0(< 0),那么 p1 所指向元素会被排在p2所指向元素的前面

如果 compar 返回值等于 0(= 0),那么 p1 所指向元素与 p2 所指向元素的顺序不确定

如果 compar 返回值大于 0(> 0),那么 p1 所指向元素会被排在 p2 所指向元素的后面。

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>
#include <stdlib.h>

int values[] = { 88, 56, 100, 2, 25 };

int cmpfunc (const void * a, const void * b)
{
return ( *(int*)a - *(int*)b );
}

int main()
{
int n;

printf("排序之前的列表:\n");
for( n = 0 ; n < 5; n++ ) {
printf("%d ", values[n]);
}

qsort(values, 5, sizeof(int), cmpfunc);

printf("\n排序之后的列表:\n");
for( n = 0 ; n < 5; n++ ) {
printf("%d ", values[n]);
}

return(0);
}

对double类型数组排序(特别要注意)**

1
2
3
4
5
6
7
double in[100]; 

int cmp( const void *a , const void *b )
{
return *(double *)a > *(double *)b ? 1 : -1;
}
qsort(in,100,sizeof(in[0]),cmp);

有些奇怪,为什么会这样?

abs()

C 库函数 int abs(int x) 返回 x 的绝对值。

labs()

适用于long int,没测试过

string.h

1
int strcmp(const char *str1, const char *str2)

进入的是变量,返回0和1

memset()

1
2
char str[100];
memset(str,0,100);

数组初始化!!!

根据空间大小,从前往后初始化

https://baike.baidu.com/item/memset/4747579?fr=aladdin#5