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 ;
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 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] ) ;
}
}
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] ;
}
}
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 ( )
{
char ch[ 10 ] = { 0 } ;
fgets ( ch, 10 , stdin ) ;
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 ( )
{
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 ( )
{
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 ( )
{
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 ( )
{
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 ( )
{
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 )
{
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 ( )
{
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] )
{
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>
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' ;
printf ( "%s\n" , my_Strchr ( ch1, c) ) ;
return 0 ;
}
3.strstr的实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 ;
}
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' )
return p;
}
if ( * temp != '\0' )
temp = arr2;
arr1 = p;
arr1++ ;
}
return NULL ;
}
int main ( )
{
char arr1[ ] = "hello world" ;
char arr2[ ] = "ld" ;
char * p = myStrstr_Ptr ( arr1, arr2) ;
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 ;
}