C基础知识总结(一)

1.C语言编译过程

C代码编译成可执行程序经过4步:
1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法
	gcc语法:gcc -E hello.c -o hello.i
2)编译:检查语法,将预处理后文件编译生成汇编文件
 	gcc语法:gcc -S hello.i -o hello.s
3)汇编:将汇编文件生成目标文件(二进制文件)
	gcc语法:gcc -c hello.s -o hello.o
4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去
	gcc语法:gcc   hello.o -o hello_elf

2.数据类型

1)数据类型
	整型:int、short、long
	字符型:char
	实型(浮点型):单精度实型 float和双精度实型 double
	1)整型(int):
		打印格式			含义
		%d			输出一个有符号的10进制int类型
		%o(字母o)	输出8进制的int类型
		%x			输出16进制的int类型,字母以小写输出
		%X			输出16进制的int类型,字母以大写写输出
		%u			输出一个10进制的无符号数
		
	2) short、int、long、long long
	数据类型				占用空间
	short(短整型)		2字节
	int(整型)			4字节
	long(长整形)			Windows为4字节,Linux为4字节(32位),8字节(64位)
	long long(长长整形)	8字节
	注意:
	需要注意的是,整型数据在内存中占的字节数与所选择的操作系统有关。虽然 C 语言标准中没有明确规定整型数据的长度,但 long 类型整数的长度不能短于 int 类型, short 类型整数的长度不能短于 int 类型。
	当一个小的数据类型赋值给一个大的数据类型,不会出错,因为编译器会自动转化。但当一个大的类型赋值给一个小的数据类型,那么就可能丢失高位。

	3)字符型:char
	字符型变量用于存储一个单一字符,在 C 语言中用 char 表示,其中每个字符变量都会占用 1 个字节。在给字符型变量赋值时,需要用一对英文半角格式的单引号(' ')把字符括起来。
	字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的 ASCII 编码放到变量的存储单元中。char的本质就是一个1字节大小的整型。
	
	4)实型(浮点型):float、double
	实型变量也可以称为浮点型变量,浮点型变量是用来存储小数数值的。在C语言中, 浮点型变量分为两种: 单精度浮点数(float)、 双精度浮点数(double), 但是double型变量所表示的浮点数比 float 型变量更精确。
	数据类型		占用空间		有效数字范围
	float		4字节		7位有效数字
	double		8字节		15~16位有效数字
	由于浮点型变量是由有限的存储单元组成的,因此只能提供有限的有效数字。在有效位以外的数字将被舍去,这样可能会产生一些误差。
	不以f结尾的常量是double类型,以f结尾的常量(如3.14f)是float类型。

	5)类型限定符
	限定符		含义
	extern		声明一个变量,extern声明的变量没有建立存储空间。
				extern int a;
	const		定义一个常量,常量的值不能修改。
				const int a = 10;
	volatile	防止编译器优化代码
	register	定义寄存器变量,提高效率。register是建议型的指令,而不是命令型的指令,如果CPU有空闲寄存器,那么register就生效,如果没有空闲寄存器,那么register无效。

	6) 类型转换
	数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题。
	转换的方法有两种:
	① 自动转换(隐式转换):遵循一定的规则,由编译系统自动完成。
	② 强制类型转换:把表达式的运算结果强制转换成所需的数据类型。
	类型转换的原则:占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类
	型转换,以保证精度不降低。
	
	7)案例:水仙花数
#include<stdio.h>

int main()
{
	int a = 100;
	//while循环实现
	//while (a < 1000)
	//{
	//	int b = a / 100;//百位
	//	int c = (a / 10) % 10;//十位
	//	int d = (a % 100) % 10;//个位
	//	if (b * b * b + c * c * c + d * d * d == a)
	//	{
	//		printf("%d ", a);
	//	}
	//	++a;
	//}
	//for循环实现
	for (; a < 1000; a++)
	{
		int b = a / 100;//百位
		int c = (a / 10) % 10;//十位
		int d = (a % 100) % 10;//个位
		if (b * b * b + c * c * c + d * d * d == a)
		{
			printf("%d ", a);
		}
	}
}

