C语言复习

#####################################################
			C高级
#####################################################
【1】
	宏:__LINE__  __func__  __FILE__   调试代码

		__LINE__  --》行号的宏  
		__func__  --》函数名的宏 
		__FILE__  --》文件名的宏
		
		printf("1111111");
		printf("2222222");
		
		printf("%s-%s-%d",__FILE__,__func__,__LINE__);
		
【2】、条件编译
		//#define XXXX
		#ifdef XXXX   //条件编译
			代码
		#endif

		#ifdef XXXX
			代码1
		#else 
			代码2;
		#endif 
		
		#ifndef XXXX
			printf("hello TI! \n");
		#endif
		#ifndef XXXX
			printf("hello TI! \n");
		#else
			代码2;
		#endif
		
		#if 0     等价于将代码注释掉
			代码
		#endif   
		
		
		#define uint31 unsigned int
		#define PI 3.14
C语言复习-0
	32个关键字集中处理;
	
	1、存储类型
		存储类型:4
		auto 		声明自动变量,缺省时编译器一般默认为 auto
		static 		声明静态变量
		register    声明寄存器变量---皇帝身边的小太监
		存寻址访问以提高效率。注意是 尽可能,不是绝对
		extern   	外部变量声明
		
		面试题:
		static关键字和extern关键字的作用?

	2、数据类型
		数据类型与“模子”  想到数据类型就想到内存
			
		数据类型:7
		void	char 	short 	int 	
		float 	double 	long 	

		void,一个神奇的类型
			如果函数没有返回值,那么应声明为 void 类型,比如打印类型的函数
			void *,强转来进行传参  如果函数的参数可以是任意类型指针,那么应声明其参数为 void *。
			注意:无此说法,单存在,void类型的数据默认占有一个字节空间
			
	3、修饰
		修饰型:4
		signed 	声明有符号类型变量
		unsigned 声明无符号类型变量
		const 声明只读变量
		volatile 说明变量在程序执行中可被隐含地改变

			我们知道计算机底层只认识 0、1.
			任何数据到了底层都会变计算转换成 0、1.那负数(有符号的数)怎么存储呢?
			肯定这个“-”号是无法存入内存的,怎么办?很好办,做个标记。
			把基本数据类型的最高位腾出来,用来存符号,同时约定如下:
			最高位如果是 1,表明这个数是负数,其值为除最高位以外的剩余位的值添上这个“-”号;
			如果最高位是 0,表明这个数是正数,其值为除最高位以外的剩余位的值。
		面试题: 
		const关键的作用?
		volatile关键字的作用?
		
	4、构造数据类型
		构造数据类型: 4
		struct 声明结构体变量
		union 声明联合数据类型
		enum 声明枚举类型
		typedef 用以给数据类型取别名(当然还有其他作用)
		
		[1]数组
			 存储的数据是同一类型
		[2]结构体
			 存储的数据可以是不同类型
		[3]共用体
			 存储的数据可以是不同类型,所有的成员占用是结构体中成员最大的空间

	5、控制语句
		控制语句:11
		if ...else 
		switch ...case default
		do ...while
		for
		break  continue  goto
		goto(goto少用或禁用,内核例外)
		
		if ... else 
			连续型特点:范围型
		switch 中的表达式可以是:
			离散型特点:整型、字符型、或常量表达式或枚举。
			注:一般来说 case语句后面的代码尽量不要超过 20 行。
			注: 按执行频率排列 case 语句
		while  和 do ...while 的差别在于第一次条件是否成立,如果条件不成立,则有区别。
		break 语句的作用是跳出当前循环。
			如有嵌套的循环,则跳出最近的循环体。
		continue:
			continue语句的作用是跳出一次循环,----即忽略continue后面的其它语句,紧接着执行下一次循环。

		for循环设计扩展: 
				在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放
			在最外层,以减少 CPU 跨切循环层的次数。
			
			优选: 							   反例:
			for(i = 0;i < 5; i ++){            for(j = 0;j < 100; j ++) {
				for(j = 0;j < 100; j ++){      	for(i = 0;i < 5; i ++){
					sum +=a[i][j];             		sum +=a[i][j];
				}                              	}
			}                                  }
		
			不能在 for 循环体内修改循环变量,防止循环失控。
			for(i = 0;i < 5; i ++){ 
				sum += i;
				i = 100;
			}

			循环要尽可能的短,要使代码清晰,一目了然。如果你写的一个循环的代码超过一显示屏,那会让读代码的人发狂的。
			解决的办法两个:
				第一,重新设计这个循环,确认是否这些操作都必须放在这个循环里;
				第二,将这些代码改写成一个子函数,循环中只调用这个子函数即可。一般来说循环内的代码不要超过 20行。

			把循环嵌套控制在 3 层以内。
				国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。
				如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个子函数

	6、函数相关
		return 
	7、其他
		sizeof() 计算对象所占内存空间大小

 
 
 
