# Ⅰ. 语法(?)

# 一。程序执行

  1. 解释:由该程序执行操作(python)
  2. 编译:由该程序将语言翻译为机器语言,由编译出的程序执行操作(C)

# 二.printf 与 scanf <stdio.h>

\n 表示换行,如果要直接输出,输入 \\n 来转义

在写的程序中换行对编译器没有影响。

scanf 需要 & a, 但是 printf 不需要 &,double 在 scanf 中必须要用 % lf

scanf 里的非变量部分在输入时也必须输入,否则可能无法正确接收数据(空格为输入任意)

%d(ld): int(long long)

%f(lf): float(double,longdouble)

% u: unsigned long long // 即使是其他类型,也可以用 % u 输出,注意 :

% c: 字符串 会自动扩展其他位,比如传入 - 1 就会使所有位的都 % e (E): 科学计数法 变成 1(传入 printf 的补码 (@int 类型是这样的) )

//double ff=1E-10 也是可以的,就是 1 的负 10 次方,输出要确定保留位数(%.3/4/5lf),会四舍五入。

float/double 的表达是离散的,不连续,遇到不能准确表达的会就近选择(精度越高,能表达的数的间隔越小)## 见下

#% d 其实时默认时 10 进制输出,不过输入可以是其他进制,会自动转化。

% d 后面没有空格,是只读取到整数结束为止,其他留给下一个变量

如有,则会顺便读取整数结束后的所有空格个,这是特殊的,用于防止误读空格

//% o:输出 8 进制(不会自带 0)

//% x(X):输出 16 进制(不会自带 0x),x 的大小写决定了输出时的大小写(这点和 % e 一样)

16 进制两位就是一个字节(8bit), 即一个 char,经常用于表答二进制 (方便变换)

8 进制是因为以前有 12 位的电脑,用 8 进制表达方便,现在一般用于单片机

# 三。关系运算

运算符:

+,-,/,*:略 // 当 +- 作为单目运算符时 (表示正负),优先级最高且只能在后面。

%:取余 // 要获取一个运算的整数部分,直接整数运算,要获得余数,则取余。

=:赋值 // 在 C 中这是也是一个运算符有输出,唯一的自右向左。a=b=6 实际上是 a=(b=6)

​ 也就是说 b=6 的输出值其实就是 6

#可以一行定义多个同类型的变量用 "," 隔开。

#不要嵌套赋值,belike:r=(r=r+3)*6*(r=r+4)

# 三。五. 复合赋值

++/--(递增 / 递减,这属于单目运算符)

count++/-- 实际上是 count=count+/-1

前缀时 (++/--a): 此句就已经是 a+/-1

后缀时 (a++/--): 此句输出还是 a,此句结束后 a+/-1

# 四。变量与常量

变量类型一旦定义就无法改变 // 没有初始值时是乱码(原来内存里的不知道什么玩意儿)

常量一旦定义无法改变(const int)// 通常全大写以区分

#定义时可以有运算

# 五。变量类型

# 总起

表示范围:char<short<int<float<double

输入输出时的格式化:% d,% ld,% lf

在内存中的大小:1 字节 (char)(8bit, 即 8 位),2 字节 (short),4 字节 (int,long (32 位环境)),8 字节 (double,long (64 位环境),long long)

//int 的大小其实也是不确定的,它等于电脑 CPU 的寄存器宽度(字长 (cpu 一次可以处理的数据长度) 以及总线

在内存中的表达形式:二进制数 (补码)(int),编码 (浮点数都是)

//sizeof () 用于输出该变量的所占字节数,不能在其中运算 (会被无视),这是静态的:

sizeof(a++)=sizeof(a)    //即使在后面printf("%d",a)输出也还是a而不是a+1
sizeof(a+1.0)//输出是8,因为整数和浮点数运算前会转换成浮点数,所以是浮点数的8
               这不意味着有进行运算,只是判断了最后还是double类型而已
# 1. 整数(int (看编译器,即一个字)/bool)

一般用 int

是有范围的,还有 short (-32768-32767),long,long long int (C99)

// 如果读取到负数,处理时又不能带符号,最后还要输出。那么可以单独 printf 一个负号,然后 x=-=x

//bool 是人为定义的,它只存在于 C99,即布尔量

整数的内部表达:

18-------00010010

三种方案:

1. 特殊标志(用第一位为 1 来代表负数运算时遇 + 则 -,乘除保留或变 0)

2. 取中间数,即 1000000 表示 0,更大为正,反之为负

3. 补码(其实就是溢出丢掉从头再来 8bit 当进到下一位时,会舍弃第九位的 1)

11111111(255,当作补码时为 - 1,因为 + 1=0)这样的好处是可以直接运算

// 对于二进制一个数的补码是 2<sup>n</sup>-1