3.程序流程结构

1)程序运行结构共有三种:
	顺序结构:程序按顺序执行,不发生跳转
	选择结构:程序通过不同的选择执行相应的操作
	循环结构:条件满足后循环执行代码
	
2)选择结构
	1)if...else...语句
	2)switch语句:根据switch后选择的不同执行不同的语句以及不同的操作
	3) 三目运算符:A>B?A:B 先进行条件判断再进行输出
	
3)循环结构
	1)while语句:满足条件循环
	2)do...while语句:先执行一次,再判断条件
	3)for语句

4)跳转语句
	1) break语句
	在switch条件语句和循环语句中都可以使用break语句:
	当它出现在switch条件语句中时,作用是终止某个case并跳出switch结构。
	当它出现在循环语句中,作用是跳出当前内循环语句,执行后面的代码。
	当它出现在嵌套循环语句中,跳出最近的内循环语句,执行后面的代码。
	2) continue语句
	在循环语句中,如果希望立即终止本次循环,并执行下一次循环,此时就需要使用continue语句。
	3) goto语句
	无条件跳转,需要一个标签进行跳转

5)产生随机数的步骤
	1)添加time.h头文件
	2)添加随机数种子:srand((unsigned int)time(NULL));

4.数组

1)数组
  在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。
  数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型
,同时所有的成员在内存中的地址是连续的。
  通常情况下,数组元素下标的个数也称为维数,根据维数的不同,可将数组分为一维数组、
二维数组、三维数组、四维数组等。通常情况下,我们将二维及以上的数组称为多维数组。

2)一维数组的定义,使用和初始化
 数组名字符合标识符的书写规定(数字、英文字母、下划线)
 数组名不能与其它变量名相同,同一作用域内是唯一的
 方括号[]中常量表达式表示数组元素的个数
 在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为零。局部数组若不初始化,内容为随机值。
 数组名是一个地址的常量,代表数组中首元素的地址。
3)一维数组案例:数组逆置和冒泡排序
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	//int arr[5];//定义数组
	//int temp = 0;//临时变量,用于存储运算中产生的数
	//int len = sizeof(arr) / sizeof(arr[0]);//计算个数
	//for (int i = 0; i < len; i++)
	//{
	//	scanf("%d", &arr[i]);
	//}
	//for (int j = 0; j < len / 2; j++)
	//{
	//	temp = arr[j];
	//	arr[j] = arr[len - 1 - j];
	//	arr[len - 1 - j] = temp;
	//}
	//for (int k = 0; k < len; k++)//打印操作
	//{
	//	printf("%d  ",arr[k]);
	//}

	int arr[5];
	int temp = 0;
	int len = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < len; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (int i = 0; i < len; i++)//外层循环控制行
	{
		for (int j = 0; j < len - 1 - i; j++)//内层循环控制列
		{
			if (arr[j] > arr[j + 1])//控制是降序还是升序
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
	for (int k = 0; k < len; k++)//打印操作
	{
		printf("%d  ",arr[k]);
	}
	return 0;
}

4)一维数组案例:双色球
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int main()
{
	srand((unsigned int)time(NULL));//添加随机数种子
	int arr_red[6] = {0};//红球数组
	int bool_blue = 0;//篮球数组
	for (int i = 0; i < 6; i++)//加红球
	{
		for (int j = 0; j < i; j++)
		{
			if ((rand() % 33 + 1) != arr_red[j])
			{
				arr_red[i] = rand() % 33 + 1;			
			}
		}
	}
	for (int i = 0; i < 6; i++)//排序
	{
		for (int j = 0; j < 5 - i; j++)
		{
			if (arr_red[j] > arr_red[j + 1])
			{
				int temp = arr_red[j];
				arr_red[j] = arr_red[j + 1];
				arr_red[j + 1] = temp;
			}
		}
	}
	bool_blue = rand() % 16 + 1;//加篮球

	for (int i = 0; i < 6; i++)//打印
	{
		printf("%d ", arr_red[i]);
	}
	printf("\t%d", bool_blue);
	return 0;
}
4)二维数组的定义和使用
二维数组定义的一般形式是:
	类型说明符 数组名[常量表达式1][常量表达式2]
	其中常量表达式1表示第一维下标的长度,常量表达式2 表示第二维下标的长度。	
