c语言简单编程练习9
1、输入一个整数,判断它是否为回文数
例:输入:12321 输出:该数是回文数 输入:12323 输出:该数不是回文数
#include <stdio.h>
int huiwenshu(int num)
{
int a[100];
int i, n, j, k;
i = 0;
n = 0;
while(num) //将整数存入数组 1234 -> a[100]={4,3,2,1};
{
n = num%10;
a[i] = n;
num /= 10;
n = 0;
i++;
}
k = i-1; //找到最后一个数的下标
for(j=0;j<(i-1)/2;j++) //第一个与最后一个比较,从两边向中间依次比较
{
if(a[j]==a[k])
k--;
else
return 0;
}
return 1;
}
int main(int argc, char *argv[])
{
int n, m;
printf("请输入一个整数:");
scanf("%d",&n);
m = huiwenshu(n);
if(m==1)
{
printf("该数是回文数\n");
}
else
printf("该数不是回文数\n");
return 0;
}
解析: 在之前我们判断的是回文字符串,字符串我们是可以通过指针指向这个字符串,然后直接把最后一个字符与第一个字符比较,依次比较即可;但是这里需要判断的是回文数,这是一个整数我们并不能直接使用指针来比较它,所以我先使用了一个数组将整数的每一位数都存入了数组,然后通过调用数组里的值进行比较判断;
这里存整数是通过除以十取余数的方式来存储,将整数除以十,得到的余数存入数组第一个元素的位置,然后得到这个数除以十后的商,用商再除以十取余得到整数十位上的数,存入数组第二个元素的位置,依次存储,最后在数组中得到的就是整数反过来读的一组数字,例如1234,在数组中就是4321;虽然是倒过来的但是并不影响我们判断它是否为回文数;
得到数组后我们直接使用一个循环来进行比较,循环的条件跟整数的位数相关,也就是跟数组中的元素个数相关,j=0;j<(i-1)/2;j++;j从0开始,到小于元素个数的一半即可,这样就能保证比较完;在这里我把判断回文数写为了一个函数,当比较到数字不相同就返回0(表示输入的整数不是回文数),要是循环能完成就返回1;这样通过判断函数的返回值就能知道输入的整数是不是回文数。
2、函数的多文件封装
多文件封装的优点:
- 模块化:
- 将代码拆分成多个文件,每个文件负责特定的功能或模块,可以使代码结构更加清晰和易于管理。
- 模块化有助于减少代码之间的依赖,使得代码更易于维护和扩展。
- 可读性:
- 将函数和类分散到多个文件中,每个文件专注于一个特定的任务或功能,可以提高代码的可读性。
- 开发者可以更容易地找到和理解所需的代码部分,而不必在大量代码中搜索。
- 可重用性:
- 通过将常用功能封装到独立的文件中,可以更容易地在多个项目或模块中重用这些功能。
- 这有助于减少代码重复,提高开发效率。
- 代码维护:
- 当需要修改或更新代码时,模块化结构使得修改更加局部化,减少了引入错误的风险。
- 独立的文件也更便于版本控制和代码审查。
- 编译和加载效率:
- 在一些编程语言中,如C或C++,将代码拆分成多个文件可以优化编译过程,因为编译器可以只重新编译那些发生变化的文件。
- 对于大型项目,这可以显著减少编译时间。
- 在一些动态语言(如Python)中,将代码拆分成多个模块也可以提高模块的加载效率,因为模块只会在需要时被加载。
- 团队协作:
- 模块化结构使得多个开发者可以并行工作在不同的模块上,减少了代码冲突和合并问题的可能性。
- 这有助于加快开发进度,提高团队协作效率。
- 命名空间管理:
- 将函数和类封装到不同的文件中,可以避免命名冲突,特别是当使用全局变量或函数时。
- 通过命名空间(如Python中的模块、Java中的包)来组织代码,可以提高代码的安全性和可维护性。
- 代码封装和隐藏实现细节:
- 将实现细节封装在单独的文件中,并通过公共接口(如API)暴露必要的功能,可以隐藏内部实现细节,提高代码的封装性和安全性。
- 这有助于减少外部代码对内部实现的依赖,使得内部实现更容易进行更改和优化。
函数的多文件封装通常分为三个文件:自定义头文件、功能函数文件、测试文件(主函数文件)
自定义头文件里面放的是库头文件(用<>引用)和我们自定义的头文件(用双引号引用)
< >和" "的区别:
尖括号< >
- 用途:主要用于引用标准库头文件或安装在系统级别的第三方库头文件。
- 查找路径:编译器会直接在它的默认包含目录中查找头文件。这些目录通常由编译器预定义或通过编译选项
双引号"
- 用途:主要用于引用项目特定或自定义的头文件,例如你自己写的头文件或者与项目紧密相关的第三方库头文件。
- 查找路径:编译器会首先在源文件所在的目录进行查找,然后才会去编译器默认的包含目录中查找。这种方式允许开发者更灵活地管理文件位置,特别是在项目结构复杂时。
下面我将题1的代码来做多文件封装,便于宝子们对照
自定义头文件:
#ifndef _HUIWEN_H
#define _HUIWEN_H
#include <stdio.h>
int huiwenshu(int num);
#endif
头文件的格式就是头两行为 #ifndef _HUIWEN_H(如果没有定义huiwen.h这个头文件)和#define _HUIWEN_H(定义huiwen.h这个头文件),最后加一行#endif;中间要写的就是所需要头文件的引用和我们自己功能函数的声明
功能函数文件:
#include "huiwen.h"
int huiwenshu(int num)
{
int a[100];
int i, n, j, k;
i = 0;
n = 0;
while(num) //将整数存入数组 1234 -> a[100]={4,3,2,1};
{
n = num%10;
a[i] = n;
num /= 10;
n = 0;
i++;
}
k = i-1; //找到最后一个数的下标
for(j=0;j<(i-1)/2;j++) //第一个与最后一个比较,从两边向中间依次比较
{
if(a[j]==a[k])
k--;
else
return 0;
}
return 1;
}
在功能函数文件里面我们首先需要引用我们自定义的头文件 #include "huiwen.h",之后就是需要实现功能的函数,这里面可以放多个功能函数,同时就需要在我们自定义的头文件里面声明所有的功能函数。
测试文件(主函数文件):
#include "huiwen.h"
int main(int argc, char *argv[])
{
int n, m;
printf("请输入一个整数:");
scanf("%d",&n);
m = huiwenshu(n);
if(m==1)
{
printf("该数是回文数\n");
}
else
printf("该数不是回文数\n");
return 0;
}
在测试文件里面我们也需要先引用我们自定义的头文件,之后就是通过调用函数来实现相应的功能。
注意:除了我们的测试文件(主函数文件) 之外,其他两个文件都不能有主函数int main()。
3、 静态存储(static)
作用:
-
在函数体内定义静态变量
- 记忆功能:被声明为static的局部变量在这一函数被调用的过程中其值维持不变,即函数返回后,该变量的值不会丢失,下次该函数被调用时,该变量将保持上次函数执行结束时的值。
- 存储位置:静态局部变量存储在全局数据区(静态区),而非栈区。
- 生命周期:静态局部变量的生命周期贯穿整个程序运行期间,但其作用域仍然是局部的,即只能在定义它的函数内部访问。
-
在模块内(函数体外)定义静态变量
- 作用域限制:静态全局变量只能被本模块内的函数访问,而不能被模块外的其他函数访问。它相当于一个本地的全局变量。
- 存储位置:同样存储在全局数据区。
- 初始化:静态全局变量只初始化一次,防止在其他文件单元中被引用。
-
定义静态函数
- 作用域限制:静态函数只能在本源文件中使用,不能被其他源文件调用。这有助于实现信息隐藏和模块化编程。
记忆功能,我也把它称为延长生命周期(不是延长作用域!!!),下面我用一个例子来说明:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
p:{
int a;
a = 10;
a++;
printf("%d\n",a);
sleep(2);
goto p;
}
return 0;
}
在代码中我使用goto做了一个循环,相信宝子们都知道上面代码会每2秒打印a的值,但是由于每次goto都回到了a定义前,因此每次打印a的值都为11,如下图:
但是我如果使用static去修饰变量a,那么goto回去之后并不会将它初始化,a任然会保持上次执行的值不变,在上次值的基础上++,执行结果如上面右图,代码如下:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
p:{
static int a =10;
a++;
printf("%d\n",a);
sleep(2);
goto p;
}
return 0;
}
4、printf的缓冲行为
当printf
将数据输出到标准输出时,这些数据的缓冲行为遵循标准输出的缓冲机制。
缓冲的一些关键点:
-
行缓冲:当输出流是交互式设备(如终端或控制台)时,标准输出通常是行缓冲的。这意味着输出会存储在缓冲区中,直到遇到换行符(
\n
)或缓冲区满时,缓冲区的内容才会被刷新到输出设备。 -
全缓冲:当输出流是文件时,标准输出通常是全缓冲的。这意味着输出会存储在缓冲区中,直到缓冲区满或显式调用
fflush
函数时,缓冲区的内容才会被写入文件。 -
无缓冲:标准错误(
stderr
)通常是无缓冲的,意味着每次调用如fprintf(stderr, ...)
都会立即将输出刷新到设备,而不会存储在缓冲区中。
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("1111");
while(1)
{
//printf("2222");
//usleep(10000);
}
return 0;
}
当printf没有加换行符‘\n’时,代码会全部执行完才会将1111打印出来,我在下面加了一个死循环之后,光标会在终端一直闪烁,并不会输出1111,运行结果如下:
把上述代码被注释掉的两行加入,就能得到当缓冲区被放满后,缓冲区的内容被刷新输出到终端的结果,如下图: