c语言作业总结

  这篇博客记录了学习c语言过程中遇到的问题,以及解决方案。很多问题都很简单,但是经常会碰到。我把这些踩过的坑记录下来,防止日后再踩。
  其中也有一些掌握不太好的,主要是写代码时不太常用,再想用的时候又要去查,所以我也把这些记录下来了。
图片来源于网络

switch的用法

  switch后不能是实参,case后不能是变量

整型数来选择情况

1
2
3
4
5
6
7
8
int n;
scanf("%d",&n); //不要忘记地址符
switch(n)
{
case 1: 语句1; break; //如果没有特殊的要求,switch后要加break;
case 2: 语句2; break;
default: 语句3; //default后不用break,因为已经到最后了
}

字符选择情况

1
2
3
4
5
6
7
8
char c;
scanf("%c",&c);
switch(c)
{
case 'a': 语句1; break; //注意case后加的是 '字符',而不是直接 字符
case 'b': 语句2; break; //与上一个区分开
default: 语句3;
}

取整的话,正数可以直接加0.5取整,但是负数不行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int round_to_nearest(float num);//我喜欢先写函数,写完再复制到上面来声明
//int round_to_nearest(float); 这个num可写可不写,只要告诉类型即可
int main()
{
float num;
int result;
while(scanf("%f",&num)!=EOF) //无限次循环语句
{
result = round_to_nearest(num);
printf("%d\n",result);
}return 0;

}

int round_to_nearest(float num)
{
if (num>=0) //正数取整
return ((int)(num+0.5));
else //负数取整
return ((int)(num-0.5)); //强制类型转换是(int)数,而不是int(数)
}

打印出表格的效果

  目前能想到的方法有两种,第一种方法是用数组处理,然后用循环把数组打印出来;第二种方法是将内容写在文件里面,然后从文件中读取打印到屏幕上。(人工手动敲出来的我就不说了,这应该是计算机做的事情,而不是人应该做的)
  数组的好处在于可以直接进行处理,不需要再进行写入和读取的操作,同时也不是一个一个定义的,存储有永久性,可以随用随调。缺点就是,也算不上是缺点吧,如果数组比较多的话,可能需要用到结构,那么写的代码行数自然就多了,而且自己可能还弄不明白,绕来绕去就糊涂了。
  文件的好处在于简单,也就是做完一步就可以把想显示的内容写到文件当中,只需要最后去按行读取、打印就可以了。(文件的读取有一个函数,会在后面写出来)

循环和递归

  如果能用循环解决问题就不要用递归解决,递归耗内存,耗时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
int intergerPower2(int base,int exponent);
int main()
{
int base,exponent,result;
while(scanf("%d %d",&base,&exponent)!=EOF)
{
result = intergerPower2(base,exponent);
printf("%d\n",result);
}
return 0;
}

int intergerPower2(int base,int exponent){
int result2=1;
if(exponent<=0) //或者是
return 1; //if(exponent<=1)
//return base;
return base*intergerPower2(base,exponent-1);

}

  递归的基本形式。

循环中gets会吃上一次的回车

  Write a program that enters 5 names of towns and their respective distance (an integer) from London in miles. The program will print of the names of the towns that are less than 100 miles from London. Use arrays and character strings to implement your program.
  例如在这题中,如果你考虑城市的名称中有空格的存在,那么你就不应该用sacnf去读取城市的名称,你应该用gets去读取。但是gets会把回车吃掉。举个例子。如果你这么写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
int less100(int miles);
int main()
{
char name[10000];
int distance[5];
int i,a=0;
for(i=0;i<5;i++)
{
printf("Please Enter Name : ");
gets(name);
printf("Please Enter the distance:");
scanf("%d",&distance[i]);
a = less100(distance[i]);
if (a == 1)
printf("\n%s is less than 100 miles from London\n\n",name);
}
return 0;
}

int less100(int miles){
if (miles < 100)
return 1;
return 0;
}


  效果是这个样子的,原因是上一次的回车停留在缓冲区,下一次输入敲回车,得到的是上次的回车,会有错位的情况,所以解决问题的方法是清除缓冲区[fflush(stdin);]stdin就是键盘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
