栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言零基础入门级 递归+回调+字符串函数+面试题全讲解【系统学习第六天】

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言零基础入门级 递归+回调+字符串函数+面试题全讲解【系统学习第六天】


C语言零基础入门级 函数大全+面试题全讲解
  • 【1】C语言-》函数入门
  • 【2】C语言-》函数的定义
  • 【3】C语言-》 函数实参与形参
  • 【4】C语言-》函数调用的流程
  • 【5】C语言-》静态函数
  • 【6】C语言-》递归函数
  • 【7】C语言-》回调函数(钩子函数)
  • 【8】C语言-》signal信号处理
  • 【9】C语言-》函数strstr
  • 【10】C语言-》函数strlen
  • 【11】C语言-》函数strtok
  • 【12】C语言-》函数strcat与strncat
  • 【13】C语言-》函数strcpy与strncpy
  • 【14】C语言-》函数strcmp与strncmp
  • 【15】C语言-》函数strchr与strrchr
  • 【16】C语言-》面试题

【1】C语言-》函数入门

在C语言中,函数意味着功能模块。一个典型的C语言程序,就是由一个个的功能模块拼接起来的整体。也因为如此,C语言被称为模块化语言。 对于函数的使用者,可以简单地将函数理解为一个黑箱,使用者只管按照规定给黑箱一些输入,就会得到一些输出,而不必理会黑箱内部的运行细节

日常使用的电视机可以被理解为一个典型的黑箱,它有一些公开的接口提供给使用者操作,比如开关、音量、频道等,使用者不需要理会其内部电路,更不需要管电视机的工作原理,只需按照规定的接口操作接口得到结果。

对于函数的设计者,最重要的工作是封装,封装意味着对外提供服务并隐藏细节。对于一个封装良好的函数而言,其对外提供服务的接口应当是简洁的,内部功能应当是明确的。

【2】C语言-》函数的定义

函数头:函数对外的公开接口
函数名称:命名规则与跟变量一致,一般取与函数实际功能相符合的、顾名思义的名称。
参数列表:即黑箱的输入数据列表,一个函数可有一个或多个参数,也可以不需要参数。
返回类型:即黑箱的输出数据类型,一个函数可不返回数据,但最多只能返回一个数据。
函数体:函数功能的内部实现

语法说明:

`返回类型 函数名称(参数1, 参数2, ……)
{
    函数体
}`

函数示例1:求两个给定整数的最大值

int max(int x, int y) // 该函数接收两个整型参数,并返回一个整型数据
{
    int z;
    z = x>y ? x : y;
    return z;
} 

函数示例2:交换两个浮点数

void swap(double *p1, double *p2) // 该函数接收两个浮点指针参数,不返回数据
{
    if(p1 == NULL || p2 == NULL)
        return;
        
    double tmp;
    tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
} 

函数示例3:初始化液晶屏,获取显存入口指针

char * initLCD(void) // 该函数不接受参数,返回一个字符指针
{
    int lcd = open("/dev/fb0", O_RDWR);
    
    struct fb_var_screeninfo vinfo;
    ioctl(lcd, FBIOGET_VSCREENINFO, &vinfo);
    int bpp  = vinfo.bits_per_pixel;
    int size = vinfo.xres * vinfo.yres * bpp/8;
    
   char * fbmem = mmap(NULL, size, PROT_READ, MAP_SHARED, lcd, 0);
   return fbmem;
} 

语法汇总:

当函数的参数列表为 void 时,表示该函数不需要任何参数。
当函数的返回类型为 void 时,表示该函数不返回任何数据。
关键字return 表示退出函数。
①若函数头中规定有返回数据类型,则 return 需携带一个类型与之匹配的数据;
②若函数头中规定返回类型为 void,则 return 不需携带参数。

【3】C语言-》 函数实参与形参

概念:
函数调用中的参数,被称为实参,即 arguments
函数定义中的参数,被称为形参,即 parameters

  • 实参与形参的关系:

实参于形参的类型和个数必须一一对应。
形参的值由实参初始化。
形参与实参位于不同的内存区域,彼此独立。

  • 示例:
// 函数定义中,x、y都属于形参,位于函数 max 的栈内存中
// 它们的值由实参一一对应初始化
int max(int x, int y)
{
    int z;
    z = x>y ? x : y;
    return z;
}

int main(void)
{
    int a = 1;
    int b = 2;
    int m;
        
    // 函数调用中,a、b都属于实参,存储于主函数 main 的栈内存中
    m = max(a, b);    
}
【4】C语言-》函数调用的流程