int a[3][4]:定义了一个三行四列的数组,数组名为a其元素类型为整型,该数组的元素个
数为3×4个。
	二维数组在概念上是二维的:其下标在两个方向上变化,对其访问一般需要两个下标。
	在内存中并并存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。
5)二维数组案例:存储学生多门成绩
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	int arr[3][3] = { 0 };//定义存储学生成绩的数组
	int stu_arrlen = sizeof(arr)/sizeof(arr[0]);//定义数组的行数
	int list_arrlen = sizeof(arr[0]) / sizeof(arr[0][0]);//定义数组的列数
	for (int i = 0; i < stu_arrlen; i++)
	{
		for (int j = 0; j < list_arrlen; j++)
		{
			scanf("%d", &arr[i][j]);//输入学生成绩
		}
		//printf("\n");
	}

	for (int i = 0; i < stu_arrlen; i++)
	{
		printf("第%d个学生的语文,数学,英语成绩为: ",i);
		for (int j = 0; j < list_arrlen; j++)
		{
			printf("\t%d ", arr[i][j]);//输出学生成绩
		}
		printf("\n");
	}

	return 0;
}
6)多维数组
	多维数组的定义与二维数组类似,其语法格式具体如下:
	数组类型修饰符 数组名 [n1][n2]…[nn];
	定义一个多维数组,定义的类型以及初始化的类型都和二维数组一样。

5.字符串

1)字符数组与字符串区别
C语言中没有字符串这种数据类型,可以通过char的数组来替代;
字符串一定是一个char的数组,但char的数组未必是字符串;
数字0(和字符‘\0’等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char的数组。
2)字符串案例:字符串追加
#include<stdio.h>

int main()
{
	char arr1[] = "abcdefg";
	char arr2[] = "123456789";
	char arr3[100] = {0};
	int len1 = sizeof(arr1) / sizeof(arr1[0]);
	int len2 = sizeof(arr2) / sizeof(arr2[0]);
	int i = 0;
	for (; i < len1; i++)
	{
		if (arr1[i] != 0)
		{
			arr3[i] = arr1[i];
		}
	}

	for (int j = 0; j < len2; j++)
	{
		if (arr2[j] != 0)
		{
			arr3[i+j-1 ] = arr2[j];
		}
	}
	//for (int k = 0; k <= len1+len2; k++)
	//{
	//	printf("%c", arr3[k]);
	//}
	printf("%s", arr3);
	return 0;
}
3)字符串的内置函数
	1) gets()
	#include <stdio.h>
	char *gets(char *s);
	功能:从标准输入读入字符,并保存到s指定的内存空间,直到出现换行符或读到文件结尾为止。
	参数:
		s:字符串首地址
	返回值:
		成功:读入的字符串
		失败:NULL

	**
	gets(str)与scanf(“%s”,str)的区别:
			gets(str)允许输入的字符串含有空格
			scanf(“%s”,str)不允许含有空格
			由于scanf()和gets()无法知道字符串s大小,必须遇到换行符或读到文件结尾
			为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。
	注:在定义存储字符的内存空间的时候,需要定义的是一个字符数组。
	**
		
	2) fgets()
	#include <stdio.h>
	char *fgets(char *s, int size, FILE *stream);
	功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
	参数:
		s:字符串
		size:指定最大读取字符串的长度(size - 1)
		stream:文件指针,如果读键盘输入的字符串,固定写为stdin,当定义为文件指针
				的时候,意为用一个指针指向文件,定义方式为FILE *fp;
	返回值:
		成功:成功读取的字符串
		读到文件尾或出错: NULL
	
		fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字
	符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets
	结尾多了“\n”。fgets()函数是安全的,不存在缓冲区溢出的问题。

	3) puts()
	#include <stdio.h>
	int puts(const char *s);
	功能:标准设备输出s字符串,在输出完成后自动输出一个'\n'。
	参数:
		s:字符串首地址
	返回值:
		成功:非负数
		失败:-1
	注:在定义存储输入的字符的空间的时候,最好提前对数组进行0初始化,这样数组在使用gets函数接收输入的时候,就会自动给尾部添加一个'\0'。
	
	4) fputs()
	#include <stdio.h>
	int fputs(const char * str, FILE * stream);
	功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0'  不写入文件。 
	参数:
		str:字符串
		stream:文件指针,如果把字符串输出到屏幕,固定写为stdout,因为输出为字符串,因此遇到‘\0’时,结束读取
	返回值:
		成功:0
		失败:-1
	
	fputs()是puts()的文件操作版本,但fputs()不会自动输出一个'\n'。