int less100(int miles);
int main()
{
char name[10000];
int distance[5];
int i,a=0;

for(i=0;i<5;i++)
{
printf("Please Enter Name : ");
gets(name);
printf("\n");
fflush(stdin); //清空缓冲区
printf("Please Enter The Distance From London : ");
scanf("%d",&distance[i]);
printf("\n");
fflush(stdin); //清空缓冲区
a = less100(distance[i]);
if (a == 1)
printf("%s\n\n",name);
}
return 0;
}

int less100(int miles){
if (miles < 100)
return 1;
return 0;
}

函数回传指针

  如果函数的返回值是一个指针的话,同时要进行多次循环调用函数的话,如果没有把指针定义成数组的形式,是无法进行整体输出的。需要在每次调用完函数之后进行输出,也就是所谓的释放空间。如果定义成数组的形式,可以回传整个指针(或者说是数组),然后用循环整体打印。
  回传的如果是数组,打印的话不能写成printf(“%d”,p);而应该是printf(“%d”,p[0]);这是读取数组里的第一个元素,依次类推。如果是printf(“%d”,p);显示的则是p数组的地址,而不是数值。

关于字符串

  可以说字符串就是数组,数组就是字符串。如果想定义二维数组的字符串可以像这样定义char string[5][10000]; string[0]就是第一个字符串,string[1]是第二个字符串,依次类推到string[4],长度均为10000。
  还有就是strlen和sizeof的区别,由于字符串都会以”\0”结尾,所以真实长度比你输入的长度多1。例如字符串’Happy’的长度为6,分别为 H a p p y \0 。 所以如果用strlen取字符串长度会得到5,也就是不会读取 \0 , 但是如果用sizeof会得到6 , 也就是算上了\0。 使用时要格外注意。

读取数组的四种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
int main()
{
int num1[5],num2[5],i;
int *p=num2;
for(i=0;i<5;i++)
scanf("%d",&num1[i]);
for(i=0;i<5;i++)
num2[i]=num1[4-i];

for(i=0;i<5;i++)
printf("%d ",num2[i]);
printf("\n");
for(i=0;i<5;i++)
printf("%d ",*(num2+i));
printf("\n");
for(i=0;i<5;i++)
printf("%d ",*(p+i));
printf("\n");
for(i=0;i<5;i++)
printf("%d ",p[i]);
printf("\n");
return 0;
}

  可以看出,数组和指针是一个东西。而且读取很方便,想读谁就可以直接读取。我才不会主动用指针写程序呢。

检验字符串是否合法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Check if the input is valid
int k=0;
printf("Please Enter Your ID Number:");
do{
if(k>0)
printf("Your ID Number Includes 2 Letters and 4 Digits:");
k++;
scanf("%s",ID);
}while(strlen(ID)!=6||valid(ID)!=1); //If the length is not 6, it will ask to put again, and the speed will be fast

int valid(char *string){
int i,letter=0,number=0;
for(i=0;i<2;i++)
{
if((string[i]>='a' && string[i]<='z') || (string[i]>='A' && string[i]<='Z')) //How many letters
letter+=1;
}
for(i=2;i<6;i++)
{
if(string[i]>='0' && string[i]<='9') //How many numbers
number+=1;
}
if(letter == 2 && number == 4) //If the string is valid, return 1
return 1;
return 0; //If it is invalid, return 0
}


  检验字符串的合法性,先检验长度是否满足,如果长度不满足,直接判断为不合法,可以加快判断速度。因为计算机做判断和循环是很浪费时间的。所以先判断字符串长度是否符合可以加快程序运行的速度。
  还有就是如果输入错误的话,让用户重新输入的提示,可以用一个判断来进行,让第二次的显示不同于第一次,如果可以的话,还可以告诉用户到底是哪里输入错误,还有怎么输入才是正确的。

文件

文件使用

  需要先定义文件

1
2
3
4
5
6
FILE *tset
if( (test=fopen("test.txt","w")) == NULL ){
perror("test.txt");
exit(1);
}

w写入,但如果之前有会把之前的清空
a追加,在文件的最后写入,不会把之前的清空
r读取。
w+,r+,a+,表示之前的功能都有,也可以写入
带b的就是以二进制的形式对文件进行操作