【3】.数组(一维)  ----- 相同的数据类型的连续存储
		问:为什么需要数组?
			答:如果有多个相同类型的数据需要连续存储的时候,我们则使用数组;
					-------------          ---------
						下面                  重点(地址连续)
						
			char   ch1,ch2,ch3;
			short  s1,s2,s3;
			int    a1,a2,a3;
			
			datatype  a[3];
					  a[0],a[1],a[2];
			   ||
			指针 
			struct node 
			union 
			enum 
			
			结构体指针
			函数指针 
	
		1.数组定义
			数据类型  数组名[数组元素的个数];
			char  a[3];
					数组元素的个数: 必须是固定的
			....
		2.数组成员
			例如:
			int a[3];
				成员:
				a[0]  a[1]  a[2]

		问题:如何访问数组成员?
			回答:在访问数组成员的时候,实际上数组的首地址 + 成员的偏移量来得到数组成员的地址
												---------    --?--

		3.数组的首地址 (标识的是一块内存的首地址)
			[1]数组名   a 
			[2]数组的第一个成员地址 &a[0]

			问题:数组名是一个什么东东?  
				回答:数组名它是一个符号(不能修改,也没有内存空间),标识数组的首地址

			注意: 数组名不能修改(不能进行++操作):只是一段内存空间首地址的标识

		4.数组的初始化 (只能在定义的时候进行,否则需要依次赋值)
			-------------------------------------------------------------
			示例:				    ???多少个成员,多少个字节
			-------------------------------------------------------------
			int  a[]   = {1,2};   	 //2个成员,8个字节
			int  a[10] = {1,2};  	 //10个成员,40个字节
			char b[]   = "hello"; 	 //6个成员,6个字节,字符串的结尾是 '\0', b[0]= 'h'   b + 1 ,b[1]的地址 
			char b[10] = {'A','B'}; //字符数组:10个成员,10个字节, b[3] = 0
			char b[2]   ={'a','b'};  //2个成员,2个字节	

			特别注意: 数组的初始化一般在内存分配的时候进行赋值,
				int a[10];
				a[10]  = {1,2,3,4,5};//错误

			1、在定义的时候初始化  int a[10]  = {1,2,3,4,5};
			2、手动初始化---遍历 

		   ?如何给数组成员赋值及如何访问数组成员:
			  1、拿到数组的首地址
			  2、成员对应的数据类型的偏移量

			练习1: 定义一个整形数组,有三个成员,第一个成员赋值为10,第二个赋值为20,第三个赋值为30,输出一下。
 
			练习2:一维数组的输入和输出:
					将输入和输出封装成函数	
			int main(int argc, const char *argv[])
			{
				int a[5];
				input_array(&a[0],5); 
				output_array(a,5);
				return 0;
			}
 

		5.计算数组成员的个数
		
			数组的大小:本质  整个大小 = (单个体积*数量)
			
			整个大小:  sizeof(数组名) : 数组的大小  -----尤其要注意sizeof(array) 这个数组的大小
			单个体积:  sizeof(第一个成员)
			数组个数:  sizeof(数组名) / sizeof(第一个成员) : 数组成员的个数
			  
			  |-----|
			  |     | a[0]
			  |-----| 
			  |     | a[1]
			  |-----| 
			  |     | a[2]
			  |-----|			
			
			定义一个宏:用来表示数组的大小
				#define ARRAY_SIZE(a)  sizeof(a)/sizeof(a[0])
 
		>>>练习: 搞定数组: 
			
			#include <stdio.h>

			int main(int argc, const char *argv[])
			{
				int a[3] = {1,2,3};
				                                     //类型分析  
				printf("a = %p\n",a);          		 //int *
				printf("&a[0] = %p\n",&a[0]); 		 //int *
				printf("&a = %p\n",&a);        		 //int (*)[3]   =--- 数组指针  ===指针指向的是一个有三个int类型的数组

				printf("(int)a + 1 = %p\n",(int)a + 1);
				printf("a + 1 = %p\n",a + 1);          //int *
				printf("&a[0] + 1 = %p\n",&a[0] + 1);  //int *
				printf("&a + 1 = %p\n",&a + 1);        //int (*)[3] 

				return 0;
			}
			
			分析:
				...
				
				linux@ubuntu:~/day5$ ./a.out 
				a = 0xbfe8be54.
				&a[0] = 0xbfe8be54.
				
				&a = 0xbfe8be54.  注:类型是不一样的
				
				(int)a + 1 = 0xbfe8be55.  ()变态强大哥,访问的是地址之后的下一个字节
				a + 1 = 0xbfe8be58.       (a + 1) - a =  0xbfe8be58 -  0xbfe8be54 = 4   偏移的一个数据类型
				&a[0] + 1 = 0xbfe8be58.
				
				&a + 1 = 0xbfe8be60.       0xbfe8be60 -  0xbfe8be54 = 12  = 3 * 4 

		? 如何验证 &a 是 int (*)[3]类型?(数组指针类型)
			int main()
			{
				int a[3];
				printf("&a = %d\n",&a);  //argument 2 has type ‘int (*)[3]’ 
			}

			warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int (*)[3]’ [-Wformat=]
			printf("&a = %d.\n",&a);