#include<stdio.h>
#include<string.h>

int main()
{
	//1.gets和puts
	//char c1[100] = {0};
	//gets(c1);
	//puts(c1);

	//2.fgets和fputs
	char ch[10] = { 0 };
	fgets(ch, 10, stdin);
	//当第三个参数为stdin时,意思为从标准输入中读取字符;
	//当第三个参数为文件指针的时候,意思为用一个指针指向文件,定义方式为FILE *fp;
	//for (int i = 0; i < sizeof(ch) / sizeof(ch[0]); i++)
	//{

	//	printf("%c\n", ch[i]);
	//}
	fputs(ch,stdout);
	return 0;
}
	5) strlen()
	#include <string.h>
	size_t strlen(const char *s);
	功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’,也就是说,使用此函数的作用为
		 返回字符串‘\0’之前的字符长度。
	参数:
		s:字符串首地址
	返回值:
		字符串s的长度,size_t为unsigned int类型

	6) strcpy()
	#include <string.h>
	char *strcpy(char *dest, const char *src);
	功能:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
	参数:
		dest:目的字符串首地址
		src:源字符首地址
	返回值:
		成功:返回dest字符串的首地址
		失败:NULL
	
	注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
			在使用VS2019进行编译的时候,会出现报错信息C4996	'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.,
			出现此问题是因为VS认为strcpy是一种不安全的写法,有两种解决办法:
				1.#pragma warning(disable:4996),添加头文件
				2.使用strcpy_s函数来进行字符串拷贝,其中strcpy_s函数为:
					格式:
					char *strcpy(char *dest, rsize_t SizeInByte,const char *src)
					参数:
					 typedef size_t rsize_t;等价于size_t的一个数据类型,意为所拷贝字符串长度
					 typedef unsigned int size_t;暂时可以理解为三者为等价的三种数据类型

	7) strncpy()
	#include <string.h>
	char *strncpy(char *dest, const char *src, size_t n);
	功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。
	参数:
		dest:目的字符串首地址
		src:源字符首地址
		n:指定需要拷贝字符串个数
	返回值:
		成功:返回dest字符串的首地址
		失败:NULL

	注:在VS中,所需的注意事项与上述相同,因此选择一样的处理办法,后面同理,因本文只
	是熟悉内置函数的用法,因此采用#pragma warning(disable:4996)的解决办法。
#define _CRT_SECURE_NO_WARNNINGS
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>

int main()
{
	//4.strcpy和strncpy
	char ch1[100];
	char ch2[] = "helloworld";

	strcpy(ch1, ch2);
	printf("%s\n", ch1);

	char ch3[100];
	char ch4[] = "helloworld";
	printf("%s\n", ch3);
	return 0;
}
	8) strcat()
	#include <string.h>
	char *strcat(char *dest, const char *src);
	功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
	参数:
		dest:目的字符串首地址
		src:源字符首地址
	返回值:
		成功:返回dest字符串的首地址
		失败:NULL
		
	9) strncat()
	#include <string.h>
	char *strncat(char *dest, const char *src, size_t n);
	功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
	参数:
		dest:目的字符串首地址
		src:源字符首地址
		n:指定需要追加字符串个数
	返回值:
		成功:返回dest字符串的首地址
		失败:NULL
