一、学习内容
-
讲解有参函数
-
形参 和 实参
-
形参——定义时的参数,形式上的参数,没有实际意义,语法上必须带有数据类型 void fun(int a,int b); void fun(int a[],int n); void fun(char *s); 可以是:变量、数组、指针
-
实参——调用时的参数,实际上的参数,有实际意义,实参语法上不加数据类型,直接传参名字 fun(a,b) fun(1,2) fun(a+2,3) fun(a,strlen(a)) fun(3,fun(4,5)) 可以:常量、变量名、数组名、指针名、表达式
-
注意:实参必须和形参保持数据类型、顺序、数量一一对应。【名字无所谓】
-
eg: void fun(int a,float b,char c); fun(3 , 4 , 'A'); //正确 fun(3,4); //错误 int x=3; float y=3.5; char z='a'; fun(x,y,z); //正确
-
-
-
值传递 和 地址传递
-
值传递
-
形参和实参分处不同的存储单元形参变实参不变
-
实参【原件】拷贝出一份值给形参【复印件】,复印件修改原件不变
-
-
-
地址传递
-
形参和实参除处在相同的存储单元里面形参变、实参也变
-
实参【原件】直接给了形参
-
-
-
-
数组传参方式
-
整型一维数组传参
-
一维数组传参需要两个参数——数组、数组的长度
-
void FA(int a[10],int n);
-
void FA(int a[],int n);
-
void FA(int *a,int n);
-
-
-
整型二维数组传参
-
二维数组传参需要三个参数——数组、行数、列数
-
void FB(int b[2][3], int hang, int lie);
-
void FB(int b[][3], int hang, int lie);
-
void FB(int (*b)[3], int hang, int lie);
-
-
-
字符串传参
-
因为字符串可以通过strlen()或者条件判断!='\0'来控制循环。所以,字符串的传参最少有一个——字符串名
-
void my_strlen(char s[100]);
-
void my_strlen(char s[];)
-
void my_strlen(char *s);
-
-
-
-
-
讲解有返回值函数
-
问:函数必须有返回值吗?
-
答:可以没有,根据需求设置返回值,而且设置返回值之后,调用时可以接收返回值,也可以不接收返回值。
-
-
有返回值函数的定义语法
-
函数数据类型 函数名(参数){ return 返回值; //返回【一个】确定值 }
-
若返回值数据类型与函数数据类型不一致,以【函数数据类型】为准。
-
return有结束函数的作用,若有多个return,遇见第一个return就结束函数。
-
若省略函数数据类型不写,默认是【int】类型。
-
-
-
-
递归函数
-
递归思想
-
将大规模问题分解成相似的小规模问题,再将小规模问题分解成更小规模的相似的问题,最终通过解决小规模问题 把大规模问题就解决。
-
-
问:函数可以自己调用自己吗?若可以的话,会出现什么问题呢?
-
可以,但是不加条件的话,会类似于“死循环”
-
-
概念
-
自己调用自己的函数。
-
-
递归三要素
-
递归边界条件【结束条件】
-
递归返回段【边界条件满足,结束】
-
递归前进段【边界条件不满足,继续】
-
-
递归函数的应用
-
斐波那契数列、树的遍历、图的遍历等
-
-
递归实现0-n求和
-
问题规模是n
假设一个函数 int fun(int n);功能是对规模n求和
fun(n); 求0到n之和
fun(n-1); 求0到n-1之和
fun(n) === fun(n-1)+n; fun(n-2); 求0到n-2之和
……
fun(1); 求0-1之和
fun(0); 求0-0之和
-
-
递归实现求斐波那契数列第n项
-
问题规模是n
假设一个函数int fbnq(int n);功能求第n项
fun(n) n
fun(n-1) n-1
fun(n-2) n-2
……
fun(2) 第2项 1
fun(1) 第1项 1
-
-
-
指针函数
-
注意
-
不可以返回局部变量的地址
-
可以返回全局变量的地址
-
可以返回static局部变量的地址
-
可以返回形参接收的地址
-
-
语法格式
-
数据类型 *函数名(参数){ 代码块; }
-
-
-
函数指针
-
本质是一个指针,指向一个函数
-
语法格式
-
数据类型 (*指针名)(参数列表);
-
-
已学习的指针:
-
int *p; 指向整型变量的地址【一维数组的首地址】
char *p; 指向单字符变量的地址【字符串/字符数组的首地址】
int (*p)[3]; 指向二维数组【行指针】
void (*p)(int a,int b); 指向函数的地址
-
-
-
函数指针数组
-
本质是一个数组,存储的元素都是函数指针。
-
语法格式
-
数据类型 (*数组名[长度])(参数);
-
-
作用
-
转移表【C语言转移表(Jump Table)是一种优化技术,可以用来代替一系列的if-else语句或switch语句,从而提高代码的执行效率。】
-
-
-
变量的作用域——全局 和 局部
-
数组、函数、指针总结
-
数组能存储指针
-
函数能返回指针
-
指针能指向数组、指向函数
-
int *p; 【指向一个int变量/一维数组】
-
int *p[3]; 【指针数组,存储int指针】
-
int (*p)[3]; 【数组指针,指向二维数组,也就是行指针】
-
int *p(); 【指针函数,返回值是int指针】
-
int (*p)(); 【函数指针,指向返回值为int的函数】
-
int (*p[3])(); 【函数指针数组,存储指针,指针指向返回值为int的函数】
-
int (*(*p)[3])(); 【p是一个指针,指向函数指针数组】
-
int (*(*p)())[3]; 【p是一个指针,指向一个函数,函数的返回值是指针,这个指针指向长度为3的int数组】
-
-
脑图
二、作业
-
以下程序的正确运行结果是( )。(鲁科安全)
int f(int a);
int main(void)
{
int a = 2,i;
for(i = 0; i < 3; i++)
printf("%4d", f(a));
}
int f(int a)
{
int b = 0;
static int c = 3;
b++;
c++;
return (a+b+c);
}
A. 777 B. 7 10 13 C. 7 9 11 D. 7 8 9
解析:
第一次调用
f(a)
:
a = 2
b = 0
(初始值),然后b++
,因此b = 1
c = 3
(初始值),然后c++
,因此c = 4
返回值:
a + b + c = 2 + 1 + 4 = 7
第二次调用
f(a)
:
a = 2
b = 0
,然后b++
,因此b = 1
c
上次调用后为 4,然后c++
,因此c = 5
返回值:
a + b + c = 2 + 1 + 5 = 8
第三次调用
f(a)
:
a = 2
b = 0
,然后b++
,因此b = 1
c
上次调用后为 5,然后c++
,因此c = 6
返回值:
a + b + c = 2 + 1 + 6 = 9
程序的输出结果:
7 8 9
解答:
D
- 在一个被调用函数中,关于return语句使用的描述,( )是错误的 (软通动力)
A. 被调用函数中可以不用return语句
B. 被调用函数中可以使用多个return语句
C. 被调用函数中,如果有返回值,就一定要有return语句
D. 被调用函数中,一个return语句可以返回多个值给调用函数
解析:
A. 被调用函数中可以不用
return
语句对于
void
类型的函数,return
语句是可选的,因为函数不需要返回值。如果函数有返回类型(如
int
、float
等),则必须使用return
返回相应类型的值。结论:正确,可以不用
return
,但这是针对void
函数的情况。B. 被调用函数中可以使用多个
return
语句函数中可以在不同的逻辑分支中使用多个
return
语句,表示在不同条件下返回不同的结果。这是常见的编程模式。结论:正确,多个
return
语句是合法的。C. 被调用函数中,如果有返回值,就一定要有
return
语句对于有返回类型的函数(如
int
、float
等),必须有return
语句来返回指定类型的值,否则编译器会产生错误。结论:正确,有返回值的函数必须有
return
语句。D. 被调用函数中,一个
return
语句可以返回多个值给调用函数一个
return
语句只能返回一个值。要返回多个值,通常需要使用结构体、指针、数组等方式来间接返回多个值。结论:错误,
return
语句一次只能返回一个值。
解答:
D
-
以下程序的运行结果为( ) (鲁科安全)
#include <stdio.h>
#include <string.h>
int SubCount(char *dest, int count)
{
strcpy(dest, "555");
count++;
return 0;
}
int main()
{
int count = 3;
char caBuf[8];
SubCount(caBuf, count);
printf("%d\n", count);
return 0;
}
A. 8 B. 4 C. 3 D. 5
解析:
在
main()
函数中:
count
初始化为 3。定义了一个字符数组
caBuf[8]
。调用了函数
SubCount(caBuf, count)
。在
SubCount()
函数中:
strcpy(dest, "555")
将字符串"555"
复制到dest
,即caBuf
中。
count++
增加了count
的值,但这里的count
是值传递,意味着main()
函数中的count
不会受到影响,SubCount()
函数只是修改了自己的副本。函数返回 0,但没有影响到
main()
函数中的count
值。
SubCount()
函数返回后,main()
函数中的count
仍然是原来的值 3。最后,
printf("%d\n", count)
输出的是main()
函数中的count
的值,即 3。
解答:
C
-
请问运行Test函数会有什么样的结果?(华辰泰尔)
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
解析:
GetMemory()
函数中:
char p[] = "hello world";
定义了一个局部字符数组p
,并将字符串"hello world"
存储在其中。该数组是局部变量,存储在函数的栈帧中。
函数返回
p
的地址(指针),但是由于p
是局部变量,函数返回后,该栈帧会被释放,p
所指向的内存地址不再有效。
Test()
函数中:调用了
GetMemory()
,并将返回的地址赋值给str
。然后调用
printf(str)
试图打印字符串。运行结果:
由于
GetMemory()
函数返回的是栈中局部变量p
的地址,函数返回后该栈帧被释放,指向的内存不再有效,因此str
指向的是一个无效的地址。printf(str)
将尝试访问该无效地址,结果会有以下几种情况:未定义行为:因为访问了无效的内存区域,程序可能会崩溃(例如导致段错误
Segmentation fault
)。在某些情况下,程序可能会打印随机数据或者乱码(如果栈内存尚未被覆盖)。
解答:
段错误
-
分析以下程序,写出运行结果并写出过程 (广域科技)
#include <stdio.h>
#include <stdlib.h>
void getalloc(char *p)
{
p = (char *)malloc(100);
strcpy(p, "hello world");
}
int main()
{
char *str = NULL;
getalloc(str);
printf("%s\n",str);
free(str);
return 0;
}
解析:
在
main()
函数中:定义了一个字符指针
str
并将其初始化为NULL
。调用了
getalloc(str)
函数,试图为str
分配内存并赋值。在
getalloc()
函数中:参数
p
是值传递,意味着p
是str
的一个副本。
p = (char *)malloc(100);
为局部指针p
分配了 100 字节的内存空间。
strcpy(p, "hello world");
将字符串"hello world"
复制到p
指向的内存区域。函数结束时,
p
仅是局部变量的修改,str
在main()
函数中的值并没有被修改。回到
main()
函数:
str
仍然是NULL
,因为在getalloc()
函数中修改的是p
的副本,而不是str
的原始指针。当调用
printf("%s\n",str);
时,str
仍然指向NULL
,这会导致未定义行为。最常见的结果是程序崩溃,出现 段错误(Segmentation fault),因为printf
试图访问空指针。
free(str);
也会导致未定义行为,因为free
不能释放NULL
指针或未分配的内存。运行结果:
由于
str
没有被成功分配内存,程序会在printf
调用处崩溃,最可能的结果是发生 段错误(Segmentation fault)。
解答:
段错误
-
下列程序的输出结果是________。(富士安全)
fun(int a, int b, int c)
{
c = a*b;
}
void main()
{
int c = 10;
fun(2,3,++c);
printf("%d\n", c);
}
解析:
main()
函数中:
int c = 10;
定义了一个整数变量c
,初始值为 10。调用
fun(2, 3, ++c)
之前,c
被前置递增(++c
),因此c
的值从 10 变为 11。在
fun()
函数中:
fun(2, 3, ++c)
被调用时,参数传递的是a = 2
,b = 3
,c = 11
(传递的是递增后的c
值)。需要注意,C 语言中,参数是通过值传递的,意味着在
fun()
函数中修改c
(局部变量)并不会影响main()
函数中的c
。在
fun()
函数中,执行c = a * b;
,这只是修改了fun()
函数内部的局部变量c
,它的值变为2 * 3 = 6
。但是,这并不会改变main()
函数中的c
。回到
main()
函数:调用
fun()
后,main()
函数中的c
仍然是 11(因为在fun()
中修改的是局部变量,不会影响main()
中的c
)。
printf("%d\n", c);
将输出c
的值,即 11。
解答:
11
-
找错题,说明那里错了(恩易物联1,深圳元征信息科技)
void test1()
{
char string[10];
char *str1 = "0123456789";
strcpy( string, str1 );
}
解析:
数组
string
的长度不足:
char string[10];
定义了一个大小为 10 的字符数组。这意味着它最多能存储 10 个字符。但是,
str1
是"0123456789"
,它有 10 个字符加上一个空字符\0
,总共需要 11 个字节 来存储。因此,当使用
strcpy( string, str1 );
复制字符串时,会试图将 11 个字符复制到一个只能容纳 10 个字符的数组中,导致 缓冲区溢出,这是一个严重的错误,可能会导致程序崩溃或不可预期的行为
解答:
段错误
-
下面的代码片段会输出__________ (飞音时代)
void test(void)
{
char *p = NULL;
strcpy(p, "hello");
printf("%s", p);
}
解析:
指针
p
初始化为NULL
:
char *p = NULL;
将指针p
初始化为NULL
,即p
不指向任何有效的内存地址。尝试将字符串复制到
NULL
指针:
strcpy(p, "hello");
试图将字符串"hello"
复制到指针p
指向的内存区域。然而,
p
是NULL
,并没有指向任何有效的内存地址,因此尝试写入会导致 段错误(Segmentation fault),程序崩溃。操作系统一般会保护
NULL
地址,防止写入。
printf("%s", p);
不会被执行:由于在
strcpy
处程序会崩溃,printf
语句不会被执行,因此程序没有输出。
解答:
段错误
-
sizeof(str); 的值是多少? (21年中航安为)
void Func(char str[100])
{
sizeof(str);
}
解析:
数组退化为指针:
在函数参数中,数组类型会自动退化为指向数组首元素的指针。因此,
char str[100]
实际上等同于char *str
。也就是说,
str
在函数内部是一个指针,而不是一个数组。
sizeof(str)
的结果:
sizeof(str)
实际上是在计算指针的大小,而不是数组的大小。指针的大小依赖于系统架构:
在32位系统上,指针大小通常为 4 字节。
在64位系统上,指针大小通常为 8 字节。
解答:
4/8
-
递归函数最终会结束,那么这个函数一定( );(北京信果科技)
A. 使用了局部变量
B. 有一个分支不调用自身
C. 使用了全局变量或者使用了一个或多个参数
解析:
A. 使用了局部变量
不一定。递归函数使用局部变量有助于保存每次递归调用的状态,但递归函数的结束条件与是否使用局部变量无关。局部变量只是用来存储数据,并不直接影响递归的终止。
B. 有一个分支不调用自身
正确。为了使递归函数能够终止,它必须有一个基本情况或终止条件,使得递归调用停止。如果递归函数在所有情况下都调用自身,那么它将永远不会终止。因此,递归函数需要至少一个分支不调用自身,以确保递归能够结束。
C. 使用了全局变量或者使用了一个或多个参数
不一定。递归函数可以使用全局变量或者参数来控制递归,但这并不是递归函数必须具备的条件。全局变量和参数可以帮助控制递归的行为,但递归函数的最终结束依赖于其终止条件。
解答:
B
-
程序如下,程序执行后的输出结果是: (中科四平)
int f(int x, int y)
{
return (y-x)*x;
}
void main()
{
int a = 3,b=4,c=5,d;
d=f(f(3,4),f(3,5));
printf("%d\n", d);
}
解析:
f(int x, int y)
函数:函数体:
return (y - x) * x;
函数返回的是
(y - x) * x
的值。
main()
函数中的变量:
int a = 3, b = 4, c = 5, d;
d = f(f(3, 4), f(3, 5));
我们需要先计算
f(3, 4)
和f(3, 5)
,然后将这两个结果作为参数传递给f()
。计算
f(3, 4)
:
f(3, 4) = (4 - 3) * 3
4 - 3 = 1
1 * 3 = 3
所以
f(3, 4) = 3
计算
f(3, 5)
:
f(3, 5) = (5 - 3) * 3
5 - 3 = 2
2 * 3 = 6
所以
f(3, 5) = 6
计算
d = f(f(3, 4), f(3, 5))
:已知
f(3, 4) = 3
和f(3, 5) = 6
因此
d = f(3, 6)
计算
f(3, 6)
:f(3, 6) = (6 - 3) * 3
6 - 3 = 3
3 * 3 = 9
所以
f(3, 6) = 9
输出结果:
printf("%d\n", d);
将输出d
的值,即9
。
解答:
9
-
请判断下面程序输出的值是多少? (信雅达)
int func(int a)
{
static int b = 0;
b+=a;
return b;
}
int main(void)
{
printf("%d %d\n", func(1)+func(3), func(5));
}
解析:
func(int a)
函数:
static int b = 0;
:b
是静态变量,它在函数调用间保持其值。
b += a;
:每次调用func
时,b
的值会增加a
。
return b;
:函数返回b
的值。
main()
函数中的代码:
printf("%d %d\n", func(1) + func(3), func(5));
计算
func(1)
:初始时,
b = 0
。调用
func(1)
时,b += 1
,所以b = 1
。
func(1)
返回1
。计算
func(3)
:之前
b = 1
。调用
func(3)
时,b += 3
,所以b = 4
。
func(3)
返回4
。计算
func(5)
:之前
b = 4
。调用
func(5)
时,b += 5
,所以b = 9
。
func(5)
返回9
.计算
func(1) + func(3)
:已经计算得出
func(1) = 1
和func(3) = 4
。
func(1) + func(3) = 1 + 4 = 5
。最终的
printf
语句:
printf("%d %d\n", func(1) + func(3), func(5));
之前已经计算出
func(1) + func(3) = 5
和func(5) = 9
。
解答:
5 9
-
这段程序的输出是(________) (青岛汉泰)
void f1(int *, int);
void f2(int *, int);
void(*p[2]) (int *, int);
main()
{
int a;
int b;
p[0] = f1;
p[1] = f2;
a=3;
b=5;
p[0](&a, b);
printf("%d\t %d\t", a, b);
p[1](&a, b);
printf("%d\t %d\t", a, b);
}
void f1(int * p, int q)
{
int tmp;
tmp = *p;
*p = q;
q = tmp;
}
void f2(int *p, int q)
{
int tmp;
tmp = *p;
*p = q;
q = tmp;
}
A. 5 5 5 5 B. 3 5 3 5 C. 5 3 5 3 D. 3 3 3 3
解析:
函数指针数组
p
的初始化:
p[0]
被赋值为f1
,p[1]
被赋值为f2
。这使得
p
成为指向f1
和f2
函数的指针数组。变量初始化和函数调用:
a = 3;
b = 5;
p[0](&a, b);
调用f1(&a, b)
p[1](&a, b);
调用f2(&a, b)
f1
函数的执行过程:
f1(int *p, int q)
的功能:
tmp = *p;
保存*p
(即a
的值)到tmp
,此时tmp = 3
。
*p = q;
将q
(即b
的值 5)赋给*p
,所以a
被更新为 5。
q = tmp;
将tmp
的值(3)赋给q
,但这只改变了q
的值,q
是局部变量,不影响b
。执行
f1(&a, b)
后:
a
的值变为 5
b
的值保持为 5结果:
a = 5
,b = 5
f2
函数的执行过程:
f2(int *p, int q)
的功能:
tmp = *p;
保存*p
(即a
的值)到tmp
,此时tmp = 5
。
*p = q;
将q
(即b
的值 5)赋给*p
,所以a
保持为 5(没有变化)。
q = tmp;
将tmp
的值(5)赋给q
,但这只改变了q
的值,q
是局部变量,不影响b
。执行
f2(&a, b)
后:
a
的值保持为 5
b
的值保持为 5结果:
a = 5
,b = 5
解答:
A
-
有以下程序段, x=7执行后的值为 ( ) (杭州快越科技)
int fun(int x) {
int p;
if(x==0||x==1)
return(3);
p=x-fun(x-2);
return p;
}
A. 0 B. 2 C. 5 D. 6
解析:
逐步计算
fun(7)
:计算
fun(7)
:
fun(7) = 7 - fun(5)
计算
fun(5)
:
fun(5) = 5 - fun(3)
计算
fun(3)
:
fun(3) = 3 - fun(1)
计算
fun(1)
:基本情况:
fun(1) = 3
回到
fun(3)
:
fun(3) = 3 - fun(1) = 3 - 3 = 0
回到
fun(5)
:
fun(5) = 5 - fun(3) = 5 - 0 = 5
回到
fun(7)
:
fun(7) = 7 - fun(5) = 7 - 5 = 2
解答:
B
-
有以下函数,该函数的返回值是:( ) (山东信通电子)
char *fun(char *p)
{
return p;
}
A. 无确切的值 B. 形参 p 中存放的地址值
C. 一个临时存储单元的地址 D. 形参 p 自身的地址值
解析:
A. 无确切的值:
不正确。函数
fun
返回的是传递给它的指针p
,并且返回值是确定的。B. 形参 p 中存放的地址值:
正确。函数
fun
返回的是传递给它的char *p
的值,即p
存放的地址值。C. 一个临时存储单元的地址:
不正确。
p
是传递给函数的实际指针值,而不是临时存储单元的地址。D. 形参 p 自身的地址值:
不正确。
p
是指向char
的指针,返回的是指针的值,而不是形参p
在内存中的地址。
解答:
B
-
编写strcpy函数 (山东山大电力技有限公司)
已知strcpy 函数的原型是
char *strcpy(char *strDest,const char *strSrc);其中 strDest 是目的字符串,strSrc 是源字符串。
(1)、不调用 C 的字符串库函数,请编写函数 strcpy。
(2)、strcpy 能把 strSr 的内容复制到 strDest,为什么还有 char"类型的返回值?
(3)、strcpy 和 memcpy 的区别。
代码解答:
#include <stdio.h>
char *strcpy(char *strDest, const char *strSrc) {
char *dest = strDest; // 保存指向目标字符串的指针
while (*strSrc != '\0') { // 遍历源字符串直到遇到终止符
*dest = *strSrc; // 将源字符串的字符复制到目标字符串
dest++; // 移动目标指针
strSrc++; // 移动源指针
}
*dest = '\0'; // 添加字符串终止符到目标字符串的末尾
return strDest; // 返回目标字符串的起始地址
}
int main() {
char src[] = "Hello, World!";
char dest[50];
strcpy(dest, src);
printf("%s\n", dest);
return 0;
}
结果展示:
解答:
问题2:
链式调用:可以通过返回指针实现函数的链式调用,例如 strcpy(dest, src) == dest
,这使得代码更加灵活和简洁。
方便使用:在某些情况下,用户可能需要直接获取目标字符串的起始地址来进行进一步操作,而不仅仅是复制字符串。
问题3:
功能:
strcpy
:用于复制以 null 终止的字符串。它会复制源字符串的所有字符,包括终止符'\0'
到目标缓冲区。
memcpy
:用于复制任意类型的内存块,不考虑数据的内容或类型。它只是按照字节进行复制,不处理字符串终止符。参数:
strcpy
:char *strcpy(char *strDest, const char *strSrc)
,接受两个char *
类型的参数,表示字符串的源和目标。
memcpy
:void *memcpy(void *dest, const void *src, size_t n)
,接受void *
类型的源和目标指针,以及一个size_t
类型的字节数,表示要复制的字节数。安全性:
strcpy
:在源字符串的末尾假设有一个终止符'\0'
,如果源字符串没有终止符或者目标缓冲区不够大,可能导致缓冲区溢出。
memcpy
:没有终止符的概念,因此不会自动处理字符串的结束,必须确保目标缓冲区足够大以容纳源数据。
-
请实现一个函数,输入一个整数,输出该数二进制表示中的1的个数。例如:把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。(矩阵软件)
代码解答:
#include <stdio.h>
int countOnes(int num) {
int count = 0;
while (num) {
count += num & 1; // 检查最低位是否为 1
num >>= 1; // 右移一位,继续检查下一位
}
return count;
}
int main() {
int num;
printf("输入一个十进制数字: ");
scanf("%d", &num);
int result = countOnes(num);
printf("%d\n", result);
return 0;
}
成果展示:
思路:
位操作计算1的个数:
int count = 0;
:初始化计数器count
,用于记录1
的个数。
while (num) { ... }
:循环直到num
变为0
。
num & 1
:位与操作检查num
的最低位。如果最低位是1
,结果为1
;否则为0
。
count += num & 1;
:将检查结果加到count
中。
num >>= 1;
:将num
右移一位,丢弃最低位,并将下一位移到最低位。
return count;
:返回1
的总数。
例如:你输入的是
9
:二进制表示:
9
的二进制表示是1001
。执行步骤:
初始
num = 9
(1001
)
num & 1
:最低位为1
,所以count
增加1
,count = 1
。
num >>= 1
:右移一位后,num = 4
(100
)。
num & 1
:最低位为0
,所以count
不变,count = 1
。
num >>= 1
:右移一位后,num = 2
(10
)。
num & 1
:最低位为0
,所以count
不变,count = 1
。
num >>= 1
:右移一位后,num = 1
(1
)。
num & 1
:最低位为1
,所以count
增加1
,count = 2
。
num >>= 1
:右移一位后,num = 0
。退出循环,
count
最终值为2
。
-
请用编写递归算法计算fibinacci数列第1000位的值。斐波拉契数列为1,1,2,3,5,8,13,21,…… (北京凝思软件)
代码解答:
#include <stdio.h>
// 递归计算斐波那契数列第 n 位的值
unsigned long long fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
int n = 1000;
// 计算第 1000 位的斐波那契数,注意这会非常耗时和可能会导致栈溢出
printf("斐波那契(%d) = %llu\n", n, fibonacci(n));
return 0;
}
-
用 C 语言写一个递归算法求 N!(华辰泰尔)
代码解答:
#include <stdio.h>
// 递归计算阶乘函数
unsigned long long factorial(int n) {
if (n <= 1) {
return 1; // 基本情况:0! 和 1! 都是 1
}
return n * factorial(n - 1); // 递归调用
}
int main() {
int n;
printf("请输入一个数字:");
scanf("%d", &n);
if (n < 0) {
printf("输入的数字不合理\n");
} else {
printf("%d! = %llu\n", n, factorial(n));
}
return 0;
}
成果展示:
三、总结
1. 学习内容概述
指针的使用和传递
学习了如何在函数中使用指针并传递变量地址,包括传递一维数组和二维数组的指针,使函数可以直接操作数组内容。
函数参数的传递方式
重点理解了C语言中函数参数的两种传递方式:**值传递**和**地址传递**。值传递会传递变量的副本,而地址传递可以通过传递指针修改原始变量的值。
递归函数设计
学习了递归函数的设计与实现,递归是函数调用自身的过程,常用于解决具有重复结构的问题,如计算阶乘、斐波那契数列等。
指针函数和函数指针
了解了指针函数和函数指针的区别与用法,函数指针可以指向一个函数,通过指针调用函数;而指针函数则是返回指针的函数。
2. 学习难点
函数指针的使用
虽然已经理解了函数指针的概念,但在实际应用中,如何将函数指针与复杂的函数调用机制结合使用,尤其是在函数作为参数传递时,仍然是一个难点。
指针与数组的区别
指针和数组在C语言中关系密切,但它们的区别和使用场景不同,特别是多维数组指针的传递和解引用,容易让初学者混淆。
递归函数的调试
递归函数虽然简单但不易调试,特别是在递归终止条件不明确或递归深度过大时,容易导致栈溢出或程序崩溃。
3. 注意事项
值传递与地址传递的选择
在编写函数时,需要根据实际需求选择合适的参数传递方式。如果函数需要修改调用者的变量,应该使用地址传递(即传递指针);如果只需要处理变量的副本,则可以使用值传递。
指针函数与函数指针的区别
指针函数是返回指针的函数,而函数指针是指向函数的指针,虽然概念接近,但要注意区分使用场景。例如,函数指针通常用于回调函数,而指针函数则多用于动态分配内存后返回地址。
递归函数的边界条件
在编写递归函数时,一定要明确递归的终止条件,否则会导致无限递归,程序会因栈溢出而崩溃。可以通过测试递归的中间结果来验证逻辑的正确性。
数组指针传递时的边界检查
传递数组指针时,要确保数组在函数内操作时没有越界,尤其是传递多维数组时,要明确数组的大小和结构。
4. 未来学习的重点
深入研究函数指针的应用场景
函数指针在回调机制和动态函数调用中有着重要作用。未来的学习可以深入研究函数指针在操作系统、事件驱动编程和动态库中的应用。
递归与迭代的比较
递归虽然简洁,但在某些情况下性能不佳,可能导致栈溢出。未来学习中可以探索递归与迭代的性能对比,并掌握如何将递归转换为迭代。
结构体与动态内存管理结合使用
指针和结构体的结合非常强大,尤其是在处理复杂数据结构时。未来可以深入学习如何使用指针动态创建结构体,并结合`malloc`和`free`进行内存管理。