C笔记ultra

这玩意儿是昵称 · 2024-10-14 · 次阅读


Ⅰ.语法(?)

一.程序执行

  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)这样的好处是可以直接运算

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

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

000000000:0

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

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

//由于-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-38里无法表示

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

//浮点数中的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–的方法,这需要知道是几位数。

这是不能判断原数字,而是那个10n,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,用于分配内存,需要<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;
}

这玩意儿是昵称