#define _CRT_SECURE_NO_WARNNINGS
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>

int main()
{
	//5.strcat和strncat
	char ch1[100] = "helloworld";
	char ch2[] = "cpp,c,python";
	strcat(ch1,ch2);
	printf("%s\n", ch1);

	char ch3[100] = "helloworld";
	char ch4[] = "cpp,c,python";
	strncat(ch3, ch4, 5);
	printf("%s\n", ch3);
	return 0;
}
	10) strcmp()
	#include <string.h>
	int strcmp(const char *s1, const char *s2);
	功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
	参数:
		s1:字符串1首地址
		s2:字符串2首地址
	返回值:
		相等:0
		大于:>0
		小于:<0

	11) strncmp()
	#include <string.h>
	int strncmp(const char *s1, const char *s2, size_t n);
	功能:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小。
	参数:
		s1:字符串1首地址
		s2:字符串2首地址
		n:指定比较字符串的数量
	返回值:
		相等:0
		大于: > 0
		小于: < 0
#define _CRT_SECURE_NO_WARNNINGS
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>

int main()
{
	//5.strcmp和strncmp
	char ch1[] = "helloworld";
	char ch2[] = "hellowarld";
	printf("%d\n", strcmp(ch1, ch2));

	char ch3[] = "helloworld";
	char ch4[] = "hellzworld";
	printf("%d\n", strncmp(ch3, ch4, 5));
	return 0;
}
	12) sprintf()
	#include <stdio.h>
	int sprintf(char *BUFFER, const char *format, ...);
	功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0'  为止。sprintf的作用是将一个格式化的字符串输出到一个目的字符串中,
	sprintf的第一个参数应该是目的字符串,使用前应该给目的字符串分配足够大的空间。
	参数:
		str:字符串首地址
		format:字符串格式,用法和printf()一样
	返回值:
		成功:实际格式化的字符个数
		失败: - 1

	13) sscanf()
	#include <stdio.h>
	int sscanf(const char *str, const char *format, ...);
	功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。就是说与上述的sprint作用相反,即从目标字符串中读取信息格式化后到指定字符串中。
	参数:
		str:指定的字符串首地址
		format:字符串格式,用法和scanf()一样
	返回值:
		成功:参数数目,成功转换的值的个数
		失败: - 1
#define _CRT_SECURE_NO_WARNNINGS
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>

int main()
{
	//6.sprintf和scanf
	//char ch1[100];
	//char ch2[] = "helloworld";
	//int m_num = 666;
	//
	//int len = sprintf(ch1,"%s\n%d\n",ch2,m_num);
	//printf("%s\n", ch1);
	//printf("%d\n", len);

	char src[] = "a=10, b=20";
	int a;
	int b;
	int len1 = sscanf(src, "a=%d,  b=%d", &a, &b);
	printf("%d\n", len1);
	printf("a:%d, b:%d\n", a, b);


	return 0;
}
	14) strchr()
	#include <string.h>
	char *strchr(const char *s, int c);
	功能:在字符串s中查找字母c出现的位置
	参数:
		s:字符串首地址
		c:匹配字母(字符)
	返回值:
		成功:返回第一次出现的c地址
		失败:NULL

	15) strstr()
	#include <string.h>
	char *strstr(const char *haystack, const char *needle);
	功能:在字符串haystack中查找字符串needle出现的位置
	参数:
		haystack:源字符串首地址
		needle:匹配字符串首地址
	返回值:
		成功:返回第一次出现的needle地址
		失败:NULL