【4】.指针(一级)
	
	面试题:谈谈你对指针的理解?
	a = 100;
	int *p = &a;
	
	* : 数据类型--》指针类型
	int :指针指向的是一个int类型的存储空间中
	p :存放的指针指向的内存空间的地址
	*p : 指向的内存空间中的内容
	
	指针类型的变量占4个字节空间(32的操作系统)

		0.导入:
			一级指针 char *p = a;
					
			int a[5] = {1,2,3,4,5};
			int *p = a;

			a + 3; 往后移动3个数据类型;
			问:此时a和p有什么区别
			答:
				[1]a是一个数组名,它标识数组首地址,不可以改变 (a ++ 错误)
				[2]p是一个指针变量,此时p保存的是数组的首地址,在本质上它是一个变量,所以它可以改变(p++ 正确)
				
			总结:
				a++;   x 
				p++;   √
					
			二级指针 char **p;
			
		1.一级指针变量的数据类型
			int   *p;        4    p+1 ;移动一个数据类型,4个字节
			short *q;        4    p+1 ;移动一个数据类型,2个字节
			char  *m;        4    p+1 ;移动一个数据类型,1个字节
			
			深刻理解指针的本质:
				所有指针变量的大小都是一样,但是指针从一块内存中一次性读写的字节数可能不一样--(取决于指针变量指向的数据类型);
				  	---------------------

				结论:32 bit机器指针变量大小就是4byte (因为地址是32bit)

			练习: 
				unsigned int x = 0x12345678;
				求x四个字节的累加和(提示:0x12 + 0x34 + 0x56 + 0x78);
				
				int main(int argc,const char *argv[])
				{
					unsigned int x = 0x12345678;
					int i = 0;
					int sum = 0;
					char *p = &x;
			#ifdef METHOD1		
					for(i = 0;i < 4;i ++){
						sum = sum + (x & 0xff);
						x >> 8;
					}
			#elif  defined METHOD2			
					for(i = 0;i < 4;i ++){
						sum = sum + p[i];
					}
			#else 
					for(p = &x; p < (unsigned char *)(&x + 1); p ++){
						sum += *p;
					}
 			#endif 
				}

				unsigned int x = 0x12345678;
	 
				4个字节:
					12   34    56   78
					
			    每次取一个字节,然后累加....	

				   i,j,k 用于循环 
				   m,n   也阔以用于循环
				   
				   a,b,c 
				   x,y,z   常用于做参数
				   c,z     常用于做返回值
 
 
		2.一级指针变量与一维数组
			0.导学:
				函数传参:                             
				----1、我传递的参数的本质是什么?      
					2、理清楚传递的参数的类型是什么?  
			
			问:为什么需要指针变量?
			答:通过指针变量来存放数据的地址,从而操作地址空间对应的数据,
		减少大规模的数据拷贝过程,提升效率;
			注:指针访问本质上还是访问的数据,但是你要明白我当前的数据是什么。

			学习指针:搞清楚两点:
				1、我当前的内存在哪里,
					全局变量   局部变量   静态变量
				2、我内存中的数据是什么
					char   short  int  还是地址(char* short* int*)  结构体类型 
					 
					 ====>访问:按照对应的类型访问就行了
					
			--------------------------------------------------------
			int a[3];//a[0] a[1] a[2]

			数组名是一个符号常量,标示数组的首地址
			如何访问数组成员?
			数组的首地址 + 成员偏移量
				a[i] <=> *(a + i)

				int a[3];
				int *p = a;
					a[i] <=> p[i] <=> *(a + i) <=> *(p + i)   //xxxx 

			问题:指针变量可以当作数组名来使用吗?
			回答:可以,指针变量需要保存数组的首地址,指针变量它是一个变量,可以改变自身的值,
				 而数组名是一个符号常量,不可以改变它的值

		   练习:
			 数组输入输出 (四种输出方法),输入输出都封装成函数接口,接口如下:
			 a[i] <=> p[i] <=> *(a + i) <=> *(p + i)   //使用这些方式实现如下接口
			 
			int main(int argc, const char *argv[])
			{
				int a[5];

				input_array(a,5);
				output_array(a,5);
				_output_array(a,5);
				__output_array(a,5);
				___output_array(&a,5);
				//my_output_array(&a,5);
				return 0;
			}

		3.一级指针变量与字符串
		  字符串:多个字符的集合,最后一个字符是'\0'(ASCII码:0)

			char *string = "hello word"; //字符串常量区,是只读的
			char buf[] = "hello word";   //有空间 
			char *str;
			char a[10];

			*string = 'x';//错误:字符串常量readonly
			string ++;//正确

			buf[0] = 'x';//正确
			buf ++;//错误:数组名是符号常量,不能修改

			str = "hello linux";//正确:str保存的是字符串首地址,字符串在常量区
			a   = "hello linux";//错误:a 是数组名,数组的初始化只能在定义的时候进行
		
		4、吓死人的传参:
			本质上传递的都是地址啊,大小是4个字节。
		
			问,, sizeof(p);
				  sizeof(*(p+100));

			#include <stdio.h>
			void test(char p[1024])
			{
				printf("sizeof(p) : %d\n",sizeof(p));
				printf("sizeof(*p) : %d\n",sizeof(*p));
			}

			int main(int argc, const char *argv[])
			{
				char buf[1024];
				test(buf);
				return 0;
			}
		
		练习:
		(1)实现strcpy这个函数
			
			char *my_strcpy(char *dest,const char* src)
			{
				char *temp = dest;
				while(*dest++  = *src++)
					;
				//栈内存不可以返回
				return temp;
			}
			
			//如果函数参数只作为输入使用的话,那么为了防止被修改,可以使用const修饰一下。
				const  就近原则 (近:?)
				const  int a = 3;      //这两个是一样的,值不可变
				int const  a = 3;	
				
				const int  *a = 3;    // 值3 不可变,地址a可以变  *a= 是一个值,
				int * const a = 3;    // 地址:a 不可变,
				
				
			//栈内存不可以返回 
			main()
			{
				char *src = "helloworld";
				char dest[1024];
								
				my_strcpy(dest,src,&myargs);
			}

		5.超级烧脑的面试题练习:
			a的首地址是0x20000000
			int a[] = {1,2,3,4,5};
			
			int *ptr1 = (int *)((int)a + 1); //本质:指向了下一个字节
			int *ptr2 = (int *)(&a + 1);     //&a,移动一个类型,整个数组的类型		
		  
		  (1):	
			int(a) 
			
			printf("%#x\n",*ptr1);   //0x20000000
			printf("%#x\n",ptr2[-1]);  //5 

		  (2):
			unsigned int a[] = {0x12345678,0x12345678,3,4,5};
			printf("%#x\n",*ptr1);    //   
			printf("%#x\n",ptr2[-1]); //
		
		
			
		小端存储:  低字节数据存放在低地址,高字节存放在高地址   === x86  ARM(默认) MIPS 
			unsigned int x = 0x12345678;   数据为从高到低 

			低地址	0x3000-->---------->-------|>---------高地址-----
						|  78  | 56 | 34 | 12 |  bf | a1 |...|   |
					0x3000--->---------->------|----->----高地址----- 
						 0x3000           0x3003
										  
		大端存储:低字节存放在高地址,高字节存放在低地址     === POWERPC  网络
			低地址	0x3000-->---------->-------|>---------高地址----- 
							|  12 | 34 | 56 | 78 |  bf | a1 |...|   |
					0x3000--->---------->------|----->----高地址------ 
										   0x3003
										   
			 练习1:
				0、验证当前系统的存储方式: 

			unsigned int x = 0x12345678; 
			
			unsigned char *p = &x;
			
			if(*p == 0x78){
				printf("little endpoint");
			}else{
				printf("big endpoint.\n");
			}
			

			
		求解过程分析:
		*********************
		注意区分大小端的存储:
		*********************

		内存中的存储: 
  
  
  

