Featured image of post Clang学习笔记

Clang学习笔记

C语言学习时记录的笔记,比前一个笔记详细

ClangLearn

Clang 定义符号常量

在宏中定义符号常量非常简单,格式如下

1
#define KEYWORD "String"

eg:white_check_mark:下面这句废话!啥也没有干就是替换了一句话.

1
2
3
4
5
6
7
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"
int main()
{
	printf("%s\n", FRELON);
	return 0;
}

Thinking:电脑💻为什么知道字符串常量在哪里该结束呢?万一把后面的代码也 读取为字符串常量了呢?

其实==Clang==会自动在字符串常量结尾加上一个转义字符\0来表示读取结束.

Clang 数据类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
graph LR
a[数据类型]-->b[基本类型]
a[数据类型]-->c[指针类型]
a[数据类型]-->e[空白类型]
a[数据类型]-->d[构造类型]
b-->f(整数类型:int)
b-->g(浮点类型:float,double)
b-->h(字符类型:char)
b-->i(布尔类型:_Bool)
d-->j(枚举类型)
d-->k(数组类型)
d-->l(结构类型)
d-->m(联合类型)

signed和unsigned的区别

一般情况下,signed都表示有正负号的区别。而unsigned表示没有负数,只有正数。

==1Byte=8bit==

Byte==比特

Clang字符串

声明字符串:char name[number];

字符串赋值:name[0]=‘F’;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
	char Gage[10];
	Gage[0] = 'J';
	Gage[1] = 'a';
	Gage[2] = 'v';
	Gage[3] = 'a';
	Gage[4] = 'N';
	Gage[5] = 'o';
	Gage[6] = '.';
	Gage[7] = '1';
	printf("我想说:%s\n", Gage);
/**-------------------------**/
	char newBee[]={'J','a','v','a',' ','N','o','.','1','\0'};
	printf("I think :%s\n",newBee );

或者使用第二种

1
char battle[]= "我认为Java天下第一";

需要注意的是:我们需要手动在数组末尾加上\0来表示读取结束,否则会出现下面的情况

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"
int main()
{
	printf("Java天下第一\n");
	char newBee[]={'J','a','v','a',' ','N','o','.','1'};
	printf("I think :%s\n",newBee );
    printf("%s\n", FRELON);
	return 0;
}

上面是源码,下面的是运行结果.