#define _CRT_SECURE_NO_WARNNINGS
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int main()
{
	//7.strchr和strstr
	char ch1[] = "hello world";
	char c1 = 'o';
	char ch2[] = "llo";
	char* p1 = strchr(ch1, c1);
	char* p2 = strstr(ch1, ch2);
	printf("%s\n", p1);
	printf("%s\n", p2);
	return 0;
}
	16) strtok()
	#include <string.h>
	char *strtok(char *str, const char *delim);
	功能:来将字符串分割成一个个片段。当strtok()在参数str的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0。
	参数:
		str:指向欲分割的字符串
		delim:为分割字符串中包含的所有字符
	返回值:
		成功:分割后字符串首地址
		失败:NULL
	
	在第一次调用时:strtok()必需给予参数s字符串
	往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void)
{
	1.已给定字符串为: helloworld@qq.cn,请编码实现helloworld输出和qq.cn输出
	//char input[25] = "helloworld@qq.cn";
	//char* p;
	//p = strtok(input, "@");

	//if (p)
	//{
	//	printf("%s\n", p);
	//}
	//p = strtok(NULL, "@");
	//if (p)
	//{
	//	printf("%s\n", p);
	//}
	//2.已给定字符串为:123abcd$myname@000qwe.请编码实现匹配出myname字符串,并输出
	char ch1[50] = "123abcd$myname@000qwe.";
	char* p;
	p = strtok(ch1, "$");
	if (p)
		p = strtok(NULL, "@");
	printf("%s\n", p);
	return 0;
}
	17) atoi()
	#include <stdlib.h>
	int atoi(const char *nptr);
	功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符('\0')才结束转换,并将结果返回返回值。
	参数:
		nptr:待转换的字符串
	返回值:成功转换后整数
	
	类似的函数有:
	atof():把一个小数形式的字符串转化为一个浮点数。
	atol():将一个字符串转化为long类型
#define _CRT_SECURE_NO_WARNNINGS
#pragma warning(disable:4996)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int main()
{
	//7.strtoi和strtof和strtol

	char ch1[] = "-10";
	int num1 = atoi(ch1);
	printf("num1 = %d\n", num1);

	char ch2[] = "0.123";
	float num2 = atof(ch2);
	printf("num2 = %f\n", num2);

	char ch3[] = "1346589";
	long num3 = atol(ch3);
	printf("num3 = %ld\n", num3);
	return 0;
}

6. 函数

6.1 概述
	C 程序是由函数组成的,我们写的代码都是由主函数 main()开始执行的。函数是 C 程序的基本模块,是用于完成特定任务的程序代码单元。
	从函数定义的角度看,函数可分为系统函数和用户定义函数两种:
	系统函数:
		这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用	它们,如我们常用的打印函数printf()。
	用户定义函数:
		用以解决用户的专门需要。

6.2 函数的定义
	函数定义的一般形式:
	返回类型 函数名(形式参数列表)
	{
		数据定义部分;
		执行语句部分;
	}
	
6.3 函数名字、形参、函数体、返回值
1) 函数名
	理论上是可以随意起名字,最好起的名字见名知意,应该让用户看到这个函数名字就知道这个函数的功能。注意,函数名的后面有个圆换号(),代表这个为函数,不是普通的变量名。

2) 形参列表
	在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称
它们是形式参数或虚拟参数,简称形参,表示它们并不是实际存在的数据,所以,形参里的变
量不能赋值。
	在定义函数时指定的形参,可有可无,根据函数的需要来设计,如果没有形参,圆括号内
容为空,或写一个void关键字
	参数具体可划分为实参和形参:
		实参:1.在用户调用该函数时传入的参数就是实参;
			  2.实参出现在主调函数中,进入被调函数后,实参也不能使用;
			 
		形参:1.在定义函数的时候,规范的函数类型中定义的就是形参。
			 2.形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
			 3. 在调用函数时,编译系统临时给形参分配存储单元。调用结束后,形参单元被释放。
		实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而
	不能由形参传回来给实参。
		实参单元与形参单元是不同的单元。调用结束后,形参单元被释放,函数调用结束
	返回主调函数后则不能再使用该形参变量。实参单元仍保留并维持原值。因此,在执行一
	个被调用函数时,形参的值如果发生改变,并不会改变主调函数中实参的值。