对于一个字节的变量(char):

000000000:0

111111111-10000000=-1~-128(补码)

00000000101111111=1127(少一位是因为 0 也要占一个表示方法,一共 2<sup>n</sup>)

// 由于 - 128 与 127 在二进制的表示上是连在一起的,所以:

char a=-128
a-=1                  // 此时输出的 a=127
char b=127
b+=1                  // 此时输出 b=-128

这个循环是:

-10......127-128......-10

对于 unsigned char:

0-255-0-255

布尔类型 (bool)

需要 #include<stdbool.h>, 但实际上不会输出所谓的 true 或者 false, 本质上还是 1/0


# 2. 浮点数(float,double)

一般用 double

** 精确计算不能使用浮点数!!!** 只能用整型或者 bcd 码

这种码只用 4bit(2^4=16), 也只表示 0~9, 它通过拼凑来得到数字,由于是表示 10 进制数,二进制运算时的结果可能要修正, 修正的规则是:当两个 BCD 码相加,如果和等于或小于 1001(10 进制的 9),无需修正。如果相加之和在 1010 到 1111 (即十六进制数 0AH~0FH) 之间,则需加 6 进行修正;如果相加时,本位产生了进位,也需加 6 进行修正。这样做的原因是,机器按二进制相加,所以 4 位 [二进制数相加时,是按 "逢十六进一" 的原则进行运算的,而实质上是 2 个十进制数相加,应该按 "逢十进一" 的原则相加,16 与 10 相差 6,所以当和超过 9 或有进位时,都要加 6 进行修正。

可以表示 +/-inf(无穷大 / 小,实际上是越界无法表达),nan(无效数字,比如 0/0,如果用整数,会报错,而浮点不会,只会输出 nan)

带小数部分,有 longdouble(C99)

float (4 字节,字长 32):有效数字 7 位,在 0 周围 10<sup>-38</sup > 里无法表示

double (8 字节,字长 64):有效数字 15 位,在 0 周围 10<sup>-308</sup > 里无法表示

// 浮点数中的 0 是单独拿出来表示的

浮点数的运算精度 (## 见上):

在运算超出 float 范围时,编译器会强制将其转换为 double,如果不想要这样,在数字后加 f

flaot a=1.345f

保留位数会影响结果,因为精度的问题,如果保留位数少,可能四舍五入后还是正确的,但是如果多起来,就会有误差。(注意,实际上内存中的就是不精确的,改变保留位数只是我们自己看的而已 )


浮点数的内部表达:

1bit 用于表达正负

11bit 用于表达指数部分

剩下的都是分数部分和其他没有利用的部分

浮点计算是由专用硬件负责的


# 3. 字符(char

也可以是整数类型,不一定都是字符(没有 “” 的话)

#字符串不能参与和整数或者浮点数的运算。

C 中使用 ASCII 编码所以:49=‘1’

字符加 n,就直接得到它后面第 n 个字符。

两个字符相减,可以得到它们之间的距离。

a+'a'-'A' 可以将大写转化为小写

a+'A'-'a' 可以将小写转化为大写

逃逸字符(\):

\b : 回退一格,实际上是回去,但是不删除(这取决于终端),但是会用后面直接连接的字符覆盖它(该字符不会再次输出)

\t : 到下一个表格位(直接到下一个固定位置)

\n,\r : 换行,回车,这来自于早期打字机,到现在,没什么区别

getchar (): 读入一个字符,返回一个 intEOF (-1),表示结束

使用 Crtl + C 强制结束

使用 Crtl + D (UNIX)/Z (WIN) 结束并输出 EOF

用户的直接输入实际上都在 shell 的缓冲区,程序运行时根据函数读取缓冲区中的数据 getchar 和 scanf 读取长度时不一样的(这也解释了为什么超出会顺延下去),这个缓冲区会有暂停的地方(用于等待你输入),如果使用了上面的快捷键,那么 shell 才会真正给出停止信号,给出 EOF

# 杂项

#整数之间的运算会直接舍弃所有小数部分,在计算过程中也是,但是整数与浮点数运算时,


整数会被转化为浮点数参与运算 // 但它还是整数,有时还会特意乘 1.0 来转化

​ (不如直接 double)


// 初始化时,浮点数应是 0.0

如果想要被当作纯二进制来看待,要写为:unsigned char(即不用补码)

这样表示范围就会变成 0~255,但是,此时不能表达负数

EX1. 断点

断点的一行是未被执行的。可以将鼠标移动到变量上查看此时该变量的值。 // 要调试运行

EX2. 交换变量(a,b)

t=a,a=b,b=t

// 整数类型除法会直接舍弃小数,可以用于整数求逆

# 4. 指针(单独放在下面)
# 5. 自定义类型
# 枚举

用于定义一些名字,而不用 const int 来:

enum 枚举类型名字,可不写{名字0,名字1...,Number};
// 这些都是常量,操作时默认 int 且从 0 开始 (所以后的 number 就真的是这个枚举的有效数量,有个套路,见下),enum 本身不是一种变量类型(但是本质上是 int),它是声明一种变量类型
要更改数值
enum a{RED=1,YELLOW,GREEN=5,NumCOLORS}
enum color {red,yellow,green};
void f(enum color c);// 使用这个类型时,前面要加 enum(C++ 不用)
int main (viod){
    enum color t=red;
    scanf("%d"&t); 
    f(t);    
	return 0;
}
void f(enum color c){
	printf("%d\n",c)	// 输出还是 1
	}

自动计数的枚举:

1729513141155

0 这里也能看出,实际上枚举很少作为类型使用,它作用在于:

  1. 枚举可以为一组整数常量赋予有意义的名称,使得代码更易于理解。例如,用 enum Weekday {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY}; 定义了一周的天数。在代码中使用 MONDAY 比直接使用数字 0(假设周一被映射为数字 0)更直观地表示星期一,提高了代码的可读性和可维护性。

  2. 通过枚举定义的变量只能取枚举中定义的特定值,编译器可以在编译时进行类型检查,防止意外地为变量赋予不合法的值。例如,如果定义了一个枚举类型 enum Color {RED, GREEN, BLUE} ,那么一个声明为该枚举类型的变量就只能被赋值为 REDGREENBLUE 这三个值之一,而不能被赋予其他任意整数值。

  3. 集中管理常量:

    当有一组相关的常量需要在程序中多处使用时,使用枚举可以将这些常量集中定义在一处,便于管理和修改。如果需要修改某个常量的值,只需要在枚举定义处进行修改,而不需要在代码中逐个查找和修改使用该常量的地方。

    例如,如果要修改表示一周中某一天的常量值,只需要在枚举定义中修改相应的枚举值,而不需要在整个程序中搜索并修改所有使用该常量的地方。

  4. 易于扩展:

    如果需要在现有枚举中添加新的常量,只需要在枚举定义中添加新的枚举值即可,不会影响到已有的代码逻辑。例如,在 enum Color {RED, GREEN, BLUE} 的基础上,如果需要添加一个新的颜色 YELLOW ,只需要将枚举定义修改为 enum Color {RED, GREEN, BLUE, YELLOW} ,而使用该枚举的代码无需进行大规模修改。

# 结构体

格式:

struct 标签 { 
    int a;
    char b[];
    float c;
    ...(其他有效变量定义,也可以是其他结构体)
}  结构变量(可以不只定义一个);
// 一般三者至少出现俩

互相包含的结构体,要进行不完整声明:

struct B;    // 对结构体 B 进行不完整声明
// 结构体 A 中包含指向结构体 B 的指针
struct A
{
    struct B *partner;
};
// 结构体 B 中包含指向结构体 A 的指针,在 A 声明完后,B 也随之进行声明
struct B
{
	struct A *partner;
};

即使两个结构体的成员一样,也会被当作两个不同的(前提是标签和结构变量要有不同)

使用:

struct Simple t1, t2[20], *t3;
// 分别表示一个结构体,结构体数组,指向该结构体的指针
也就是说*t3可以指向t1

与 typedef 连用:

typedef struct
{
	int a;
    char b;
    double c;
} Simple2;   // 注意,这里的 Simple2 是别称,结构体三者只出现了一个
Simple2 u1,u2[],*u3
# EX
# EX 整数求逆
  • 如果 %10,会得到个位数

  • 如果 / 10,会去掉个位数

    当去掉一个个位数后,会有新的一个个位数(原来的十位数),可以再 %10 读取

    C 中不会自动换行,所以 while 中直接 printf 出来,连起来的就刚好是逆向的

    但是 0 在开头也会输出。如果不要,那么用 ret*=10+a (读取的数) 的循环顶位,再输出。(0*10=0, 所以在读取到第一位有效数字前的 0 不会被输出,在之后的则有 * 10 顶出个位来加)

如果要正序且末尾有 0,那么只能使用 i-- 的方法,这需要知道是几位数。

这是不能判断原数字,而是那个 10<sup>n</sup>,n 可以先用 / 那个数读取长度,这时要用 pow (), 当然。也可以在读取循环时顺便来个变量 * 10

// 这里使用 while 代替 do-while,虽然 mask 是对的(直接加会因为 do-while 的无条件执行一次而多 10 倍,当然,可以事后 / 10),但是读取后原数没了,所以有了 t

# EX 类型转化

所有的类型转化都只是在运算中,不会改变这个变量以及它本身的类型

自动:

当运算符两边不一致时,会自动传化为较大的类型:

char--short--int--long--long long

int--float--double

但是对于 printf(不包括 scanf):

小于 int 都会变成 int

float 都会变成 double

强制:

优先级高于所有其他运算

(int) 32 // 注意安全性,不要越界

# 六。条件判断(if/switch)

# 1.if-else
if (x>100) {
代码1
}else if (x>1) {
代码2
}else { 
代码3
}

级联 if-else:

必需范围从大到小,因为实际执行的只有一个,而且顺序执行。

如果都要判断,要用多个 if(能不用就不用,会多次判断)

// 不同于 python,c 中的 if,else 都是就近匹配,不是依赖缩进,所以最好加上大括号

# 2.switch-case
switch (type) {         //type 必须是整数类型
case 常量:              // 这个常量可以是常数,也可以是常数计算的表达式
xxx                      C99中还可以用定义的常量
break
case 常量:
xxx
break
default:               // 当上述一个都没有时跳转
xxx
}                      // 这时相当于一个级联 if

switch 本质上是一种跳转,如果不 break,会直接向下执行,而且

这种跳转只有一次,跳完后所以 case 都当作不存在。

(break 会直接跳出整个 switch)

ps:比较像批处理中的标签一样的玩意儿(?)

# 七。循环 (for/while)

# 1.while
while (条件){
循环体                         
}
// 只要条件依然满足,就会循坏,不会执行下面的代码
//if 只有一次判断,而 while 有多次

在调试时,可以随便在 while 中 printf 些什么,用于直观判断运行情况,最后记得注释掉就行。

数位数的算法:

1. 用户输入 x

2. 初始化 n=0

3.n++

4. 如果 x>0,回到 3

5. 否则 n 为结果

// 一般来说,在进入 while 前,循环体要先执行一次,这样才会是 “循环”,这就是:

do-while 循环

do
{  
循环体
}while(循环条件);    //先执行一次,然后判断
//不要忘记封号          也可以说是先执行,再判断,while则反之

也就是说,do-while 至少执行一遍,while 可能一遍都不做

EX:rand () 可以召唤随机整数,使用方法:

#include<stdlib.h>
#include<time.h>
...
int main(){
srand(time(0));
int a=rand()%100+1   // 这使得数在 100 以内
...
}
# 2.for

for (初始条件;循环条件;每轮动作){           //这个动作接到每次循环结尾
循环体                                   //i的值可以在初始化中定义
}                                         但是只有C99才行,初始条件可以                                           省略           

循环可以有两种计数方式:

for (int i=0;i<5;i++) 或者 for (int i=1;i<=5;i++) 都是循环 5 次

for 循环实际上与 while 是一样的!!!任何 for 都可以改写成 while 循环 。

// 如果是要固定次数的循环,那么用 for;

// 如果必需执行一次,就用 do-while 循环;

// 其他都用 while

EG: 判断素数

...
isPrime=1         //这里实际上是证伪,所以初始为1,用来避免反复输出,for(i=2;i<x;i++) {                    //掐头去尾是这样的
	if (x%1==0){
		isPrime=0;          //实际上不break也行,但是会反复赋值
		break;              //continue只会直接跳到下一轮循环
	}
if isPrime==1{            //这里也可以不用isPrime,直接判断i==x
	printf("是素数\n");      看有没有完整执行for,是否有break掉。
}else{
printf("不是素数是\n");
}
}  
...
# 3. 循环的嵌套

注意,循环的控制变量必需不一样

比如,判断 100 以内的素数:// 是素数改成 printf

可以在之前的代码上套一个

for (int x=2,x<=100,i++) {}     //记得也要x++

输出 50 个素数:

int cnt=0;  
while (cnt<50){}     //输出后记得cnt++

也可以用 for (x=2;cnt<50;x++) // 这时删掉最后的 x++

EX. 接力 break:
这需要一个变量(exit), 开始是 1,当满足条件时将其赋值为 1,随后多个 break 加上 if,判断 exit 是否为 1(这是为了防止未完成就 break)

也可以使用 goto, 用法和命令行差不多,但是定义标签时的:在后面。

这玩意儿最好只用在快速跳出多个循环(要不然乱跳容易乱)

# 八。逻辑运算 / 条件运算 /,

# 1. 逻辑运算

运算符:

!:非!a:是 a 就 false,不是则 true

&&:与 全部 true 则 true

||:或 一个 true 即可

优先级:

()> ! > 关系运算 > && > || > 赋值运算

方向:

自左向右,如果已经不成立,就不会接下去判断,所以,赋值运算不要写到里面,可能不会执行,即发生短路

# 2. 条件运算

运算符:

(条件) ? 条件满足时的值 : 条件不满足的时候的值 // 相当于 if,else

优先级:

只大于赋值运算

嵌套条件表达式:

自右向左结合(快跑,没有可读性的玩意儿)

# 3. 逗号表达式

优先级:

最低,比赋值还低,要用到必需通过括号提升优先级

运算方式:

取右边值,比如 a=(1,2), 此时 a=2。

一般不运算,平常在 for 中来加入多个每轮动作(也不是运算.jpg)

# Ⅱ. 函数与数组

# 函数

// 为了避免重复代码(同时方便维护),或者精简主函数

# 1. 定义函数

一般来说,main 函数写在最下面,因为编译器是自上而下看的,否则有可能会编译不通过(这看编译器),如果一定要 main 在前面,可以先来个函数原型声明

//(其实就是将函数大括号以外的部分复制一份,加个封号然后单独放在 main 函数前面(事实上可以写在里面,原型声明里也可以不用写或乱写参数名称,只要类型定义是一样的就行(不建议))。定义的部分一定要和声明一致,否则 error,如果不声明,且定义部分在 main 函数下面,那么有些编译器会猜测该函数的返回类型,如果和下面实际定义不相同,有可能也会抛出 error (发生类型冲突))//

函数原型声明不能冲突,但是可以放空,表示不确定,这时假设与实际冲突,会按照实际。

(这样做会没有对输入类型的检查,可以在 double 中传 int, 不会报错但是值不对,如果确实没有,加 void)

函数中不能定义函数,但可以原型声明。

int (返回类型) hanshuming (函数名) ()(参数表){

// 定义函数还可以用 void (中文意是没有),表示没有返回值,int 有返回值,return 必需带值,size_t 就是 unsign int 类型

// 参数表里逗号分割,看到这个就可以断定这段代码是函数,所以即使是空的也要有,输入时是按顺序复制的

函数体

return 变量

// 返回主函数的结果 int 就是说这里 return 的变量是 int 类型

}

调用时写为:

hanshuming (变量) ;

// 即使不输入什么值,也要括号,否则会 warnning

return:

1. 停止函数执行,并返回值

2. 返回一个表达式

#一个函数中可以有多个 return 语句,这会导致不是单一出口

调用有返回的函数却不赋值也是可以的,不会警告或报错。

当然,没返回的肯定不能赋值

# 2. 参数传递

可以传递:

字面量,变量,函数返回值,计算结果

有强制类型转换,如果声明的参数是 int,传入的是 double, 这个 double 会变成 int (warning),反之也是

C 语言只能传值,而不能是变量 (即使是指针,实际上也只是传了个地址数据,也不是把指针本身传递过去)

形式参数与实际参数

函数声明的就是形式参数,实际参数是你调用时传过去的数据 (不是变量)

本地变量 (在函数内部定义的变量就是这个函数的本地变量,包括参数)(局部变量 / 自动变量)

每次函数运行会产生独立变量空间(栈帧?)

变量的生存期和作用域:

1728559512145

注意:C 语言没有 jacvascript 的闭包特性,所以完全不能跨。

1728559884799

在 {} 中定义的参数,生存期和作用域也仅限于 {} 中这里即使在 else 中,也不能访问。

如果是 [static]( 静态存储期 ,全局变量默认有。定义这个,只能改变生存期,不能改作用域,该不可见还是不可见。它的作用在于当这个函数被多次调用时,它的值不会被初始化(不在栈中)), 生存期会变成整个程序,但是作用域没变化

局部变量优先原则:当在函数中的其他类型的 {} 中定义一个在之外定义过的变量,在 {} 中出现,调用的是其中定义的那个,不是原有,比如:

1728565695161

直接写一个 {} 一般用于调试

这里输出的两个 a 不一样

1728565791256

# 数组

# 1. 定义数组

类型 数组名 [元素个数] eg.int num [100] // 索引从 0 开始是第一个,所以只有 0~99

赋值:

1728786107668

前面一种写法仅限 C99

定义后必需遍历数组初始化

C 中有个特殊写法,就是 count [number]={0}, 效果和遍历写 0 是一样的

输出时也是循环遍历

集成化初始时的定义:

int a[10]={[0]=2, [2]=3, 6}      // 没有得到值的,都是 0;没有指定索引的,顺延上一个

例题:统计数组

# 2. 数组运算

要改变数组中的某一个,使用 search 函数

loc=search(x,a,sizeof(a)/sizeof(a[0]));   // 不要 []
if (loc!=-1){          // 这个函数没找到会返回 - 1,实际上是初始值,这个函数遍历了每个位置,有就加 1
    printf("%d在第%d个位置上\n",x,loc);  // 得到的其实是索引,不是位置,因为初始不是 0
}else{
    printf("不存在");
}

search () 要提供大小,其实是因为数组作为函数参数时,是作为指针,只传了第一个元素的地址

# 3. 数组的大小

sizeof 数组 :得到字节数(对于 int,/4 才是数组内元素的数字)

对于任意类型,可以用:

sizeof(a)/sizeof(a[0])

# 4. 数组的赋值

数组变量本身不能被赋值,也不能将数组赋值给数组

只能遍历

1728788625234

素数还有一种求法(但是看起来比之前的更长,所以没写在那个 cpp 里面)

1728790633321

当发现一个素数时,将他加到 prime 里面去,用 cnt++ 就是可以先写入这个位置,再移到下一个,这样可以从第一个开始写,这是因为非素数都可以由比他小的素数乘以某个数得到。

// 那个 (i+1)%5 是为了控制一行输出 5 个

其他算法:

1728791561052

就是每使用一个数,就在数组中排除它的倍数

就是:

1728791648318

// 赋值为 0 表示不是素数,每个数都判断过去是否是它的倍数

// 第一个 i 用于初始化,后面拿来遍历输出

# 5. 二维数组

int a[3][5]  // 一般认为是三行五列(这是内存中的放法), 不能 a [i,j],这样里面是逗号表达式,实际上是 a [j]
集成化初始定义:
    a[][5]={0,1,2,3,4},{2,3,4,5,6}   // 内存中都是直接填的,所以写成一维数组也可以

# 6. 字符数组与字符串

char word[]=['H','e','l','l','o']; // 字符数组
char word[]=['H','e','l','l','o','\0'];// 字符串,本质上还是字符数组,C 语言中实际上没有叫字符串的东西
或者
char word[]=['H','e','l','l','o',0]
字符串就是以0结尾的字符,它表示字符串的结束(不算在字符串长度中,但是占空间(有索引)),如果要读入特定个数,记得位数+1,留给0
   特殊的:
    char word[]="";是一个空字符串“\0”,而不是普通的字符数组
# 字符串函数 <string.h>

可以对普通的字符数组操作

序号 函数 & 目的
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾,作为返回值。
3 strlen(s1); 返回字符串 s1 的长度 (不包括结尾 0, 用 char 时 sizeof 就包括,其他类型不用 sizeof,单位不一样)
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。空格,结尾的 \0 也会算进去,实际上这个函数就是挨个比较,不相等时输出这两者之间的差值。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
# strcmp:

它的原型,可以是:

1729430901698

# strcpy:

因为有时不知道这个指针的作用域,防止突然消失,拷贝一份

char *strcpy(char *restrict dst,const char *restrict src);
//restrict表明src和dst不重叠(C99),否则会指向同一个地方

这个函数会返回 dst 值

使用时要申请内存:

char *dst = (char*)malloc(strlen(src)+1)// 为 \0 提供空间
strcpy(dst,src)
......
free(dst)

它的原型是:

然后可以传入俩数组(代码里的 const 防止自赋值(指向同地址))

rest 应该是 ret, 这是因为此时的 dst 指向最后的元素,不能直接返回它

要先用 ret 记录好初始位置

# strchr:

还有一个 strrchr 表示从右边开始找

如果要找第二个:

char s[]="hello";
char *p =strchr(s,'l');
p = strchr(p+1,'l'); // 这表示从第一个‘l’后一个开始找

输出找到的字符前的一段,可以:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(){
char s[] = "hello";
char *p=(char*)malloc(strlen(p)+1)//malloc 返回指针 + 1 也是按类型
strcpy(t,p);
printf("%s\n"t);
free(t);
return 0;
}

后一段:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(){
char s[] = "hello";  // 这只是一个字符数组,这里不能是字符串
char *p=strchr(s,'l');
char c=*p;   // 暂存原来的值
char *t=(char*)malloc(strlen(s)+1);
strcpy(t,s);   // 将被提前结束的 s 字符数组 copy
printf("%s\n",t);
*p=c         // 将 s 数组还原
free(t);
return 0;
}

这是因为字符串函数的操作都是以 \0 作为结束,所以这里实际上是提前结束了 s 字符串

# strstr:

还有 strcasestr (不区分大小写的查找)

基本格式

char *strstr(const char *s1,const char *s2);
char *strcasestr(const char*s1,const char *s2);
# 字符串变量 “字面量”
char *str ="Hello""World"   
/* 实际上是六个字节,因为编译器会自动加 \0。连续的字符串会自动拼成一个
等价于。这种写法等同于上面那个字符串的写法(编译后)。*/
char *str ="Hello\
World"   // 注意,这样上下俩行的连接会带上 Tab, 这样防止太长,不好看。

但是不能用字符串来运算

字符串数组前默认有 const, 不能修改

在编译时就已经确定,如果存在字面量相同的,那么会指向同一个地方

想要修改,就只能是普通的字符数组(实际上它们储存的位置都不一样)

1729425745879

char * 不一定是指向字符串,它也可以用来指向普通的字符数组,

用它来直接定义的,一定是字符串(这一句必需初始化,否则只是普通的指针)。(不能是空指针)

字符串数组输出时,用 % s

一个 % s 只会读到空格前,继续输出才行。

// 这是不安全的,因为不知道实际输入可能有多长,可能发生数组越界

限制输入可以用 %7s (表示最多读 7 个,多余的会放到下一个 scanf(如有),这意味着可以不用回车同时输入上下多个 scanf), 但是若某一个要输入的长度小于 7,必需在输入完这个后回车再继续

# main 函数的参数:

1729428005682

argc [0]: 一定是 a.out(该程序名), 程序刚开始时的输入会放到后面(空格分隔)

1729428257169

这是使用符号链接来启动程序(符号版快捷方式?)

# Ⅲ. 不知道是什么

# 一。辗转相除法

(更为高效)

算法内容:

如果 b=0,计算结束,a 就是最大公约数,否则,计算 a 除以 b 的余数,让 a=b, 而 b

等于那个余数,回到第一步

演示:

a b t

12 18 12 // 这里实现了交换

18 12 6

12 6 0

6 0

所以,最大公约数是 6

# Ⅳ. 指针

# 从入门到放弃

# 1. 取址符 (&)

int i=&i    //会有warning,强制类型转换可以消除
//在64位下typeof(&i)是8个字节,32位下和int一样4字节
地址输出用%p

取地址不能有运算

数组的指针默认是指向第一个元素的地址,数组在内存中是连续的。

数组越界,要传递数组大小,就是因为数组本身就是一个指针,没有边界检查。

# 2. 指针类型的变量

int *p = &i    //p 在内存中得到的是 i 的地址,称为 p 指向 i
int* p,q和int *p,q 是一样的,此时q都是普通的int  // 不存在 int * 类型!!
    定义只再次使用*p就是解引用,值为指向的地址的内容这里类型就是int(每次都要)。

1728812248575

*p 就是 i, 这样就可以访问外面的变量

scanf 就是把你传入的数据写到那个变量的地址上,如果不加 &,会把变量名当作地址,写到别的地方去(没有类型检查)

# 3. 指针使用

# 1. 在函数中交换变量

使用 * 变量就可以间接对 main 函数的变量改变

1728812654340

指针常用于返回值,尤其是多个 (return 只能返回一个)

为了区分返回,函数返回状态值,指针返回数据值 (所以最后常来 return 0)

注意:

*p 必须先指向一个变量,再 * p 赋值,否则那个值会被当成是地址

# 2. 传入数组

传入的实际上不是数组,而是指向这个数组的指针 (C 语言中只能传数值是这样的)

函数参数表中的数组,实际上是个指针,在 [] 中写东西,是完全没有用的

这就是说,你可以直接将数组传入一个指针

1728813490503

数组变量是一个特殊的指针,单个单元都是变量,而且在内存中是连续的(与指针数组不一样)

数组 = const 指针 (所以数组不能互相赋值)

int b []-----int const *b,此时不能用 b++,

在 C99 中:

被 const 的指针指向的变量可以变,但是不能是通过 const 指针

int i;
const int* p1 = &i;    //实际上是const *p,也就是*p/i(解引后的值)不能改,但是p(地址)可以改
int const* p2 = &i;    //同上
int *const p3 = &i;    //p(地址)不能改,但是*p/i可以

const int b []: 表示里面的所有都是常量,这可以在变量原型中写,就不会改变传入数组的值

# 3. 指针运算

# +,-

p++, 实际上是加了一个类型的大小 (+1 就是加一个类型大小,sizeof)

*p++(常用于数组类的连续操作): ++ 的优先级比解引用操作符 * 的优先级高。在表达式 *p++ 中, ++ 操作符会先于 * 操作符被执行。这意味着 p 指针首先会增加,然后 * 操作符会解引用增加后的指针。

但是,由于 p++ 是一个后缀递增操作符,它返回的是递增前的指针值。所以,即使 p 指针在 * 操作之前已经递增了, *p++ 表达式仍然会返回递增前的指针指向的值。

人话:表达式的值还是 * p, 但是这句之后指针指向 *(p+1)

*q=a[0]--------*(q+1)=a[1]   //* 是单目运算符,所以加 ()
指针也可以相减,是两者之间的距离(地址差/sizeof
# 其他运算

<,<=,==,>,>=,!=(地址大小比较,数组是递增排列的)

# 0 地址

1728820093755

所有进程都有 0 地址(都是虚拟地址),也是不能写的,有的系统,不能读。

特殊事情包括:1. 初始化(没赋值就崩溃)

​ 2. 返回值(这事成不了!)

NULL: 必须全大写,有的编译器只能用 NULL,0 和 NULL 反而不一样

# 赋值

必需同类型,因为不同类型的 sizeof 不一样。

# 4. 指针类型转化

1728821034165

注:强制类型转化的作用都只限于该句

# 5. 指针用处

1728821145218

# 4. 动态内存分配

1.malloc()

C99 之前不能用变量定义数组大小,所以:

int *a=(int*)malloc(n*sizeof(int));  //malloc, 用于分配内存,需要 & lt;stdlib.h>
// 用法:
malloc(size_t size)    // 不能传递类型,所以 sizeof (以字节为单位),返回的是 void*,(int*)// 上面就是指针赋值,前面用于指定类型 (这样才能赋值)
                       // 就是在转化
然后就可以当数组了

如果申请空间失败,会返回 0 或者 NULL,还能用于推出循环,比如

1728821974878

会报错,但是不会终止程序,还会向下进行。

2.free()

只能还申请空间 (不是申请的不行) 的首地址,否则会报错并终止(有运算也地搞回来)

free (NULL):什么事情都不会发生,因为指针一般习惯上会初始化为 0,要是没用到,也不会报错

切记:malloc () 最后一定要接 free (), 但是不要再次 free

# EXTRA

来自菜鸟教程 (?)

# 指针数组:

ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。

#include <stdio.h>
 const int MAX = 3;
 int main (){
   int  var[] = {10, 100, 200};
   int i, *ptr[MAX];
   for ( i = 0; i < MAX; i++){
      ptr[i] = &var[i]; /* 赋值为整数的地址 */
   }
   for ( i = 0; i < MAX; i++){
      printf("Value of var[%d] = %d\n", i, *ptr[i] );
   }
   return 0;
}
还可以有字符串指针数组
char *a[]={"Hello","World","aadfdfefcrverg"}
//a[1]=Hello,a[2]=World...

也可以用一个指向字符的指针数组来存储一个字符串列表,如下:

#include <stdio.h>
 
const int MAX = 4;
 
int main (){
   const char *names[] = {
       "Zara Ali",
       "Hina Ali",
       "Nuha Ali",
       "Sara Ali",
   };
   int i = 0;
   for ( i = 0; i < MAX; i++){
      printf("Value of names[%d] = %s\n", i, names[i] );
   }
   return 0;
}

这跟普通数组相比,地址是不连续的,运算方法一样。

字符指针数组可以达到和枚举相似的效果:

char *s[3]={"apple","bpple","cpple"};
int a;
scanf("%d",a);
print("%s",s[a]);
// 这样就可以将数字 (作为索引) 对应上字符串

# 指向指针的指针:

纯套娃,定义就是 int **var 等

这种变量解引用一次后得到的就是被指向的指针,仍然是个地址

#include <stdio.h>
 
int main ()
{
   int  V;
   int  *Pt1;
   int  **Pt2;
   V = 100;
   /* 获取 V 的地址 */
   Pt1 = &V;
   /* 使用运算符 & 获取 Pt1 的地址 */
   Pt2 = &Pt1;
   /* 使用 pptr 获取值 */
   printf("var = %d\n", V );
   printf("Pt1 = %p\n", Pt1 );
   printf("*Pt1 = %d\n", *Pt1 );
   printf("Pt2 = %p\n", Pt2 );
   printf("**Pt2 = %d\n", **Pt2);
   return 0;
}

# 返回指针的函数:

定义:int * myFunction ()

C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量

#include <stdio.h>
#include <time.h>
#include <stdlib.h> 
 
/* 要生成和返回随机数的函数 */
int * getRandom( )
{
   static int  r[10];             // 这样结束后只是不可见,不会销毁,可以传递其值
   int i;
 
   /* 设置种子 */
   srand( (unsigned)time( NULL ) );     //srand () 用于初始化 rand (),null 其实是默认
   for ( i = 0; i < 10; ++i)        // 获取时间是为了保障 rand () 的随机,否则多次调用时可能会重复
   {
      r[i] = rand();
      printf("%d\n", r[i] );
   }
 
   return r;
}
 
/* 要调用上面定义函数的主函数 */
int main ()
{
   /* 一个指向整数的指针 */
   int *p;
   int i;
 
   p = getRandom();
   for ( i = 0; i < 10; i++ )
   {
       printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
 
   return 0;
}

# 函数指针:

定义

typedef int (*fun_ptr)(int,int);  // 声明一个指向同样参数、返回值的函数指针类型
fun_ptr a=max;  // 声明这个类型,只是为了在这句定义里检查类型是否符合,其实就是类型检查,还有就是方便修改,不用一个一个改类型,同时方便创建
...

# 回调函数:

函数指针作为某个函数的参数

可以根据不同的条件调用不同的函数,比较灵活

#include <stdlib.h>  
#include <stdio.h>
 
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++)
        array[i] = getNextValue();
}
 
// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}
 
int main(void)
{
    int myarray[10];
    /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int (函数的返回值), 而不是函数指针 */
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}