C语言初步学习笔记

时间:2021-03-19 00:19:28

嵌套循环

1.从键盘输入n值(10>=n>=3),然后计算并输出∑ i!=1!+2!+…+n!。

外层循环控制加(i),内层循环控制阶乘(j
使内外的i,j值相等(初始值均为1
内层循环每次从1开始叠成到j,当ji相等时,跳出内层循环,执行叠加语句:sum=p+sum,再次执行外部循环直到i=n

叠乘—加到sum—再叠乘—新值加到sum—i=n循环结束

注意:在外层循环中每次内层循环开始之前要初始化 p = 1(为了保证内层循环中叠乘为从1开始到j结束)

#include <stdio.h>
void main()
{
    int i,j,n;
    long p,sum =0;
    printf("Input n:");
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        p = 1;
        for(j=1;j<=i;j++)
        {
            p = p*j;
        }
        sum = sum+p;
    }
    printf("1!+2!+…+%d!=%ld\n",n,sum);
}

随机数

stdlib.h ———–调用随机数
变量 = rand() ————变量被计算机“想”的数赋值
srand(1) ——-调用第一组随机数种子(每组随机数种子固定,如果无此调用,则每次计算机“想”的数不变)

time.h——-调用电脑时间
srand(time(NULL)) ———- 使电脑当前以秒计算的日历时间作为随机数种子

流程的转移控制

goto语句

可以任意跳转到程序的任何方位。
goto END; START:
… …
END:… goto START;(又回到上面重新执行)

通常与if语句联合使用:if(表达式) goto 语句标号;

语句标号:…

优点:goto是快速跳出多重循环的捷径,并且可以跳向共同的出口位置(结尾一个一句标号,中间可以用多个goto语句同时跳到结尾),进行退出前的错误处理工作。
注意:尽量避免使用goto语句从而避免造成程序逻辑混乱或死循环。

break与continue

break为跳出当前循环。(如果为嵌套则跳出break语句所在循环)
continue为跳过后面尚未执行的语句直接开始下一次循环,不终止整个循环的执行。(再次判断循环的表达式,并且算作完成一次循环而使得 i+1)(如果为嵌套则执行所在循环的下一次)



//输入5个正整数并且显示他们。负数立即终止。


#include <stdio.h>
void main()
{
    int i,n;
    for (i=1;i<=5;i++)
    {
        printf("Please enter n:");
        scanf("%d",&n);
        if (n<0)    break;
        printf("n=%d\n",n);
    }
    printf("Program is over! \n");
}

穷举法

韩信点兵问题:韩信有一队兵,按1-5报数,最后一个士兵报数为1;按1-6报数,为5;按1-7报数,为4;按1-11报数,为10。韩信至少有多少兵?

穷举法使得X1开始逐个试验,直到第一个使关系式成立的数,即为所求。

#include <stdio.h>
void main()
{
    int x;
    for (x=1;;x++)
    {
        if(x%5 == 1 && x%6 == 5 && x%7 == 4 && x%11 == 10)
        {
            printf("%d\n",x);
            break;
        }
    }
}

或者也可引用exit()函数直接结束程序运行(调用stdlib.h

#include <stdio.h>
#include <stdlib.h>
void main()
{
    int x;
    for (x=1;;x++)
    {
        if(x%5 == 1 && x%6 == 5 && x%7 == 4 && x%11 == 10)
        {
            printf("%d\n",x);
            exit(0);
        }
    }
}

使用标志变量:

  定义一个标志变量find,先置find为假,表示“未找到”,一旦找到了满足给定条件的解,就将find置于真,表示“找到了”。 (!find值为真时,继续循环,直到find值为真)

#include <stdio.h>
void main()
{
    int x;
    int find = 0;              //置找到标志变量为假
    for (x=1;!find;x++)        //find为假时继续循环
    {
        if(x%5 == 1 && x%6 == 5 && x%7 == 4 && x%11 == 10)
        {
            printf("x = %d\n",x);
            find = 1;          //置找到标志变量为真
        }
    }
}

0为假,1为真,循环控制条件为!find(find值为假时, 继续循环),直到满足条件,对fiind赋值为1(表示find为真),退出循环。

函数

函数分为标准库函数自定义函数两类。
函数就相当于零部件,可先单独设计、调试、测试好

函数定义的基本格式:
返回值类型 函数名(类型 形式参数1, 类型 形式参数2,…)←函数头部
{                    —————————–
  声明语句序列                  ↑ 函数体
  可执行语句序列                 ↓
}                    —————————–

函数命名建议使用英文单词及其组合,做到“见名知意”
变量名通常用小写字母开头的单词组合而成。
函数名则用大写字母开头的单词组合而成。

函数体必须用一对花括号包围。(相当于函数体的定界符
函数体内部定义的变量只能在函数体内访问,称为内部变量
函数头部参数表里的变量,称为形式参数。(也是内部变量,只能在函数体内访问)
形参表是函数的入口。形参表里的形参相当于运算的操作数返回值为函数运算的结果

计算整数n的阶乘n!(用函数编写)

//函数功能:用迭代法计算n!
//函数入口参数:整形变量n表示阶乘的阶数
//函数返回值:返回n!的值

long Fact(int n)
{
    int i;
    long result = 1;
    for (i = 2;i <= n;i++)
    {
        result *= i;
    }
    return result;
}

在函数定义的前面写上一段注释来描述函数的功能及其形参,是一个非常好的编程习惯。

函数调用

调用函数必须提供一个实际参数的表达式给被调用的函数。(提供一个变量去执行被调函数中的操作)
调用其他函数的函数被称为主调函数
主调函数把实参的值复制给被调函数的形参的过程,称为参数传递


//调用函数Fact()来计算m!。

#include <stdio.h>
void main()
{
    int m;
    long ret;
    printf("Input m:");
    scanf("%d",&m);
    ret = Fact(m);                 //调用函数Fact(),并将函数的返回值存入ret
    printf("%d! = %ld\n",m,ret);
}


long Fact(int n)
{
    int i;
    long result = 1;
    for (i = 2;i <= n;i++)
    {
        result *= i;
    }
    return result;
}

函数内部最后必须有return来将值返回给主调函数。
函数的返回值只能有一个,类型可以是除数组以外的任何类型
return语句可以有多个,但不代表函数可以有多个返回值。(多个return语句通常出现在if-else语句中,表示在不同的条件下返回不同的值

void类型可以没有return语句,直到运行到函数最后一条语句后再返回。
(return语句直接作为函数的返回值返回给主调函数)

函数原型

main()前声明函数原型long Fact(int n);,否则会警告。(注意有分号)
(如果先定义函数再写主函数程序则不需要声明)

#include <stdio.h>
long Fact(int n);
void main()
{
;
}

long Fact(int n)
{
return …;
}

函数封装与防御性程序设计

外界对函数的影响仅限于几个入口参数。
函数对外界的影响仅限于一个返回值和数组、指针类型的参数。

函数可以拥有遇到不正确使用或非法数据输入时仍能保护自己避免出错的能力。(如上一个阶乘函数输入负值则会有非法输出)
在函数入口处增加对函数参数合法性的检查,为增强程序健壮性的常用方法。
(在程序中增加一些代码,用于专门处理某些异常情况的技术,称为防御性程序设计

所有的控制分支(如if)都应有返回值。

处理非法输入:在函数中控制分支语句中使用return 返回一个值(如return -1),然后在主函数中处理这个情况(if (ret==-1) ,输出 “error”)

#include <stdio.h>
long Fact(int n);
void main()
{
    int m;
    long ret;
    printf("Input m:");
    scanf("%d",&m);
    ret = Fact(m);
    if (ret == -1)
        printf("Input data error!\n");
    else
        printf("%d! = %ld\n",m,ret);
}


long Fact(int n)
{
    int i;
    long result=1;
    if (n<0)
    {
        return -1;
    }
    else
    {
        for (i=1;i<=n;i++)
            result *= i;
        return result;
    }
}

实参的数量必须和形参相等
实参的类型必须和形参匹配

函数设计基本原则

如果某一功能重复实现3遍以上,就应考虑将其写成函数。
(1)函数规模要小,尽量控制在50行以内。
(2)函数功能要单一。
(3)每个函数只有一个入口和一个出口。
(4)在函数接口中清除地定义函数的行为,包括入口函数、出口函数、返回状态、异常处理等。定义好函数接口以后,轻易不要改动。
(5)在函数的入口处,对参数的有效性进行检查。
(6)在执行某些敏感操作(如执行除法、开方、取对数、赋值、函数参数传递等)之前,应检查操作数及其类型的合法性,以避免发生除零、数据溢出、类型转换、类型不匹配等因思维不缜密而引起的错误。
(7)考虑如果调用函数失败,该如何处理。
(8)与屏幕显示无关的函数,通过返回值报告错误。调用函数要检查函数返回值。
 与屏幕显示有关的函数,函数负责相应的错误处理(返回值-1,在主函数中当变量等于-1时,打印错误error)
 错误处理代码一般放在函数末尾,对于某些错误,还要设计专门的错误处理函数。
(9)确保函数的实参类型与形参类型相匹配。
 在程序开头进行函数原型声明,并将函数参数的类型书写完整(无参数时用void声明)。
(10)确保函数中的所有控制分支都有返回值。无返回值时用void声明。

函数的递归调用和递归函数

如果一个对象部分地由它自己组成或按它自己定义,我们称它为递归的。


//用递归方法计算整数n的阶乘n!

#include <stdio.h>
long Fact(int n);
void main()
{
    int n;
    long result;
    printf("Input n:");
    scanf("%d",&n);
    result = Fact(n);
    if (result == -1)
        printf("n<0,data error!\n");
    else
        printf("%d! = %ld\n",n,result);
}

long Fact(int n)
{
    if (n<0)
        return -1;
    else if (n == 0 || n == 1)            //基线情况,即递归终止条件
        return 1;
    else                                  //一般情况
        return (n*Fact(n-1));             //递归调用,利用(n-1)!计算 n!
}

将复杂问题逐步简化并最终转化为一个最简单的问题。
一个递归调用函数必须包含如下两个部分:
(1)由其自身定义的与原始问题类似的更小规模的子问题,它使递归过程持续进行,称为一般情况
(2)递归调用的最简形式,它是一个能够用来结束递归调用过程的条件,通常称为基线情况

自己调用自己,让其循环叠乘。

“在函数内直接或间接地自己调用自己”的函数调用,称为递归调用,这样的函数称为递归函数

变量的作用域和存储类型

被花括号括起来的区域叫做语句块

每个变量尽在定义它的语句块内有效,并且拥有自己的存储空间。

不在任何语句块内定义的变量,称为全局变量。(作用域为整个程序
在其他语句块定义的变量,叫局部变量


//打印出计算Fibonacci数列每一项时所需的递归调用次数

#include <stdio.h>
long Fib(int n);
int count;               //全局变量count用于累计递归函数被调用的次数,自动初始化为0
void main()
{
    int n,i,x;
    printf("Input n:");
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        count = 0;               //计算下一项Fibonacci数列时将计数器count清零
        x = Fib(i);
        printf("Fib(%d) = %d,count = %d\n",i,x,count);
    }
}

long Fib(int n)
{
    count++;                     //累计递归函数被调用的次数,记录于全局变量count中
    if(n == 0)  return 0;
    else if (n == 1)  return 1;
    else return (Fib(n-1)+Fib(n-2));
}

变量的存储类型

变量的存储类型指编译器为变量分配内存的方式。
类型:自动变量、静态变量、外部变量、寄存器变量

如果没有指定变量的存储类型,那么变量的存储类型就缺省为auto(自动变量)

自动变量:
使用最多的变量,在进入语句块时被分配内存,退出语句块时内存被释放。
(也就是说变量仅在所属语句块内有效,在其他语句块就算是同名变量也互不干扰)

静态变量:
保持上一次退出函数前拥有的值。
static定义的变量称为静态变量

static 类型名 变量名;

程序退出时才释放内存。
仅被初始化一次。

#include <stdio.h>
long Func(int n);
void main()
{
    int i,n;
    printf("Input n:");
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        printf("%d! = %ld\n",i,Func(i));
    }
}

long Func(int n)
{
    static long p = 1;          //定义静态局部变量
    p *= n;
    return p;
}

外部变量

在所有函数之外定义的变量没有指定其存储类别,那么它为外部变量
外部变量是全局变量。
如果要在定义点之前或者在其他文件中使用它,那么就需要关键字extern对其进行声明。

extern 类型名 变量名;

寄存器变量

寄存器变量就是用寄存器存储的变量。

register 类型名 变量名;

使用频率较高的变量声明为register,执行速度快。
现代编译器自动把普通变量优化为寄存器变量,所以一般无须特别声明变量为register

数组

三种格式:1.int PI[5];
     2.int PI[]={1,2,3,4,5};
     3.int PI[0]={5};PI[1] = 1;PI[2] = 2;……

数组的下标都是从0开始的。

向函数传递数组时直接用数组名作为函数实参调用。(ReadScore(PI,n))。

二维数组

类型 数组名 [第一维长度][第二维长度]
一维表示一列几个数,二维表示一行几个数。
一维二维合起来锁数时一维二维=几行几列。

冒泡排序

将相邻变量两两比较,之后把较大的数放在后面。

#include <stdio.h>
int main()
{
    int arr[]={13,15,61,81,52,56,89,12,56,14};
    int i,j;

    for(i=0;i<10;i++)                    //先将基本格式输出
    {
        if(i != 9)
            printf("%d, ",arr[i]);
        else
            printf("%d",arr[i]);     
    }

    for(i=8;i>=0;i--)                    //控制每趟比较的最大下标
    {
        for(j=0;j<=i;j++)                //控制每次相邻元素比较的下标
        {
            if(arr[j]>arr[j+1])          //如果左边元素比右边元素大,则交换
            {
                int temp;
                temp = arr[j];
                arr[j]=arr[j+1];
                arr[j+1]=temp;
            }
        }
    }
    printf("\n-----排序之后-----\n");
    for(i=0;i<10;i++)
    {
        if(i != 9)
            printf("%d, ",arr[i]);
        else
            printf("%d",arr[i]);
    }
    return 0;
}

先输出格式(数,数,数),再添内容与操作。

遍历查找

#include <stdio.h>
int getIndex(int arr[5],int value);
void main()
{
    int arr[5]={22,12,19,38,17};
    int value;
    int index;
    scanf("%d",&value);
    index = getIndex(arr,value);
    if(index != -1)
    {
        printf("%d Exist,number:%d\n",value,index);
    }
    else
    {
        printf("%d No exist.\n",value);
    }
}
//查找并返回下标
int getIndex(int arr[5],int value)
{
    int i;
    for(i=0;i<5;i++)
    {
        if(arr[i]==value)
        {
            return i;
        }
    }
    return -1;
}

DP

多阶段决策问题:一个问题可以分为若干个阶段,每个阶段都要做出决策;

状态:每个阶段开始面临的自然状况或客观条件.在上例中每个阶段的状态就是到达当前结点的两个子结点的选择.

面对问题要寻找动态规划的方法,首先要清楚一点,动态规划不是算法,它是一种方法,它是在一件事情发生的过程中寻找最优值的方法,因此,我们需要对这件事情所发生的过程进行考虑。而通常我们从过程的最后一步开始考虑,而不是先考虑过程的开始。

1、构造问题所对应的过程。

   2、思考过程的最后一个步骤,看看有哪些选择情况。

   3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。

   4、使得子问题符合“最优子结构”。

   5、找到边界,考虑边界的各种处理方式。

   6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。

   7、考虑如何做备忘录。

   8、分析所需时间是否满足要求。

   9、写出转移方程式。( 当mineNum = 0且people >= peopleNeeded[mineNum]时 f(people,mineNum) = gold[mineNum]

   当mineNum = 0且people < peopleNeeded[mineNum]时 f(people,mineNum) = 0

   当mineNum != 0时 f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]与f(people, mineNum-1)中的较大者,前两个式子对应动态规划的“边界”,后一个式子对应动态规划的“最优子结构”.

)