【5】.二级指针与二维数组
	
	二维数组:多个一维数组组成

	int a[3][2];
	三个一维数组,每个一维数组两个元素

	a[0] : 一维数组的名字
	a[0][0]  a[0][1]

	a[1]  
	a[1][0]  a[1]a[1]

	a[2]
	a[2][0]  a[2][1]

	----------------------------------------------------------------------------------
	a   : 二维数组的数组名,它是符号常量,标示二维数组的首地址, 它是一个数组指针类型
	a[0]: 一维数组的数组名,它是符号常量,表示一维数组的首地址,它是普通指针类型
	&a[0][0]:获取第一个元素的地址,它是一个普通指针类型
	--------------------------------------------------------------------------------
	
	测试:
		#include <stdio.h>

		int main(int argc, const char *argv[])
		{
			int a[3][2];

			printf("a = %p\n",a);
			printf("a[0] = %p\n",a[0]);
			printf("&a[0][0] = %p\n",&a[0][0]);

			printf("-------------------------\n");

			printf("a + 1 = %p\n",a + 1);      				//移动了一行          ====>一行,2(列) * 4(元素的数据类型int) = 8
			printf("a[0] + 1= %p\n",a[0] + 1); 				//移动了一列的一个元素====>一个元素,4个字节
			printf("&a[0][0] + 1 = %p\n",&a[0][0] + 1); 	//移动了一个 		  ====>一个元素,4个字节

			return 0;
		}

	
		分析:
		linux@ubuntu:~/day5$ gcc 13_array2.c
		linux@ubuntu:~/day5$ ./a.out 
		a = 0xbf8aaec8.
		a[0] = 0xbf8aaec8.
		&a[0][0] = 0xbf8aaec8.
		a + 1 = 0xbf8aaed0.
		a[0] + 1= 0xbf8aaecc.
		&a[0][0] + 1= 0xbf8aaecc.
                                                                                      
		a + 1 - a =  0xbf8aaed0 -  0xbf8aaec8 = 8  //一行                             |
		a[0] + 1 - a[0]  =  0xbf8aaecc - 0xbf8aaec8  = 4  //一列的元素                | ===>类型的大小
		&a[0][0] + 1 - &a[0][0]  = 	0xbf8aaecc -  0xbf8aaec8 = 4  //一行的一个元素    |
	

	int a[3][2];  //二维数组 
	int (*p)[2];  //数组指针

	p = a;
	a[i][j] <=> *(a[i] + j) <=> *(*(a + i) + j) <=>p[i][j] <=> *(p[i] + j) <=> *(*(p + i) + j) 

	----------------------------------------------------------------------------------------------
	练习:
	int a[2][4] = {1,2,3,4,5,6,7,8};
	//输出这个二维数组,用至少三种方式。
	
	
	输入:
	int input_array(int (*p)[4],int n)  //这个n常表示行数
	{
		int i = 0,j = 0;
		
		for(i = 0;i <n;i ++){
			for(j = 0;j <4;j ++){
				scanf("%d",&p[i][j]);
			}
		}
	}
 
	练习2:二维数组的线性输出测试:
	