函数调用时,进程的上下文会切换到被调函数,当被调函数执行完毕之后再切换回去。

  • 局部变量与栈内存

局部变量概念:凡是被一对花括号包含的变量,称为局部变量

局部变量特点:

某一函数内部的局部变量,存储在该函数特定的栈内存中 局部变量只能在该函数内可见,在该函数外部不可见
当该函数退出后,局部变量所占的内存立即被系统回收,因此局部变量也称为临时变量
函数的形参虽然不被花括号所包含,但依然属于该函数的局部变量

栈内存特点: 每当一个函数被调用时,系统将自动分配一段栈内存给该函数,用于存放其局部变量
每当一个函数退出时,系统将自动回收其栈内存 系统为函数分配栈内存时,遵循从上(高地址)往下(低地址)分配的原则

  • 示例代码:
int max(int x, int y) // 变量 x 和 y 存储在max()函数的栈中
{
    int z;            // 变量 z 存储在max()函数的栈中
    z = x>y ? x : y;
    return z;         // 函数退出后,栈中的x、y 和 z 被系统回收
}

int main(void)
{
    int a = 1; // 变量 a 存储在main()函数的栈中
    int b = 2; // 变量 b 存储在main()函数的栈中
    int m;     // 变量 m 存储在main()函数的栈中,未赋值因此其值为随机值
    
    m = max(a, b);
}

技术要点: 栈内存相对而言是比较小的,不适合用来分配尺寸太大的变量。return 之后不可再访问函数的局部变量,因此返回一个局部变量的地址通常是错误的。

【5】C语言-》静态函数

背景知识:普通函数都是跨文件可见的,即在文件 a.c中定义的函数可以在 b.c中使用。
静态函数:只能在定义的文件内可见的函数,称为静态函数。

语法:

staitc void f(void) // 在函数头前面增加关键字 static ,使之成为静态函数
{
    // 函数体
}

要点[面试题常问]: 静态函数主要是为了缩小函数的可见范围,减少与其他文件中重名函数冲突的概率。
静态函数一般被定义在头文件中,然后被各个源文件包含。

【6】C语言-》递归函数

递归概念:如果一个函数内部,包含了对自身的调用,则该函数称为递归函数。

递归问题:

  • 阶乘。
  • 幂运算。
  • 字符串翻转。
  • [X]

要点:
只有能被表达为递归的问题,才能用递归函数解决。
递归函数必须有一个可直接退出的条件,否则会进入无限递归。
递归函数包含两个过程,一个逐渐递进的过程,和一个逐渐回归的过程。

示例:依次输出 n 个自然数。

思路:先输出前面的 n-1 个自然数,再输出最后一个自然数 n 。而要输出前面的 n-1 个自然数,递归调用自身即可。

// 该函数的功能:依次输出 n 个自然数
void f(int n) 
{
    if(n < 0)          // 1,当满足此条件时,不再进行递归。
        return;
        
    f(n-1);            // 2,递归调用自己,输出前 n-1 个数
    printf("%dn", n); // 3,输出最后一个自然数 n
}

递归调用时,函数的栈内存的变化如下图所示。可见,随着递归函数的层层深入,栈空间逐渐往下增长,如果递归的层次太深,很容易把栈内存耗光。 层层递进时,问题的规模会随之减小,减小到可直接退出的条件时,函数开始层层回归。

【7】C语言-》回调函数(钩子函数)

概念:函数实现方不调用该函数,而由函数接口提供方间接调用的函数,称为回调函数。
示例:系统中的信号处理,是一个典型的利用回调函数的情形。

【8】C语言-》signal信号处理

要点:
上面示例中函数 sighandler 是回调函数。

signal()将函数回调函数传递给内核,使得内核可以在恰当的时机回调 sighandler。
应用开发者和内核开发者只要约定好回调函数的接口,即可各自开发,进度互不影响。

【9】C语言-》函数strstr

示例:

char *s = "abcd.txt";
char *p = strstr(s, ".wps");

if(p == NULL)
    printf("文件[%s]不是WPS文件n", s);
else
    printf("文件[%s]是WPS文件n", s);
【10】C语言-》函数strlen


示例:

char *s = "www.yueqian.com.cn";
printf("粤嵌官网地址的长度是:%dn", strlen(s));
【11】C语言-》函数strtok

注意:

该函数会将改变原始字符串 str,使其所包含的所有分隔符变成结束标记 ‘’。