1
2
3
4
/mnt/d/Clang_test » gcc hello_world.c -o hell && ./hell                                                frelon@Joker-Lee
I think :Java No.1`
                   �5
Written By Frelon O(∩_∩)O
1
char newBee[]={'J','a','v','a',' ','N','o','.','1','\0'};

改成下面这样的就不会报错了

Clang if判断

 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>
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int a;
	printf("How old are u :" );
	scanf("%d",&a);
	if(a<12) printf("Out of  here\n");
	else printf("Please come in\n");
	printf("%s\n", FRELON);
	return 0;
}
/** ---------------------------- **/
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int a;
	printf("How old are u :" );
	scanf("%d",&a);
	if(a<12) {
		printf("Out of  here\n");
	}
	else {
		printf("Please come in\n");
	}
	printf("%s\n", FRELON);
	return 0;
}

可以看到我们可以用这种最简便的方式来写if else,当然下面的那种才是我们提倡的方式,因为方便改Bug🤣。

Clang switch判断

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int a = 40;
	switch(a){
		case 10 : printf("10\n");break;
		case 20 : printf("20\n");break;
		default : printf("Error!\n");

	}
	printf("%s\n", FRELON);
	return 0;
}

可以看出来switch很简单,但是实际编程中我们用for更多一点。

Clang 找到素数

 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
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

void main()
{
	int  gol [100];
	for (int se = 0;se<=99;se++){
		gol[se] = (se+1);
	}
	for (int sup = 0;gol[sup]<=100;sup++){
		if (gol[sup]==1||gol[sup]==2)
		{
			printf("%d是素数\n",gol[sup]);

		}else{
			int yupe;
			for (int i = 2; i < gol[sup]; i++)
			{
				yupe = gol[sup] % i;
				if (yupe == 0){
					printf("%d不是素数\n",gol[sup] );
					break;
				}

				if(yupe != 0 && (gol[sup]-1) == i){
					printf("%d是素数\n",gol[sup] );
				}
				
			}
		}
	}
	printf("%s\n", FRELON);
}

这个是上面知识的综合运用

Clang指针

指针:一般是一个抽象概念,用于指向内存中的地址

指针变量:一般是☞指向内存中的某个地址值

定义指针变量:类型名 *指针变量名;

eg:int *num;

clang指针取地址运算符:&

clang指针取指针变量数据值:*

eg:

1
2
char *str = &a;//取内存中的地址的值
printf("%c \n",*str);//取指针变量指向的数据的值

Clang🈲给未初始化的指针变量赋值!

Clang给未初始化的指针变量赋值的做法是非常危险的 ! 并且也是不能这么写的,因为该指针没有初始化,系统就不知道这个指针应该指向内存中的具体哪一个值。如果这个时候给它赋值,就相当于在内存中随便找一个地址给其赋值,如果我们修改了内存中系统关键的数据,那就有可能导致系统崩溃,又或者我们随机赋值给了别的程序,也会导致别的程序出现问题,同时程序本身也不能给正确的数据赋值,也可能会出现错误

1
2
3
4
5
6
7
#include <stdio.h>
int main {
    int *ios;//这种不初始化的行为非常危险
    int *android = null;//建议所有的不知道目的的指针都这样,指向null。
    *ios = 123;
    return 0;
}

Clang指针与数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
#define FRELON "Written By Frelon O(∩_∩)O"

void main()
{
	char ui[] = "Hello_world";
	char *jk = ui;
	
	printf("ui[0]   = %p\n", &ui[0]);
	printf("ui      = %p\n", &ui);//整个字符串数组的地址就是数组第一个元素的地址

	printf("ui[1]   = %p\n", &ui[1]);
	printf("p(ui+1) = %p\n", (jk+1));//按照上面的思路来,那么数组第二个元素应该是数组的指针变量+1

	printf("%s\n", FRELON);
}
1
2
3
4
5
6
/mnt/d/Clang_test » gcc hello_world.c -o hell && ./hell
ui[0]   = 0x7fffcd7b163c
ui      = 0x7fffcd7b163c
ui[1]   = 0x7fffcd7b163d
p(ui+1) = 0x7fffcd7b163d
Written By Frelon O(∩_∩)O

上面就是clang指针与字符串数组,下面我们再看看clang指针与数字数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <string.h>
#define FRELON "Written By Frelon O(∩_∩)O"

void main()
{
	int osi [] = {1,2,3,4,5,6,7,8,9};
	int *sdCard = osi;

	printf("osi[0]    的指针: %p\n"  , &osi[0]);
	printf("osi       的指针: %p\n"  , &osi);
	printf("sdCard    的指针: %p\n\n", sdCard);
	printf("osi[1]    的指针: %p\n"  , &osi[1]);
	printf("sdCard+1  的指针: %p\n"  , sdCard+1);

	printf("%s\n", FRELON);
}
1
2
3
4
5
6
7
8
/mnt/d/Clang_test » gcc hello_world.c -o hell && ./hell
osi[0]    的指针: 0x7ffff6824620
osi       的指针: 0x7ffff6824620
sdCard    的指针: 0x7ffff6824620

osi[1]    的指针: 0x7ffff6824624
sdCard+1  的指针: 0x7ffff6824624
Written By Frelon O(∩_∩)O

Tips:使用scanf的时候,大家有没有发现,当我们需要输入单个intcharscanf("%d",&sis)。我们需要用到指针来指向该变量内存中的地址,但是当我们要获取字符串数组的时候,我们可以scanf("%s",name) 我们就不需要给数组也来个**&**取它的内存中的地址,想想看这个是为什么?

在这里我们就可以推测出,数组名可能就是一个☝️内存中的地址信息,而且还是第一个元素的地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int lolita [] = {1,2,3,4,5,6,7,8,9};
	int *po = lolita;
	
	printf("lolita    = %p\n", lolita);
	printf("lolita[0] = %p\n",&lolita[0] );
	printf("po        = %p\n", po);
	printf("*po       = %d\n\n", *po);

	printf("lolita[1] = %d\n",lolita[1]);
	printf("*(po+1)   = %d\n", *(po+1));
	printf("lolita[1] = %p \n",&lolita[1]);
	printf("po+1      = %p\n", (po+1));
	
	printf("\nlolita[0] = %p\nlolita[1] = %p\nlolita[2] = %p\nlolita[3] = %p\nlolita[4] = %p\nlolita[5] = %p\n",&lolita[0],&lolita[1],&lolita[2],&lolita[3],&lolita[4],&lolita[5]);
	printf("%s\n", FRELON);
	return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
lolita    = 0x7ffeed7d89b0
lolita[0] = 0x7ffeed7d89b0
po        = 0x7ffeed7d89b0
*po       = 1

lolita[1] = 2
*(po+1)   = 2
lolita[1] = 0x7ffeed7d89b4 
po+1      = 0x7ffeed7d89b4

lolita[0] = 0x7ffeed7d89b0
lolita[1] = 0x7ffeed7d89b4
lolita[2] = 0x7ffeed7d89b8
lolita[3] = 0x7ffeed7d89bc
lolita[4] = 0x7ffeed7d89c0
lolita[5] = 0x7ffeed7d89c4
Written By Frelon O(∩_∩)O

上面的数据可以看出,同一个数组内的元素在内存中确实是连续排列的,并且一个int类型的元素占用了4B(字节)内存空间,合情合理!

再思考一个问题吼!为什么我们指针加一,却可以指向下一个数组的元素呢?不应该是0x7ffeed7d89c0 + 1 = 0x7ffeed7d89c1这样吗?为什么0x7ffeed7d89c0 + 1 = 0x7ffeed7d89c4?

因为我们之前定义过指针的类型为int,所以指针的加一是根据我们定义的数据类型的长度来加的,而不是直接的数学意义上的在原数基础上相加1,这就是指针的迷人之处,它可以完美的和数组结合起来,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	char *log = "jokemetopsdfsdf";

	int ok = strlen(log);
	for(int er = 0;er < ok ; er++){
		printf("%c", *(log+er));
	}
	printf("\n");
	printf("%s\n", FRELON);
	return 0;
}

可以看到可以用指针来直接定义一个数组,然后用指针加一的方式来访问各个元素。

Tips:数组名是一个地址,而指针是一个左值(不可以被修改)

指针数组和数组指针

int *p[5] = 指针数组

int(*p)[5] = 数组指针

 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
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int qwe [3][3] = {0};
	// int *rt = &qwe
	int k = 0;
	for(int er = 0;er < 3; er++ ){
		for (int i = 0; i < 3; i++)
		{
			qwe[er][i] = ++k;
		}
	}
	printf("qwe               =  %p\n",qwe);
	printf("*qwe              =  %p\n",*qwe );
	printf("**qwe             =  %d\n\n",**qwe );

	printf("qwe + 1           =  %p\n",(qwe+1) );
	printf("*(qwe + 1)        =  %p\n",*(qwe+1) );
	printf("**(qwe + 1)       =  %d\n\n",**(qwe+1) );

	printf("*(qwe + 1)+1      =  %p\n",*(qwe+1)+1 );
	printf("*(*(qwe+1)+1)     =  %d\n",*(*(qwe+1)+1) );
	printf("*((qwe + 1)+1)    =  %p\n",*((qwe+1)+1) );
	printf("**((qwe + 1)+1)   =  %d\n\n",**((qwe+1)+1) );

	printf("%s\n", FRELON);
	return 0;
}
/** ------------------------------------------------- **/
qwe               =  0x7ffee1b399c0
*qwe              =  0x7ffee1b399c0
**qwe             =  1

qwe + 1           =  0x7ffee1b399cc
*(qwe + 1)        =  0x7ffee1b399cc
**(qwe + 1)       =  4

*(qwe + 1)+1      =  0x7ffee1b399d0
*(*(qwe+1)+1)     =  5
*((qwe + 1)+1)    =  0x7ffee1b399d8
**((qwe + 1)+1)   =  7

Written By Frelon O(_)O

注意⚠️区分 *(point+1)+1和*((point+1)+1) = ((point+1)+1) 的区别

Void Point

void point 并不是空指针,它表示还没有确定类型的指针,你可以将任何类型的指针指向void point,看操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int yyu = 4;
	char ui[] = "Winner";
	void *qw;
	qw = &yyu;
	printf("%p \n%p\n",qw,&yyu );
	printf("*wq = %d\n",*(int *)qw );//(int *)表示的是强制类型转换,给void转换成 int
	qw = &ui;
	printf("%p \n%p\n",qw,&ui );
	printf("*qw = %s\n",(char *)qw );//因为字符串比较特殊,所以不用解引用,也可以直接通过指针直接读取,直到读取到‘\0’
	printf("%s\n", FRELON);
	return 0;
}

point to point

P2P:指向指针的指针,其实也非常简单,就是多套了一层娃,要想使用就需要两次解引用,即两个**看操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int ios = 4;
	int *rt = &ios;
	int **goo = &rt;
	printf("rt    = %p\n", rt);
	printf("goo   = %p\n*goo  = %p\n**goo = %d \n", goo,*goo,**goo);
	printf("%s\n", FRELON);
	return 0;
}

指向常量的指针

指向常量的指针:可以修改为指向不同的变量(常量),可以通过解引用来读取指针指向的数据 ,但是不能修改

解读:指向 常量(被const修饰过的基本数据类型)的 指针(指针没有被const修饰,可以修改)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	const int pr = 3;
	int ps = 1024;
	const int *ae = &pr;
	printf("Now ae = %d\n",*ae );
	ae = &ps;
	printf("Now ae = %d\n",*ae );
	printf("%s\n", FRELON);
	return 0;
}

指向非常量的常量指针:上面的指针可以被修改指向,而常量指针就是利用cons使指针不可以被修改指向

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	const int vmware = 999;
	int exsi = 1999;
	int  * const HyperV = &exsi;
	printf("HyperV = %d\n", *HyperV);
	
	*HyperV = vmware;

	printf("HyperV = %d\n", *HyperV);
	printf("%s\n", FRELON);
	return 0;
}

性质: 虽然不可以修改const修饰过的指针指向,但是可以通过解引用来修改指针指向的值

指向常量的常量指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	const int vmware = 999;
	int exsi = 1999;
	const int  * const HyperV = &exsi;
	printf("HyperV = %d\n", *HyperV);
	
	*HyperV = vmware;

	printf("HyperV = %d\n", *HyperV);
	printf("%s\n", FRELON);
	return 0;
}

指向常量的常量指针就是在指针的基本数据类型前面加上const,让指针解引用的方式修改不了数据。

Clang函数

函数的出现其目的就是为了提高代码的复用性,让我们可以用更少的 代码量完成更多的任务。

 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
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main()
{
	int a=12,b=13,c=14,d=13,e=15,f=15,g=17,h=13,i=12,j=10;
	if(a>b){
		printf("%d\n",a);
	}else{
		printf("%d\n",b);
	}

	if(c>d){
		printf("%d\n",c);
	}else{
		printf("%d\n",d);
	}

	if(e>f){
		printf("%d\n",e);
	}else{
		printf("%d\n",f);
	}

	if(g>h){
		printf("%d\n",g);
	}else{
		printf("%d\n",h);
	}

	if(i>j){
		printf("%d\n",i);
	}else{
		printf("%d\n",j);
	}

	printf("%s\n", FRELON);
	return 0;
}

可以看到,为了一件简单的小事,我们如果不用函数需要多次写重复的代码,降低了代码的复用性,也给我们的后期维护带来了很大的难题,当我们用上函数以后,如下:

 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>
#define FRELON "Written By Frelon O(∩_∩)O"

int whoBigger(int a,int b){
	if (a>b)
	{
		printf("%d\n",a);
	}else{
		printf("%d\n",b);
	}
	return 0;
}

int main()
{
	int a=12,b=13,c=14,d=13,e=15,f=15,g=17,h=13,i=12,j=10;
	whoBigger(a,b);
	whoBigger(c,d);
	whoBigger(e,f);
	whoBigger(g,h);
	whoBigger(i,j);
	printf("%s\n", FRELON);
	return 0;
}

可以看得出来,有了函数以后代码量明显的减少了很多

函数传递指针参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

void vbs(int *bbc){
	printf("size of bbc = %lu\n",sizeof(bbc) );
	printf("bbc+2 =%d \n",*(bbc+2) );
	*(bbc+2) = 7; 
	printf("bbc+2 =%d \n",*(bbc+2) );
}

int main()
{
	int we[] = {1,2,3,4,5,6,7,8,9,0,1,2,3,4};
	printf("sizeof we  = %lu\n",sizeof(we) );
	vbs(we);
	
	printf("we[2] = %d\n",we[2]);
	printf("%s\n", FRELON);
	return 0;
}

可以看到与我们别的语言的函数不同,如果函数的参数是指针的话,那么无论在main函数还是自定义函数,修改指针就会直接修改内存中的数据。别的语言例Java可能需要先定义一个全局变量,然后在自定义函数里操作这个全局变量,在我们Clang里,就可以直接利用指针参数来达到自定义函数内部对全局参数的修改。

可变参函数

其实可变参数函数就是利用Clang的一个头文件 #include <stdarg.h> 实现的,其参数包括:va_list,va_start,va_arg,va_end

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

#define FRELON "Written By Frelon O(∩_∩)O"

int sum(int n,...){//...就表示有未知个参数
	int all = 0;
	va_list  vl;//定义参数列表
	va_start(vl , n);//将上面的列表传入va_start,再加上一个参数n

	for (int i = 0; i < n; i++)
	{
		all+=va_arg(vl,int);//va_arg用于获取参数列表里面的各个值(就是我们传入的未知个参数)
	}
	va_end(vl);//然后还要关闭参数列表
	return all;
}

int main()
{
	int bolose = sum(6,12,34,14,456,46,897);
	printf("%d \n",bolose );
	printf("%s\n", FRELON);
	return 0;
}

指针函数

返回指针的函数,本质是函数

返回数据类型 + * + 函数名 + (变量类型1,…);

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

char * cre(char op){
	switch(op){
		case 'a':return "Apple";
		case 'b':return "baidu.com";
		case 'c':return "cdn";
		default:return "google.com";
	}
}
int main()
{
	char io_i;
	printf("Please input word:");
	scanf("%c",&io_i);
	char * poj = cre(io_i);
	printf("%s\n",poj );
	printf("%s\n", FRELON);
	return 0;
}

Tips:不要在函数里面返回局部变量的指针,因为局部函数的数据只在局部函数里面可以使用,不可以在主函数里面使用。

函数指针

顾名思义函数指针就是指向函数的指针,其本质还是一个指针

指针函数:int *p ( );

函数指针:int (*p) ( );

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int squ(int num){
	return num*num;
}

int main()
{
	int num;
	int (*piute)(int);
	printf("Please input a number:");
	scanf("%d",&num);
	piute = squ;//piute = &squ;也是没有问题的
	printf("The result is %d\n",(*piute)(num) );//piute(num)也是OK的
	printf("%s\n", FRELON);
	return 0;
}

函数指针作为参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

int add (int numb1, int numb2){
	return numb1+numb2;
}
int sub (int numb1 , int numb2){
	return numb1-numb2;
}
int comp(int (*weic)(int,int),int numb1,int numb2){//该函数共有三个参数,一个是函数指针(该指针要求需要两个int类型的参数,还需要一个int类型的返回值),其余两个参数是int类型即可。
  
	return (*weic)(numb1,numb2);
}

int main()
{
	printf("%d\n", comp(add,3,5));
	printf("%s\n", FRELON);
	return 0;
}

其实关键点就是comp,它有一个函数指针作为参数,该函数指针指向的函数需要两个参数,这两个参数传给comp,comp在计算时再调用指针函数,将comp的两个int类型参数传给指针函数,指针函数刚刚好接收这两个参数进行运算。其实过程非常简单,只不过是套了两层娃。

下面是一个更加简单的实例,你要是还看不懂,我也没办法🌝。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

char whobig(int abc,int def){
	if (abc>=def)
	{
		return 'Y';
	}else{
		return 'N';
	}
}
char javafirst(char (*yep)(int,int),int aa,int bb){
	return (*yep)(aa,bb);
}

int main()
{
	printf("%c\n",javafirst(whobig,1,13));
	printf("%s\n", FRELON);
	return 0;
}

函数指针作为返回值

 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
#include <stdio.h> 
#define FRELON "Written By Frelon O(∩_∩)O"

char whobig(int abc,int def){
	if (abc>=def)
	{
		printf("I'm whobig\n");
		return 'Y';
	}else{
		printf("I'm whobig\n");
		return 'N';
	}
}

char whosmall(int abc,int def){
	if (abc<def)
	{
		printf("I'm whosmall\n");
		return 'N';
	}else{
		printf("I'm whosmall\n");
		return 'Y';
	}
}

char (* noclang(int ios))(){//总体来看,这是一个指针函数,其返回值为指针类型。其结构为:char * noclang (){}
	if (ios == 0)
	{
		return whobig;	
	}else{
		return whosmall;
	}
}

char javafirst(char (*yep)(int,int),int aa,int bb){
	return (*yep)(aa,bb);
}

int main()
{
	int a = 1;
	int b = 10;
	char (*ok) ();//声明一个函数指针ok。
	ok = noclang(0);//将函数noclang()的地址值赋给ok。
	printf("%d 大于 %d : %c\n",a,b,javafirst(ok,a,b));
	printf("%s\n", FRELON);
	return 0;
}

一句话区分指针函数和函数指针:谁*前面有括号谁就是函数指针

Tips⚠️:

函数指针:重点在指针,表示它是一个指针,它指向的是一个函数。eg: int (*fun)();

指针函数:重点在函数,表示它是一个函数,它的返回值是指针。 eg: int* fun();

数组指针:重点在指针,表示它是一个指针,它指向的是一个数组。int (*fun)[8];

指针数组:重点在数组,表示它是一个数组,它包含的元素是指针 itn* fun[8];

Clang函数作用域&&生存期

Clang存储类型

clang的存储类型有:auto 、register、static、extern

一般情况下我们所有的变量存储类型都为auto,

寄存器变量:register,并不是说定义为寄存器变量就一定会被CPU放入寄存器,而是有可能会被放入寄存器,如果没有被放入寄存器那么这些变量就会自动变为auto变量,如果一个变量被定义为register变量以后,就无法通过取址操作(&)来获取该变量的地址

static:静态变量,其中静态全局变量和静态局部变量的生存期相同,他们都是随着函数执行结束才会被销毁。

extern:extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。

[关于Clang extern的一个困惑](关于Clang extern的一个困惑.md)

Clang递归

话不多说直接上代码:

 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>
#define FRELON "Written By Frelon O(∩_∩)O"

int reuse(int iso){

	printf("%d\n",iso);
	iso+=1;
	
	if(iso == 17){
		printf("GameOver\n");
		return 0;
	}else{
		reuse(iso);

	}
	return iso;
}

int main(){
	int oo = reuse(1);
	printf("%d\n",oo );
	printf("%s\n",FRELON);
	return 0;
}

可以看到这个递归就是在函数的内部不停的继续调用函数本身,这不就是套娃🪆嘛!

但是套娃也需要注意⚠️:因为一定要有停止条件,否则一直在执行就会无休止的浪费内存(ps:并不会无休止的浪费,因为到达一定次数以后就会造成栈内存溢出,代码就会被终止)

Clang hanoi总结

在学习递归的时候接触到了汉诺塔这个小游戏。发现真的是烧脑子,被这个小游戏虐的体无完肤,就像是一年级小朋友做高三题目,根本无处下牙。虽然知道用递归的方法来解决问题,但是就不知道代码该怎么写。看了网上的教程,知道了代码如何写的了,但是还是不明白为什么要这样写,大部分都是只说出了原理,像我这种比较笨的人,听懂了原理依然很蒙。没办法就只能用最原始的办法,用最小的数字慢慢的往上套,来一步一步的理解。

先上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

void hano(int n,char x,char y,char z){
	if(n == 1){
		printf("%c --> %c\n",x,z);
	}else{
		hano(n-1,x,z,y);
		printf("%c --> %c\n",n,x,z);
		hano(n-1,y,x,z);
	}
}

int main (){
	hano(3,'A','B','C');
	return 0;
}

经测试代码没有问题,但是我有问题啊!为什么要

hano(n-1,x,z,y); ​ printf("%c --> %c\n",n,x,z); ​ hano(n-1,y,x,z);

而且为什么是hanoi(n-1,x,z,y)而不是xyz。真的很蒙,然后用笔纸慢慢的计算,发现这样写才是正确🙆‍♂️的做法,因为我也不知道怎么解释,但是就这样是正确的,然后我又发现,hanoi这个函数,在n为奇数或者是偶数的时候选择的路径并不一样,以 n = 3 && n = 4为例

 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
JokerMBP L-🌵-轩-🍂-X ~/Clang_test 
580 ◯ : gcc hello_world.c -o hell && ./hell                                                                     [~]
第一步: A --> C
第2 步: A --> B
第一步: C --> B
​``````````````````
第3 步: A --> C
第一步: B --> A
第2 步: B --> C
第一步: A --> C
​````````````````````````````````````
#上面👆是n=3
JokerMBP L-🌵-轩-🍂-X ~/Clang_test 
580 ◯ : gcc hello_world.c -o hell && ./hell                                                                     [~]
第一步: A --> B
第2 步: A --> C
第一步: B --> C
​``````````````````
第3 步: A --> B
第一步: C --> A
第2 步: C --> B
第一步: A --> B
​````````````````````````````````````
第4 步: A --> C
第一步: B --> C
第2 步: B --> A
第一步: C --> A
​``````````````````
第3 步: B --> C
第一步: A --> B
第2 步: A --> C
第一步: B --> C
​``````````````````````````````````````````````````````
#上面👆是n=4
JokerMBP L-🌵-轩-🍂-X ~/Clang_test 
580 ◯ :                             

当n为奇数时,A –> C

当n为偶数时,A –> B

虽然这是一个小发现,但是对我理解这个函数有很大的帮助,因为我之前一直搞不懂 printf("%c --> %c\n",n,x,z);实质上它的输出是上一步传过来的x,y,z。而并非单纯的为x,y,z本身。可以利用下面的图片理解!

流程图片

Clang内存管理(stdlib.h)

void * malloc(size_t size);申请内存

malloc函数向系统申请一个size大小的内存空间,并返回该块空间的指针(void类型的指针,方便我们转化)。注意,这里申请的内存位于内存的堆里面,如果不手动释放该内存,那么他就会一直被占用,直到程序被关闭。栈内存一般都是存放局部变量的,某个方法(函数)被调用才会进栈空间,调用结束就会自动被清理掉。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>
#include <stdlib.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main (){
	int * p = malloc(sizeof(int));
	*p = 99;
	printf("%d\n",*p);
	free(p);
	printf("%s\n",FRELON);
	return 0;
}

void free (void * p); 释放内存

接收一个指针,指向动态内存的区域。然后释放该内存,必须是由:malloc,calloc,realloc申请的内存,否则无效,还有一点需要注意,就是不能将这些动态申请到的指针重新指向一个内存地址,这样会导致内存地址的指针丢失,也就无法再释放该内存地址。

还要注意内存泄漏,如果没有即时释放掉内存,就有可能导致程序在运行时内存占用越来越多。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#define FRELON "Written By Frelon O(∩_∩)O"


int main (){
	int * p = malloc(8 * sizeof(int));
	for(int er = 1; er <= 8 ;er++ ){
		*p = er;
		printf("%d\n",*p);
		p +=1;
	}
	free(p);
	printf("%s\n",FRELON);
	return 0;
}

malloc申请定长内存,可以用于数组。

calloc

malloc申请的内存并没有初始化,所以申请到的内存都是一些混乱的数据

我们可以使用以下函数来快速初始化:(这些方法依赖,string.h)

memset 使用常量字节填充内存空间
mencpy 拷贝内存空间的数据
memmove 移动内存空间到另一个内存空间
memcmp 比较内存空间
memchr 在内存空间搜索字符

所以为了让在申请内存后自动被初始化,就有了**calloc( )**它和malloc是一样的用法,只是会自动初始化而已。

void * realloc(* point , size );

用于修改malloc|calloc申请内存空间的大小,如果修改后的空间大于原空间,那么一切没问题,但是如果小于原空间,会造成数据丢失。该函数会返回一个新的指针,并不会在原来的基础上进行修改。但是原空间也会被自动释放。还有一点需要注意,注意内存溢出。

Clang宏定义

不带参数的宏定义,一般都是只做替换操作的。并且在定义时也会全部大写。末尾不需要分号;

宏定义的作用域是从定义的那一步到整个程序结束,但是可以随时用 #undef来终止宏定义,宏定义可以套娃。

1
2
3
4
5
6
7
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

int main (){ 
	printf("%s\n",FRELON);
	return 0;
}

这就是一个最简单的宏定义程序;

Clang结构体声明

struct name{

结构体1;

结构体2;

。。。

};

 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
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

struct Web{
	char siteUrl[128];
	char siteName[20];
	int isPub;
	int isFree;		
} web;

void movlop(char * a,char b [],int c){
	for(int i = 0; i<= c;i++){
		*(a+i) = b[i];
	}
}


int main (){

	char url []  = "https://jokeme.top";
	char name [] = "Jokeme Blog";

	movlop(web.siteUrl,url,sizeof(url));
	movlop(web.siteName,name,sizeof(name));

	web.isPub  = 1;
	web.isFree = 1;
	
	printf("网站url:%s\n网站名称:%s\n可否访问:%d\n是否免费:%d\n",web.siteUrl,web.siteName,web.isPub,web.isFree );

	printf("%s\n",FRELON);
	return 0;
}

结构体的初始化非常的简单,可以直接在结构体后面写

还有就是结构体可以嵌套🪆,嵌套后的初始化也简单,看操作:

 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>
#define FRELON "Written By Frelon O(∩_∩)O"

struct Date {
	int year;
	int month;
	int day;
} date;

struct Web {
	char siteUrl[128];
	char siteName[20];
	struct Date date;
	int isPub;
	int isFree;	
} web = {
	"https://jokeme.top",
	"Jokeme Blog",
	{2019,11,23},
	1,
	1
};

int main (){
	printf("网站地址:%s\n网站名称:%s\n成立日期:%d-%d-%d\n可否访问:%d\n是否免费:%d\n",web.siteUrl,web.siteName,web.date.year,web.date.month,web.date.day,web.isPub,web.isFree );
	printf("%s\n",FRELON);
	return 0;
}

结构体数组

声明方式一:在定义期间声明

1
2
3
struct 结构体名 {
	... ...;
} 数组名[长度];

声明方式二:先声明一个结构体,在以此为基础定义一个结构体数组

1
2
3
4
struct 结构体名 {
	... ...;
};
struct 结构体名 数组名[长度];

结构体指针

众所周知,有数组的地方就有指针的存在,结构体指针的定义

1
struct Web * pin;

注意!结构体的变量名并不是指针,所以在对结构体指针操作时需要自己取址。

通过指针来访问结构体成员有两种方法:

1: (*结构体指针).成员名

2: 结构体指针 -> 成员名

可以看得出来,第二种更加简便

 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>
#define FRELON "Written By Frelon O(∩_∩)O"

struct Date {
	int year;
	int month;
	int day;
} date;

struct Web {
	char siteUrl[128];
	char siteName[20];
	struct Date date;
	int isPub;
	int isFree;	
} web = {
	"https://jokeme.top",
	"Jokeme Blog",
	{2019,11,23},
	1,
	1
};

int main (){
	struct Web * pin = &web;
	printf("成立年份:%d\n",pin->date.year);
	printf("%s\n",FRELON);
	return 0;
}

说明两个相同类型的结构体是可以直接进行赋值的

1
2
3
4
5
6
7
8
9
struct Darwin{
	int a;
	int b;
} x , y;

x.a = 1;
x.b = 2;
y = x;
//这种操作是可以的。 

把结构体作为参数传递

 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
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

struct Date {
	int year;
	int month;
	int day;
} date;

struct Web {
	char siteUrl[128];
	char siteName[20];
	struct Date date;
	int isPub;
	int isFree;	
} web = {
	"https://jokeme.top",
	"Jokeme Blog",
	{2019,11,23},
	1,
	1
},wb2;
struct Web ptWeb(struct Web web){
	printf("网站地址:%s\n网站名称:%s\n成立日期:%d-%d-%d\n可否访问:%d\n是否免费:%d\n",web.siteUrl,web.siteName,web.date.year,web.date.month,web.date.day,web.isPub,web.isFree );
	return web;
}
int main (){
	wb2 = ptWeb(web);
	putchar('\n');
	ptWeb(wb2);
	printf("%s\n",FRELON);
	return 0;
}

把结构体指针作为参数传递

 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
#include <stdio.h>
#define FRELON "Written By Frelon O(∩_∩)O"

struct Date {
	int year;
	int month;
	int day;
} date;

struct Web {
	char siteUrl[128];
	char siteName[20];
	struct Date date;
	int isPub;
	int isFree;	
} web = {
	"https://jokeme.top",
	"Jokeme Blog",
	{2019,11,23},
	1,
	1
},wb2;
struct Web ptWeb(struct Web * web){
	printf("网站地址:%s\n网站名称:%s\n成立日期:%d-%d-%d\n可否访问:%d\n是否免费:%d\n",web->siteUrl,web->siteName,web->date.year,web->date.month,web->date.day,web->isPub,web->isFree );
	return *web;
}
int main (){
	wb2 = ptWeb(&web);
	putchar('\n');
	ptWeb(&wb2);
	printf("%s\n",FRELON);
	return 0;
}

可以看到其实把结构体指针作为参数传递其实更能体现clang语言的特性「运行效率高」,因为传递一个结构体占用的内存远远比一个几字节的指针占用的内存多,所以作为程序员我们尽量选择运行效率高的那种方式来实现功能。

动态申请结构体

可以使用malloc为结构体动态申请内存空间,此时结构体就在堆内存中

Ten = malloc(sizeof(struct Web));

Clang链表

链表有很多种,单链表,多链表,循环链表,块状链表

链表结构为:

1
2
graph LR
A[结构体\\指针部分]-->B[结构体\\指针部分]-->C[...]-->D[结构体部分\\指针部分]-->E[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
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
#include <stdio.h>
#include <stdlib.h>
#define FRELON "Written By Frelon O(∩_∩)O"

struct Date {
	int year;
	int month;
	int day;
	struct Date * ndate;
};

void printDate(struct Date * nw){
	printf("      %p\n",nw);
	printf("year:%d\nmonth:%d\nday:%d\nndate:%p\n\n",nw->year,nw->month,nw->day,nw->ndate);
}

void searchDay(struct Date * nd,int target){
	while(nd!=NULL){
		if (nd->month==target)
		{	
			printf("year:%d\nmonth:%d\nday:%d\nndate:%p\n\n",nd->year,nd->month,nd->day,nd->ndate);
			nd=nd->ndate;
		}else{
			nd=nd->ndate;
			printf("Soryy ! Not found\n\n");
		}
	}
}

void setdate(struct Date * dt,struct Date * nt){
	if( (dt->year) == 0){
		dt->year = 2021;
		dt->month = 1;
		dt->day = 4;
		
		nt->year = 2021;
		nt->month = 2;
		nt->day = 8;

	}
	if (dt->ndate == NULL)
	{
		dt->ndate = nt;
	}
}

int main (){
	struct Date * nowDate = malloc(sizeof(struct Date));
	struct Date * nowdat1 = malloc(sizeof(struct Date));
	struct Date * nowdat2 = malloc(sizeof(struct Date));
	struct Date * nowdat3 = malloc(sizeof(struct Date));

	setdate(nowDate,nowdat1);
	setdate(nowdat1,nowdat2);
	setdate(nowdat2,nowdat3);

	// printDate(nowDate);
	// printDate(nowdat1);
	searchDay(nowDate,2);

	printf("%s\n",FRELON);
	return 0;
}

Clang typedef

typedef的作用是给数据类型定义一个别名

Licensed under CC BY-NC-SA 4.0