【6】 指针数组 
	本质:就是一个数组,数组中存放的是地址值,数组成员等价于多个指针变量
	int a;    //一个整形变量
	int a[3]; //一个有三个元素的整形数组
	int *p[3];//本质上是数组,数组的成员是指向整形的指针:成员:p[0] p[1] p[2]

	p     : 数组名,标示是指针数组的首地址,它是int **
	p[0]  : 数组的成员,它是int *
	&p[0] : 获取第一个元素的首地址,它是int ** 
		
	问:什么时候需要定义指针数组?
		答:需要连续存放多个地址的时候 ----连续访问多个连续地址。
	
	
	练习:  定义一个指针数组,包括三个成员,成员的内容是"hello","world","linux"

#if 0	
	int main(int argc, const char *argv[])
	{
		char *p_array[] = {"hello","word","linux",NULL};	
		show_point_array(p_array);
		return 0;
	}
#endif 

	1、实现show_point_array,依次将"hello","world","linux"换行输出
	2、实现show_string_char,依次输出"hello"的每一个字母,”world","linux"也是。


扩展:
	多级指针:
	----------------------------------------------------------------------------
	多级指针变量:
	<1>一级指针变量保存普通变量的地址
	<2>二级指针变量保存一级指针变量地址
	<3>三级指针变量保存二级指针变量地址
	------------------------------------------------------------------	


	扩展:---------------------------------------------------------------------------
		字符串和字符

		[1]字符
		   '字符'  在内存中存放数据 (ASCII码 [0-127])
			'\0'   ----   0
			'0'    ----  48
			'A'    ----  65
			'a'    ----  97
			'\n'   ----  10
			字符在内存中还是以字符的形式表示。

		[2]字符串
			本质: 
				"多个字符"  && 特点:一定是有一个结束的字符 '\0'
			-----------------------------
				字符串以‘\0’结束,以‘\0结束’,以‘\0结束’....
			------------------------
			
		练习1:输出ascii码0-9,输出a~f的ascii码
 
 
	扩展:
		1、空指针
		
		如果一个指针不指向任何数据,我们就称之为空指针,用NULL表示
			int *p = NULL;
		NULL的本质: 
			#define NULL  ((void *)0) (位于stdio.h)
			对空指针可读不可写。
			if(p == NULL){
				
			}
		
		2、野指针 
		(1)指针变量没有被初始化。指针变量在创建的同时应当被初始化,
		 要么将指针设置为NULL。
		 例如: 
			----确保指针指向有效的地址
				char *p = NULL;
			    char *str = (char *) malloc(100);
		(2)指针p被free之后,没有置为NULL,系统认为这块内存还能被访问。
			char *ptr = (char *)malloc(100);  //分配内存
			......
			free(ptr);  //释放
			ptr = NULL;  //指针置空
		(3)指针操作超越了变量的作用范围。
			char *str = (char *) malloc(5);
			strcpy(str, "linux");
			
		野指针的影响:会导致内存泄漏。

 