由于该函数需要更改字符串 str,因此 str 指向的内存必须是可写的。 首次调用时 str 指向原始字符串,此后每次调用 str 用
NULL 代替。

示例:

char s[20] = "www.yueqian.com.cn";

char *p = strtok(s, ".");  // 首次调用时,s 指向需要分割的字符串
while(p != NULL)
{
    printf("%sn", p);
    p = strtok(NULL, "."); // 此后每次调用,均使用 NULL 代替。
}

注:上述代码的运行结果就是将字符串 s 拆解为"www"、“yueqian”、“com” 和 “cn”

【12】C语言-》函数strcat与strncat


注意: 

这两个函数的功能,都是将 src 中的字符串,复制拼接到 dest 的末尾。 strcat() 没有边界控制,因此可能会由于 src的过长而导致内存溢出。 strncat() 有边界控制,最多复制 n+1 个字符(其中最后一个是‘’ )到 dest 的末尾。

示例:

char s1[10] = "abc";
strcat(s1, "xyz");
printf("%sn", s1); // 输出 "abcxyz"

char s2[10] = "abc";
strcat(s3, "123456789"); // 此处操作内存溢出,可能会发生内存崩溃


char s[10]  = "abc";
strncat(s, "123456789", sizeof(s)-1);//10-1=9
printf("%sn", s); // 输出 "abc123456",两个字符串被拼接到了一起,且不会溢出

注意:strncat()是具备边界检查的安全版本,推荐使用。

【13】C语言-》函数strcpy与strncpy

注意:

这两个函数的功能,都是将 src 中的字符串,复制到 dest 中。 strcpy() 没有边界控制,因此可能会由于 src的过长而导致内存溢出。 strncpy() 有边界控制,最多复制 n+1 个字符(其中最后一个是 ‘’ )到 dest 中。

示例:

char s1[5] = "abc";
strcpy(s1, "xyz");
printf("%sn", s1); // 输出 "xyz",原有的"abc"被覆盖

char s2[5] = "abc";
strcpy(s2, "123456789"); // 此处操作内存溢出,可能会发生内存崩溃
char s[5] = "abc";
strncpy(s, "123456789", sizeof(s)-1);//5-1=4
printf("%sn", s); // 输出 "1234",有边界保护,不会溢出

注意:strncpy()是具备边界检查的安全版本,推荐使用。

【14】C语言-》函数strcmp与strncmp

注意: 比较字符串大小,实际上比较的是字符的ASCII码值的大小。 从左到右逐个比较两个字符串的每一个字符,当能“决出胜负”时立刻停止比较。

示例:

printf("%dn", strcmp("abc", "abc")); // 输出0,两个字符串相等
printf("%dn", strcmp("abc", "aBc")); // 输出1,"abc" 大于 "aBc"
printf("%dn", strcmp("999", "aaa")); // 输出-1,"999" 小于 "aaa"
【15】C语言-》函数strchr与strrchr


注意:

这两个函数的功能,都是在指定的字符串 s 中,试图找到字符 c。
strchr() 从左往右找,strrchr() 从右往左找。
字符串结束标记 ‘’被认为是字符串的一部分。

示例:

char *p=NULL;
p = strchr("www.qq.com", '.'); // 从左到右找到第一个出现的字符'.'
printf("%sn", p);             // 输出 ".qq.com"

p = strrchr("www.qq.com", '.');// 从右到左找到第一个出现的字符'.'
printf("%sn", p);             // 输出 ".com"
【16】C语言-》面试题

(函数声明)
【1】写出下面所描述的各个函数的声明语句:

函数 f1 接受一个 int 类型的参数,没有返回值。
函数 f2 接受两个 int 类型的参数,并返回一个 int 型数据。
函数 f3不接收任何参数,也不返回任何数据。
解析: 本题只需要写出函数的声明即可,不需要写定义。

参考代码:

1. void f1(int);
2. int  f2(int, int);
3. void f3(void);

(函数定义与调用)
【2】编写一个函数,使其返回3个整型参数中的最大值。

解析: 首先,该函数需要接受3个参数,其次,要在函数中求出最大值并返回。

参考代码:

#include 

// 求出3个数中最大的一个
int max(int x, int y, int z)
{
    int max = (x>y) ? x : y;
    return (max>z) ? max : z;
}

int main(void)
{
    printf("请输入3个整数: ");
    
    // 为了突出重点,此处未进行输入合法性检测,望读者知悉
    int a, b, c;
    scanf("%d%d%d", &a, &b, &c); 
    
    printf("最大的数是: %dn", max(a, b, c));
    return 0;
}