3) 函数体
	花括号{ }里的内容即为函数体的内容,这里为函数功能实现的过程,这和以前的写代码没太大区别,以前我们把代码写在main()函数里,现在只是把这些写到别的函数里。

4) 返回值
	函数的返回值是通过函数中的return语句获得的,return后面的值也可以是一个表达式。

	a)尽量保证return语句中表达式的值和函数返回类型是同一类型。
	b)如果函数返回的类型和return语句中表达式的值不一致,则以函数返回类型为准,即函数返回类型决定返回值的类型。对数值型数据,可以自动进行类型转换。
	double max() // 函数的返回值为double类型
	{
		int a = 10;
		return a;// 返回值a为int类型,它会转为double类型再返回
	}
	注意:如果函数返回的类型和return语句中表达式的值不一致,而它又无法自动进行类型转换,程序则会报错。
	c)return语句的另一个作用为中断return所在的执行函数,类似于break中断循环、switch语句一样。
	d)如果函数带返回值,return后面必须跟着一个值,如果函数没有返回值,函数名字的
	前面必须写一个void关键字,这时候,我们写代码时也可以通过return中断函数(也可以
	不用),只是这时,return后面不带内容( 分号“;”除外)。
6.4函数的调用
		定义函数后,我们需要调用此函数才能执行到这个函数里的代码段。这和main()函数
	不一样,main()为编译器设定好自动调用的主函数,无需人为调用,我们都是在main()函
	数里调用别的函数,一个 C 程序里有且只有一个main()函数。

6.5 函数的声明
	如果使用用户自己定义的函数,而该函数与调用它的函数(即主调函数)不在同一文件中,
或者函数定义的位置在主调函数之后,则必须在调用此函数之前对被调用的函数作声明。
	所谓函数声明,就是在函数尚在未定义的情况下,事先将该函数的有关信息通知编译系统,
相当于告诉编译器,函数在后面定义,以便使编译能正常进行。
	注意:一个函数只能被定义一次,但可以声明多次。
	函数定义和声明的区别:
	1)定义是指对函数功能的确立,包括指定函数名、函数类型、形参及其类型、函数体等,
	它是一个完整的、独立的函数单位。
	2)声明的作用则是把函数的名字、函数类型以及形参的个数、类型和顺序(注意,不包括
	函数体)通知编译系统,以便在对包含函数调用的语句进行编译时,据此对其进行对照检
	查(例如函数名是否正确,实参与形参的类型和个数是否一致)。

6.6 main函数与exit函数
	在main函数中调用exit和return结果是一样的,但在子函数中调用return只是代表子函数
终止了,在子函数中调用exit,那么程序终止。

6.6 多文件(分文件)编程
	1)把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
	2)在头文件对应的xxx.c中实现xxx.h声明的函数
		也就是说在头文件.h中声明,在对应的相同名字的.c中实现该函数。
		
	3)防止头文件重复包含:
	当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 include 
多次,或者头文件嵌套包含。
	为了避免同一个文件被include多次,C/C++中有两种方式,一种是 #ifndef 方式,一
种是 #pragma once 方式。
	①:#pragma once 直接声明即可
	②: #ifndef:
				#ifndef __SOMEFILE_H__
				#define __SOMEFILE_H__
						
				// 声明语句
						
				#endif

补充

1)部分string.h内置函数的实现:
1.strcmp的实现
#include<stdio.h>
//返回值为两个字符串之间的差值
int my_strcmp()
{
	char ch1[] = "hello world";
	char ch2[] = "hello worle";
	int len1 = sizeof(ch1) / sizeof(ch1[0]);
	int len2 = sizeof(ch2) / sizeof(ch2[0]);
	int len = len1 > len2 ? len2 : len1;
	int i = 0;
	while (ch1[i] != '\0' && ch2[i] != '\0')
	{
		if (ch1[i] != ch2[i])
		{
			//printf("不相等!");
			return ch1[i] - ch2[i];
		}
		i++;
		if(ch1[i] == '\0' || ch2[i] == '\0')
		{
			return 0;
		}
	}
	return 0;
}
int main()
{
	int a = my_strcmp();
	if (a > 0)
	{
		printf("a大\n");
	}
	else if(a<0)
	{
		printf("b大\n");
	}
	else
	{
		printf("相等\n");
	}
	return 0;
}
2.strchr的实现
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//strchr函数