【7】.函数   	
	设计/封装规则:
		一个函数是完成一个特定功能的代码 , 
		调用这个函数的时候,可以给函数传入参数,
		同时可以通过函数传出参数,还可以通过函数返回结果。
			
	1.函数的定义(或实现)
		数据类型	函数名(数据类型  变量名,....)
		{
			C语言语句;
		}	
		-----------------------------------------------
		注意: 
			0、一个函数尽可能只实现一个功能;
			1、最好通过函数名就可以知道函数的功能,参数同理(通过参数名知道参数是做什么的)
			2、尽量避免在参数中使用全局变量,静态变量static等
			3、可以给函数传入参数和或借助参数传出结果,且参数个数尽量控制在4个以内
			4、如果函数没有返回值,那么应声明为void类型,有则必须按类型返回(必要时可以强转);
			5、函数的代码尽可能小于100行(保证代码的可读性)
			6、不要返回指向“栈内存”的指针,栈内存在函数体结束时被自动释放
		------------------------------------------------

	2.函数的声明
		***声明是为了告诉编译器(我在外面)
		extern 数据类型	函数名(数据类型  变量名,....);

		声明是函数的造型[类型](函数类型:函数的返回值类型和函数的参数类型)

	3.函数调用
	   函数名(参数数据或变量名);
	   不关心函数返回值
	   
	4.返回值 
	   变量 = 函数名(参数数据或变量名);
	   需要获取函数调用结束的时候返回的值
	   
	   注意:
			调用一个函数之前,必须有函数的声明或是已经定义过

	 ---------------------------------------------------------		
		1、设计一个函数,将一个unsigned int类型数据传递给这个函数,这个函数需要将这个unsigned int类型
	的每个字节做累加,然后返回累加的结果.
		位操作和指针操作实现;
		位操作提示:sum = sum + (x & 0xff);


	练习2: 设计一个函数,实现字符串逆置:
		//字符串逆置用数组和指针两种方式实现
	作业:
		作业:
		<1>设计一个函数 find_max_min_chr(p_arry,&max,&min); 寻找最大和最小的字符
		
		char *p_arry[] = {"hello","world","linux",NULL};

	作业:
		练习(可选---全部实现):
				<1>写一个函数;strchr()用来找出参数s字符串中第一个出现的参数c地址, 	
				<1>设计一个函数,计算字符串长度
				<2>设计一个函数,完成字符串拷贝和字符串前n个字符拷贝(清空目标区)
				<2>设计一个函数,完成字符串比较和字符串前n个字符比较
				<2>设计一个函数,完成字符串追加和字符串前n个字符追加


		
		