文件的读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
FILE *record;
readfile(record);//调用函数
void readfile(FILE *record)
{
char *str1,*ptr;
char *res[999];
int p = 0;
FILE *pFile;
char mystring[1000];
pFile = fopen("record.txt","r");
if (pFile == NULL)
perror ("Error opening file");
else {
while (fgets (mystring , 100 , pFile) != NULL ) //读取长度为100
{
int len = strlen(mystring); //Read the record.tet by line
if(mystring[len-1]=='\n') //Think of a line as a string
mystring[len-1] = '\0'; //按行读取

char* tmp = (char*)malloc(100*sizeof(char));
memcpy(tmp,mystring,len); //usage memcpy(dest, src, strlen(src));
res[p++] = tmp;
for(i=0;i<p;i++)//如果想打印文件,可以直接printf("%s",res[i]);
{
ptr = strstr(res[i],ID);
//strstr用来判断前面的字符串是否包含后面字符串的内容。
} //Determine whether the string containing the ID
if( ptr != NULL ) //If it does contain the string of ID
printf("%s\n",ptr); //Printf the string
}
fclose (pFile); //Close the file
}
}


  文件使用前要打开,使用后要关闭,可以的话可以清空缓冲区,并把指针调回。

精准计时

1
2
3
4
5
6
7
8
clock_t start, finish;
double duration;
start = clock();
//把需要进行计时的部分放到这里
finish= clock();
duration = (double)(finish- start) / CLOCKS_PER_SEC;
//这个用的是处理器的时间计时,所以要除以1s的处理器时间(CLOCKS_PER_SEC)
printf("The time is %.2lfs.\n",duration);//一般秒就保留到两位小数就可以了。

如何产生真正的随机数,随机并且平均

  随机数的产生依赖种子,一般就是用时间的不同显示种子的不同。我们不用秒作为种子,用更精细的clock(),也就是srand((unsigned)clock());
  比如大作业的十个算术题,要加减乘除每个最少两次,最多三次怎么做到呢?
  可以让前八个题每种算法都只出现两次,后两个只要是不同的就行。
  用循环和判断来解决这个问题。比如加法,

1
2
3
4
5
6
7
8
9
10
11
int add=0;
int i;
for(i=0;i<8;i++)
if(add<2)
{
进行加法运算;
i++;
add++;
}
else
i--;

  如果要用switch来选择,那么应该每个后面加一个break; 注意区分break和continue的区别,break是停止,是跳出,而且只能跳出一层,不可以直接都跳出来。continue是结束本次循环,重新进行循环是否满足条件的判断,满足就进行,不满足就不进行,而不是直接开始下次循环。也帮有的同学看过代码,出现的问题就是还没有确定满足能输出的条件呢,就add++了,所以最后出来的题的数目一定会小于10道。同时也有后面的问题,就是最后两次的判断条件是if(add<1)后面的都一样,出来的结果肯定是只有八个题,因为前面已经加减乘除的计数都到2了,所以小于1是不可能的,程序就会出现想卡出一样的情况。
  这个i- -用的很巧妙,简而言之就是如果不行,这次循环大家就当无事发生,重新来过。

while 的循环

  如果想用while一直循环,可以直接写while(1){运行语句}就可以了,虽然老师说这就是个死程序,但是你可以中间添加break; 来实现你的目的,等到用户输入一个特定的值的时候,你就break跳出循环,十分的方便。顺便提一句,想把内容调回开头可以用goto语法,但是不是很推荐,有兴趣的可以自己了解。
  如果你想一直输入无限个数,就用while(scanf(“%d”,&num)){运行语句}。如果是想输入有限个数,可以定义有几个数n,然后用for循环,个人觉得for循环可以解决循环80%的问题,剩下的20%是用while,极少数用do-while,就是判断合法性的时候用一下do-while。for可以代替while和do-while,就是用的时候看起来不太美观。不要为了使用某个语句而用某个语句,而是你觉得就应该用它,你就用它,自己看着怎么舒服怎么来。
  同时如果while循环一个大的整体的时候,需要考虑下一次的把上一次的该清零的清零,该重置的重置,不然会影响程序的运行。并不是简单的三行代码就能实现完美循环,还要不断的调试、优化。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!