char* my_Strchr(const char ch1[],const char c)
{
	char* p = ch1;
	while (*p != '\0')
	{
		if (*p == c)
		{
			return p;
		}
		p++;
	}
	return NULL;
}

int main()
{
	char ch1[] = "hello world";
	char c = 'f';
	//int len = sizeof(ch1) / sizeof(ch1[0]);
	//char*p = my_Strchr(ch1, c,len);
	printf("%s\n", my_Strchr(ch1, c));
	return 0;
}
3.strstr的实现
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//1.数组的形式实现
char* myStrstr_arr(char* arr1,char* arr2)
{
	int index = 0;
	int i = 0;
	int j = 0;
	while (arr1[i] != '\0')
	{
		while(arr1[i] == arr2[j])
		{
			i++;
			j++;
			index++;
			if (index == strlen(arr2))
				return &arr1[i - j];
		}
		i = i - index + 1;
		j = j - index;
		index = 0;
	}
	return NULL;
}
//2.以指针的方式实现
char* myStrstr_Ptr(char* arr1, char* arr2)
{
	char* p = NULL;
	char* temp = arr2;
	while (*arr1)
	{
		p = arr1;
		while (*arr1 == *temp)//不相等的时候跳出
		{
			temp++;
			arr1++;
			if (*temp == '\0')//条件满足,为'\0'则执行返回条件
				return p;
		}
		//只有当temp跳入到'\0'的时候,说明temp遍历完毕了,说明相等,否则不相等
		if (*temp != '\0')//条件不满足,重置条件
			temp = arr2;
		arr1 = p;
		arr1++;
	}
	return NULL;
}

int main()
{
	char arr1[] = "hello world";
	char arr2[] = "ld";
	//char* p = arr1;
	//char* p  = strstr(arr1, arr2);
	//char* p = myStrstr_arr(arr1, arr2);//1.数组形式实现
	char* p = myStrstr_Ptr(arr1, arr2);//2.指针形式实现
	printf("%s\n", p);
	return 0;
}
4.strcat的实现
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

void myStrcat(char* ch1, char* ch2)
{
	while (*ch1)
	{
		ch1++;
	}
	while (*ch2)
	{
		*ch1 = *ch2;
		ch1++;
		ch2++;
	}

}

int main()
{
	char ch1[50] = "hello world";
	char ch2[20] = " hi cpp";
	myStrcat(ch1, ch2);
	printf("%s\n", ch1);
	return 0;
}
5.strlen的实现
#include<stdio.h>

size_t myStrlen(const char* ch)
{
	int a = 0;
	while ((*ch++) != '\0')
	{
		a++;
	}
	return a;
}
int main()
{
	char ch1[] = "helloworld";
	int a = myStrlen(ch1);
	printf("%d\n", a);
	return 0;
}
6.strtok的实现
#include<stdio.h>

char* my_strtok(char* str, char* demion)
{
    static char* p_last = NULL;
    if (str == NULL && (str = p_last) == NULL)
    {
        return NULL;
    }
    char* s = str;
    char* t = NULL;
    while (*s != '\0')
    {
        t = demion;
        while (*t != '\0')
        {
            if (*s == *t)
            {
                p_last = s + 1;
                if (s - str == NULL)
                {
                    str = p_last;
                    break;
                }
                *s = '\0';
                return str;
            }
            t++;
        }
        s++;
    }
    return NULL;
}

int main()
{
	char ch1[] = "393689416@qq.com";
	char ch2[] = "@";
	char* p = my_strtok(ch1, "@");
    printf("%s\n", p);
    char ch3[] = ".";
    char* p1 = my_strtok(NULL, ".");
	printf("%s\n", p1);
	
	return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值