(函数定义与调用、递归)
【3】编写一个函数 myPower(x, N)myPower(x,N),输入给底数xx和幂指数NN,计算输出x^Nx N 。
其中xx可以是任意浮点数,NN是整数。
例如:

float ans1 = myPower(4, 2);     // 4的2次方
float ans2 = myPower(3.14, -2); // 3.14的-2次方

要求用两个版本来实现 myPower:

采用循环的算法实现。
采用递归的算法实现。
解析:
本题主要考察循环和递归两种常见的解题思维方式。

参考代码:

#include 
#define EPSILON 3.4e-38

// 第一种,用循环思维方式解题:
float myPower1(float base, int exp)
{
    int i;
    float tmp = base;
    for(i=0; i
        base *= tmp;
    }
    return base;
}
// 第二种,用递归思维方式解题:
float myPower2(float base, int exp)
{
    if(exp == 0)
        return 1;
    
    return myPower2(base, exp-1) * base;
}
int main(void)
{
    printf("请输入底数和幂指数(例如:3.14, 2)代表求3.14的平方:");
    
    float base;
    int exp;
    scanf("%f, %d", &base, &exp);
    
    // 第一:0的0次方没有意义
    if(base == 0 && exp == 0)
        printf("0的0次方无意义!n");
    
    // 第二:0的任意次方(0次方除外)均为0
    if(base == 0 && exp != 0)
        printf("%f^%d = 0n", base, exp);
    
    // 第三:任意数(0除外)的0次方均为1
    else if(base != 0 && exp == 0)
        printf("%f^%d = 1n", base, exp);
    
    // 第四:负整数次幂,转换为正次幂的倒数
    else if(exp < 0)
    {
        exp *= -1;
        printf("%f^%d = %.6fn", base, exp, 1/myPower1(base, exp));
        printf("%f^%d = %.6fn", base, exp, 1/myPower2(base, exp));
    }
    
    // 第五:正整数次幂
    else
    {
        printf("%f^%d = %.6fn", base, exp, myPower1(base, exp));
        printf("%f^%d = %.6fn", base, exp, myPower2(base, exp));
    }
    
    return 0;
}

(函数、字符串处理)
【4】编写一个程序,将两个给定的字符串连接起来,要求不能用 strcat 或 strncat 函数。

解析: 本题的主要考点有几个:
第一,字符串都有一个结束标记,拼接两个字符串时需要将前面字符串的结束标记 ‘’覆盖掉。
第二,要考虑字符串拼接后的存储空间是否足够,不能发生内存溢出的情况。

参考代码:

#include 
#include 

char *myStrcat(char *str1, char *str2)
{
	int i;
	i = strlen(str1);

	int j;
	for(j=0; j<50-i  ; j++)
	{
		str1[j+i] = str2[j];
	}

	return str1;
}

int main(void)
{
	char str1[50] = "hello";
	char str2[50] = "123456789012345";

	printf("合并后: %sn", myStrcat(str1, str2));
	return 0;
}

(递归函数)
【5】编写一个程序,用户输入整数 N,程序输出第 N 项斐波那契数。

参考代码:

#include 

int fibonacci(int num)
{
    // 第1和第2个斐波那契数不需要经过任何计算,规定都是1
    if(num == 1 || num == 2)
        return 1;
    
    // 第N个斐波那契数,等于第N-1个和第N-2个斐波那契数之和
    return (fibonacci(num-1) + fibonacci(num-2));
}


int main(void)
{
    printf("你想看第几个斐波那契数?");
    
    int num;
    scanf("%d", &num); // 为了突出重点,此处未进行输入合法性检测,望读者知悉
    
    printf("第 %d 个斐波那契数是: %dn", num, fibonacci(num));
    return 0;
}

(函数、字符串处理)
【6】编写一个函数,接收一个给定的字符串,返回去除所有的空格后的字符串。
参考代码:

#include 
#include 

char * trim(char *s)
{
    if(s == NULL)
        return s;

    int size = strlen(s) + 1;
    char tmp[size];

    for(int i=0, j=0; i<=size; i++)
    {
        if(s[i]!=' ')
        {
            tmp[j] = s[i];
            j++;
        }
    }

    memcpy(s, tmp, size);
    return s;
}

int main(int argc, char **argv)
{
    char s[100];
    bzero(s, 100);
    fgets(s, 100, stdin);

    printf("%sn", trim(s));
    return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/892405.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号