---------------------------------------------------------------------------------	

	
【7】 函数指针   (重要:linux驱动开发)

		类型 + 变量名或数组名

		int a;                   
		int a[3];
		int *a[3];                
		int (*)[3]            //数组指针类型
		int myadd(int ,int)         int (*) (int ,int )   //函数指针类型

		二 函数指针类型变量

		问:什么时候需要用到函数指针变量?
			答:用来存放函数入口地址(函数名即函数入口地址),便于通过函数指针调用这个函数(回调)

		问:为什么不直接调用函数,而需要通过函数指针来调用?
			答:在一个软件设计中,为了方便别人使用我们的代码;
			在我们给别人提供接口的时候,并不清楚别人具体的函数名是什么的,
		那为了避免使用者在提供函数接口时候的随意性,我们通过函数指针类型来限定使用者的函数类型。
			 
		1.定义
		根据函数的类型来定义

		int  add(int a,int b);
		void test1(char *,int a);
		void test2(void);

		int  (*p_fun)(int ,int);
		void (*p_fun)(char *,int);
		void (*p_fun)(void);

		扩展:
		  .快速完成函数指针的定义: 
			以mystrcpy函数和add函数为例:
			char *mystrcpy(char* dest,const char* src)
			int add(int a,int b)

			a、这是两个函数,那么函数的类型是什么呢? 
				答:去掉函数名,去掉参数名,就得到了函数类型

				char *  (char* ,const char* )
				int (int ,int )

			b、定义函数指针  
				答:指针替换掉原来的函数名 :
				等价于: *p=mystrcpy   *q = add

			c、得到函数指针
				char * (*p)(char* ,const char* )
				int (*q)(int ,int )
				
			d、调用
				p=mystrcpy
					p(dest,src);
				q = add
					q(a,b);
			
		练习1: 使用函数指针调用mystrcpy和add函数,并输出结果

		练习2: 函数指针的高级使用
		
		
#if 0
		#include <stdio.h>

		int add(int a,int b)
		{
			return (a + b);
		}

		int sub(int a,int b)
		{
			return (a  - b);
		}

		int calc(int a,int b,int (*pfun)(int ,int ))
		{
			int ret;
			ret = pfun(a,b);
			return ret;
		}

		int main(int argc, const char *argv[])
		{
			printf("10 + 20 = %d\n",calc(10,20,add));
			printf("10 - 20 = %d\n",calc(10,20,sub));
			return 0;
		}
		
#endif 


	
	扩展学习:
		函数指针数组

		int main(int argc, const char *argv[])
		{
			int (*p[2])(int ,int);

			printf("10 + 20 = %d\n",calc(10,20,add));
			printf("10 - 20 = %d\n",calc(10,20,sub));

			p[0] = add;
			p[1] = sub;

			printf("10 + 20 = %d\n",p[0](10,20));
			printf("10 - 20 = %d\n",p[1](10,20));
			
			return 0;
		}

		
	扩展:实现:

		练习3: 遍历函数指针数组,依次完成加减乘除的操作。
		提示:
			void calc_function_pointer(int a,int b,int (**q)(int ,int))
			{
				for( ; *q != NULL;*q++)
				{
					printf("%d\n",(*q)(a,b));
				}
			}
		
			//定义函数指针数组,实现一个计算器
			int (*q[])(int ,int) = {add,sub,mul,div,NULL};

		调用:
			calc_function_pointer(10,20,q);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值