![在这里插入图片描述](https://img-blog.csdnimg.cn/20210330134045808.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nhb0RhbkxpZmU=,size_16,color_FFFFFF,t_70#pic_center)
函数
函数声明
函数的声明就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。函数的声明要出现在函数使用前。声明格式如下。
返回类型 函数名(参数类型 参数名,参数类型 参数名,...);
函数定义
函数的定义就是交代函数的具体实现。在实现一个函数时,可以考虑使用C库提供的函数,也可以自己实现具体功能。函数定义可以理解为对函数声明的具体实现。定义的格式如下。
返回类型 函数名(参数类型 参数名,参数类型 参数名,...)
{
...
doSomething
...
}
函数调用
函数嵌套调用
函数可以嵌套调用,但不可以嵌套定义。最简单的入门程序helloword就是一个函数嵌套调用的例子。
int main()
{
printf("hello world");
return 0;
}
main函数内部嵌套调用了printf函数。
函数链式访问
链式访问就是把一个函数的返回值当作另一个函数的参数。
printf("%d",strlen("hello world"));
strlen函数返回"hello world"的长度,这个长度作为printf函数的参数使用。这样做可以少定义一个变量,节省内存空间。
函数递归
函数调用自身就叫做函数递归。递归的思想在于“大事化小”。递归存在两个必要条件:
- 函数必须有终止条件;
- 每次调用自身后,更加接近这个终止条件。
经典的一个递归例子就是斐波那契数列。下面代码是用递归方式实现斐波那契数列。
int fib(n)
{
if (n == 1 || n == 2)
{
return 1;
}
return fib(n - 1) + fib(n - 2);
}
对比一下非递归形式实现斐波那契数列。
int fib(n)
{
int a = 1,b = 1,c = 0;
int i;
for(i=2;i<n;i++)
{
c = a + b;
a = b;
b = c;
}
return c;
}
对比递归形式,非递归形式代码量少。缺点是当n非常大时,会造成栈溢出,时间复杂度为O(2^n),所以在实际上递归形式效果比非递归形式差得多。
数组
数组定义
数组就是相同元素的集合。不管是一维还是二维数组,数组元素在内存中都是连续存储的。
数组的创建
一维数组的创建
数组元素类型 数组名[数组大小];//数组大小为常量
二维数组的创建
数组元素类型 数组名[数组行大小][数组列大小];//行、列大小为常量
数组的初始化
一维数组的初始化
int arr[] = {
1,2,3};
int arr[10] = {
1,2,3};
char str[] = {
'h','i'};
char str[10] = {
'h','i'};
char str[] = "hi";
数组在创建时如果不指定大小,那么就必须初始化。数组元素个数根据初始化内容确定。
对于上述代码最后三种情况来说,第一种指定了数组中具体存放的元素,元素个数会根据初始化内容来确定,也就是2个字符;第二种情况指定了数组大小为10,前两个元素为’h’和’i’,剩余用’\0’填充;最后一种情况未指定数组大小,并且用字符串字面形式进行初始化,所以与第一种情况不同,存放到数组的元素除了’h’和’i’之外,还有’\0’,因此数组大小为3。
二维数组的初始化
int arr[2][2] = {
1,2,3,4};
int arr[2][2] = {
{
1,2},{
3,4}};
int arr[][2] = {
{
1,2},{
3}};//{3}实际上是{3,0}
二维数里面的每个元素是一个一维数组,因此在初始化时,可以在一个{}内,再用{}为每个一维数组初始化,当为一维数组初始化的元素个数小于一维数组大小时,剩余空间用0初始化。
为二维数组初始化时,列的大小不能省,因为数组会根据每列元素个数和初始化的元素确定行的大小。反之,知道行数不能确定列数。
数组的使用
一维数组的使用
对数组元素的访问采用下标引用操作符[ ]。下标从0开始。数组名是首元素的地址,因此在进行函数传参时将数组名传给函数,在函数内部是无法计算得到数组长度的,在函数内部使用sizeof(数组名)得到的是数组首元素地址的大小。因此在给函数传一个数组时,还要传一个数组长度。
int fun(int arr[],int len)
{
...
}
二维数组的使用
二维数组的使用同样通过下标的方式。因为是二维数组的每个元素都是一个一维数组,所以遍历二维数组的时候,要比遍历一维数组多一个for循环。
for(i = 0;i < rows; i++)
{
for(j = 0;j < cols; j++)
{
...
}
}
字符串
字符函数
- isdigit用来判断字符是否为字符0~9
- isspace用来判断判断字符是否为空格
- isupper用来判断字符是否为大写
- islower用来判断字符是否为小写
字符串函数
长度不受限
- strcpy
- strcat
- strcmp
strcpy函数声明为char* strcpy(char * destination, const char * source )
。将源字符串source拷贝到目标字符串destination中,源字符串必须以’\0’结尾,拷贝时同时将’\0’拷进目标字符串。
strcat函数声明为char * strcat ( char * destination, const char * source )
。功能与strcpy相似,不同的是拷贝的时候是从目标字符串的’\0’处开始拷贝。
strcmp函数声明为int strcmp ( const char * str1, const char * str2 )
。功能是比较两个字符串,比的是ASCII码,从头开始比,若两个字符相同并且目标字符串没有遍历完时,则移动源字符串和目标字符串的字符指针。否则,就返回对应位置的差值。
长度受限
- strncpy
- strncat
- strncmp
相比于长度不受限的strcpy,strcat,strcmp,只是指定了要操作的字符个数,而不是对整个字符串进行操作。
字符串查找
函数声明为 char* strstr(const char* ,const char*)
。第一个参数为字符串,第二个参数为要在第一个字符串中找的字符串。如果没找到,则返回NULL,否则返回第一次找到子串的字符指针。
错误报告
函数声明为char* strerror(int errnum)
。参数为错误码,函数返回错误码所对应的错误信息。错误码在errno.h中定义,使用时可以直接传入errno.h中定义的errno,表示最近发生错误的错误码。
字符串长度
strlen可以得到一个字符串的长度,通过指针的移动,统计字符个数,指针遇到’\0’就停止。
字符串转数字
atoi首次遇到非数字字符或者不是’+’/’-'就返回0,如果不是,就遍历字符串,直到遇到非数字字符,将数字字符串转成整形然后返回。
字符串常见操作
字符串操作主要体现在算法面试题中。为此,应当在日常算法练习中积累相关知识。
操作符
移位操作符
移位有左移和右移。左移时,操作数二进制形式地位补0;右移时,分两种情况,若为算术右移,操作数二进制形式高位用符号位填充,若为逻辑右移,高位用0填充给。
//左移
int i = 1;//1的二进制形式0001
i = i<<1;//1左移一位变成0010
//右移
int i = -1;//-1的二进制形式为全'1':FF FF FF FF(用16进制表示32位-1)
i = i>>1;//若为算术右移,高位用符号位填充,右移1位,值不变;若为逻辑右移,高位填0,变为7F FF FF FF FF
位操作符
位操作符主要有按位与、或、非。
- 按位与时,两者为1,结果才为1,其它情况结果为0。
- 按位或时,两者只要有一方为1,结果为1,否则结果为0。
- 非,即按位取反,1变0,0变1。
逻辑操作符
- 逻辑与
&&
,当且仅当所有条件为真时,才为真,其它情况结果为假。 - 逻辑或
||
,当且仅当所有条件为假时,结果才为假,其它情况结果为真。
需要注意的是,这两者的运算存在短路运算
,对于&&
来说,当有一个条件为假时,后面的条件就不去管了,对于||
来说,当有一个条件为真时,就不管后面的条件了。
表达式求值
整型提升
表达式的操作数在求值过程中可能需要转换成其它类型。因为通用CPU难以实现两个8bit的数相加运算,CPU内算术逻辑运算单元(ALU)的操作数的字节长度一般是int的字节长度,所以表达式中的字符和短整型操作数在使用之前会被转换成普通整形,即整形提升。
char c1 = -1;
char c2 = 1;
char c3 = c1 + c2;
c