zoukankan      html  css  js  c++  java
  • 20160129.CCPP体系详解(0008天)

    程序片段(01):函数.c+call.c+测试.cpp
    内容概要:函数

    ///函数.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.函数:
    //  1.函数的作用:重用代码,重用功能
    //      表象:代码的封装,代码的重用
    //      实质:功能的封装,功能的重用
    int main01(void)
    {
        system("tasklist");
    
        system("pause");
    }
    
    //02.函数的声明与定义浅析:
    //  1.函数的声明的定义的使用时机不同:
    //      函数的声明用于编译时期-->编译检查
    //      函数的定义用于链接时期-->链接检查
    //      函数的真正使用:声明和定义缺一不可
    //  2.由于编译器的不同所导致的差异:
    //      C语言编译器:宽泛(VS2015自动配置了编译器所需要的静态库Lib的目录)
    //          可以既没有声明也没有定义(只是没有显式的在代码中标注,但是
    //          编译器能够自动识别到),编译器当中有两个配置选项(库目录+附加依赖项)
    //          但是,如果编译器当中没有配置这两项(库目录和附加依赖项)就会编译报错
    //          注意:编译时期需要声明,链接时期需要实体
    //      C++语言编译器:严格
    //          必须既有声明也有定义,必须显式的在代码中进行标注
    //          编译时期需要声明,链接时期需要定义
    //  3.在代码当中函数声明和定义出现的时机:
    //      标准做法:函数声明必须出现在函数调用之前
    //      函数声明的位置既可以独立形式出现,也可以出现于函数体内部,但必须出现
    //      在调用之前(CCPP同时支持的规则)
    //  4.关于形参是否存在形参名称的问题:
    //      函数声明的时候可以没有形参名称,
    //      但是,函数实现的时候必须有函数的形参名称
    int getres(int a, int b, int c);//函数的声明
    
    int main01(void)
    {
        //代码重用
        int x = 11, y = 12, z = 13;
        x = x*x*x;
        y = y*y*y;
        z = z*z*z;
        int res = x + y + z;
        res = getres(x, y, z);
        printf("%d 
    ", res);
    
        int a = 10, b = 12, c = 13;
        a = a*a*a;
        b = b*b*b;
        c = c*c*c;
        int res1 = a + b + c;
        res1 = getres(a, b, c);//函数通过代码的重用实现了功能的重用
        printf("res1 = %d 
    ", res1);
    
        system("pause");
    }
    
    //03.为了让程序能够连接成功,在函数进行声明之后就必须进行函数的定义
    //      函数的声明:只是表明函数存在,你可以使用这个函数的名称(表明有,可以形式用)
    //      函数的定义:确切表明函数存在,你可以使用这个函数的本身(确实有,可以实际用)
    int getres(int a, int b, int c)
    {
        return a*a*a + b*b*b + c*c*c;
    }
    ///call.c
    //01.编译器的不同特点测试:
    //  1.VS2015的编译器,默认进行了编译器所需的静态库(LIb)的配置:
    //      因此,虽然没有函数的具体声明,但是C语言程序却可以静态库(Lib)
    //      的配置选项进行函数的定位,最终找到函数实体本身
    //      注:C语言由于默认对静态库(Lib)的配置,因此C语言的编译比较宽泛
    //          有静态库(LIb)的路径,可以自动定义,不以来与函数的确切声明
    //  2.C++的编译器:严格控制
    //      要求必须有函数的声明和定义,才能够打包成为应用程序
    //          编译的时候需要检测函数的声明是否存在?
    //          链接的时候需要检测函数的实现是否存在?
    int main02(void)
    {
        system("calc");//系统库函数,标准库函数
    
        system("pause");
    }
    ///测试.cpp
    #include <stdio.h>
    #include <stdlib.h>
    
    int add(int a, int b);//函数声明
    
    //01.自定义函数的声明和定义特点:
    //  1.刚刚那个是针对于系统函数的声明和定义的区别
    //      自定义函数相对于系统函数而言,就是没有标准库(Lib)而已
    //  2.由于编译器所导致的不同:
    //      C语言编译器特点:
    //          没有函数声明,但是有静态库(Lib)配置-->编译通过
    //          没有函数声明,有定义,且实体在任意位置-->可能编译通过,可能编译不通过
    //              放在调用之前的定义,编译通过
    //              放在调用之后的定义,可能通过,可能不通过
    //                  由于C语言编译器的特点,所以可能检测的出来,也可能检测不出来
    //                  函数的声明和定义都可以缺掉,只要静态库(Lib)中包含,模糊匹配
    //      C++语言编译器:
    //          没有函数声明,但是又静态库(Lib)配置-->编译不通过
    //          没有函数声明,有定义,且实体在调用之前可以调用
    //              要求:函数的声明和定义缺一不可,准确匹配
    int main03(void)
    {
        add(1, 2);
        printf("%d 
    ", add(10, 20));
    
        system("pause");
    }
    
    int add(int a, int b)
    {
        return a + b;
    }

    程序片段(02):C语言函数调用实例.c+函数.c+run.c
    内容概要:函数的分割+函数的划分

    ///C语言函数调用实例.c
    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.函数的使用特点:
    //  1.C语言当中的异常处理函数abort();
    //      (1).用于表明某个位置出现了错误
    //          (提示方式:以windows弹窗作为异常提醒)
    //      (2).函数特点:
    //          只是一个提示,点击弹窗之后,程序依然可以继续执行
    //          不会直接中断整个应用程序(直接表明异常函数abort()的调用)
    //  2.C语言当中的函数特点:
    //      不可以进行函数的嵌套定义!
    //      C++语言当中不允许函数的直接嵌套,但是允许间接的通过
    //          Lambda表达式实现函数的嵌套形式
    int main01(void)
    {
        int a;
        int b;
    
        scanf("%d,%d", &a, &b);
        if (b == 0)
        {
            abort();//处理程序的异常
        }
        printf("%d 
    ", a / b);
    
        //void go()
        //{
    
        //}
    
        system("pause");
    }
    ///函数.c
    #include <stdio.h>
    #include <stdlib.h>//std:表明标准静态库-->跨平台静态库-->C语言标准跨平台静态函数库(Lib)
    #include <Windows.h>//第三方静态库:仅仅适用于Windows的静态函数库
    
    void run(char *path)//外部函数,C语言当中的代码重用(功能重用),主要依赖于函数的使用特点
    {//被调函数
        ShellExecuteA(0, "open", path, 0, 0, 1);//默认窗口打开方式
    }
    
    //01.区分主调函数和被掉函数的概念:
    //  在那个函数代码块儿中写其他函数的调用语句,那么:
    //      那个函数就是所谓的主调函数
    //      其他函数就是所谓的被调函数
    int main02(void)
    {//主调函数
     // run(""C:\Program Files\Tencent\QQ\QQProtect\Bin\QQProtect.exe"");
     // run("C:\Users\yincheng01\AppData\Roaming\baidu\BaiduYun\baiduyun.exe");
    
        system("pause");//库函数,不加头文件,C语言可以,但是为了代码规范,还是要添加上头文件的
    }
    ///run.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.函数的组成元素分析:
    // 函数的声明:int getmax(int a, int b);-->末尾的函数声明结束符(";")分号不允许省略
    //  函数的实现:int getmax(int a, int b){return a > b ? a : b;}-->代码块儿("{}")当中的语句就是函数实现语句
    //  返回值类型:int-->限制函数的返回值最终类型
    //  函数名称:getmax-->实质即使函数指针-->函数存放函数声明的地址-->另外还有函数定义的地址(两个地址不用)
    //      C语言当中goto语句的实现原理就如同汇编语言当中jump原理-->通过反汇编可以区分(函数声明地址和函数实现地址的不同)
    //      C语言当中的应用程序在应用程序被加载进内存之后,就会新建一张函数表(类似于变量表)-->里面记录了函数定义的地址
    //          于是我们就可以通过函数声明的地址找到具体函数定义的地址(这是实现劫持的原理:函数指针)
    //          改变函数指针的指向,可以让其具备不同的行为,以至于没有行为也是通过这个进行控制的
    //      函数声明变量-->存储函数定义(实体变量<==>普通变量)的地址-->所以函数声明变量叫做函数指针(存放地址的变量叫做指针变量)
    //          所以:函数声明变量叫做函数指针
    //  形式参数表:(int a, int b)-->int a,int b代表的就是实际的参数本身
    //  函数执行体:{return a > b ? a : b;}-->函数实体的代码块儿内容
    //  函数返回值:return a > b ? a : b;-->return语句表明函数的具体反回值
    int getmax(int a, int b);//函数的语句块儿不允许声明,所以通过空语句分号(";")进行表示
    
    int getmax(int a, int b)
    {
        //int a;//函数体内部定义变量不可以和形式参数名称重名
        return a > b ? a : b;
    }
    
    int main03(void)
    {
        printf("%p 
    ", getmax);
    
        system("pause");
    }

    程序片段(03):void.c+fun.c
    内容概要:函数的使用和参数

    ///void.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int add(int a, int b);//遵循软件工程规范,在函数调用之前必须明确函数的声明
    
    //01.void类型的使用特点:
    //  1.出现位置的不同,意义不同
    //      返回值类型位置:
    //          表明函数不需要返回值,不用通过return关键字显式的将返回值带出
    //      函数形式参数位:
    //          表明该函数无需参数值,明确函数不需要传入实际参数
    //  2.void的使用注意事项:
    //      可以用来定义指针类型-->void *-->俗称干地址-->没有明确解析方式的地址
    //          -->但是由于地址的大小已经确定(要么4字节|要么8字节)-->编译器决定
    //              所以知晓存储一个地址需要开辟多少个字节
    //          -->因此,指针变量的内存地址开辟成功
    //      不能用来定义变量类型-->void---->因为没有明确类型,没有明确的解析方式
    //          -->不能描述变量所需要开辟的存储空间究竟需要多大?
    //          -->导致开辟普通变量的内存空间失败
    //02.返回值和返回值类型的使用注意事项:
    //  1.返回值的类型要求与返回值的类型保持一致!
    //      如果不一致将会发生数据类型的转换(自动类型转换+自动类型转换)
    //  2.如果返回值的类型采用void描述:
    //      C语言采用其他类型的返回值进行返回,那么编译器不会报错,但是返回的值却可能是不正确的
    //      C++语言采用其他类型的返回值进行返回,那么编译器直接进行报错,说类型的不匹配
    //03.return关键字的作用:
    //  1.返回值:将值从被调函数当中带出
    //  2.中断多层嵌套循环的执行(区分于goto语句的实现特点)
    //  3.中断函数的执行
    //04.所有的函数,默认的返回值类型都是int类型
    //  包括特殊的main函数的默认返回值类型也是int类型
    //  只是函数若是没有明确的声明返回值类型,而进行返回异常的整数
    int main01(void)
    {
        printf("%d 
    ", add(10, 20));
        //void a;//"a":非法使用"void"类型,代表任何类型
    
        return 1;//返回值应该与返回值类型一致
        //如果函数申明为void,却用return返回一个其他类型的值,那么C++编译器报错,由于类型不匹配
        //但是C语言的编译器不会进行报错
    
        system("pause");
    }
    
    add(int a, int b)
    {
        return a + b;
    }
    
    int main02(void)
    {
        getchar();//根据函数调用找到找到函数实体-->函数声明-->函数实体
        getchar();//参数即使为空,函数的调用依然需要添加上小括号("")
    
        system("pause");
    }
    ///fun.c
    #include <stdio.h>
    #include <stdlib.h>
    
    void change(int a)//函数的副本机制:int a-->形式参数的声明
    {//读取寄存器当中的整型值,构建当前函数所需使用的内存变量值
        a = 3;
        printf("&change = %p, change = %d 
    ", &a, a);
    }
    
    int main03(void)
    {
        //主调函数当中传递给被调函数的参数叫做实际参数,简称实参
        change(10);//副本,开辟内存容纳寄存器的的值-->寄存器当中的值可以直接进行读取使用
    
        system("pause");
    }
    
    //01.函数参数的特点:
    //  1.主调函数和被调函数当中的参数是不同的概念:
    //      (1).所处的位置不同:
    //          栈内存不同,不同的函数处于不同的运行时堆栈
    //          所以即使名称相同,也是不同的变量
    //      (2).不可以跨函数访问局部变量
    //          运行时堆栈的不可见特点
    //          上下层(运行时堆栈)当中的局部变量不可以夸堆栈访问
    //  2.主调函数传递给被调函数的实际参数的副本可能的存储位置:
    //      未接收-->寄存器-->缓存器-->未经使用的常量数据
    //      接收了-->栈内存-->存储普通的副本数据,栈内存容得下
    //      接收了-->堆内存-->如果副本数据很大,就必须采用堆内存空间进行存储
    int main04(void)
    {
        int a = 10;
    
        printf("&main = %p, main = %d 
    ", &a ,a);
        change(a);
        printf("%d 
    ", a);
    
        system("pause");
    }

    程序片段(04):输入输出.c+return.c
    内容概要:return与参数

    ///输入输出.c
    #include <stdio.h>
    #include <stdlib.h>
    
    int add(int a, int b)//int a, int b这两个形式参数,只有在被调用的时候,才会涉及到自动分配和自动释放
    {
        printf("add1 a = %d, b = %d 
    ", a, b);
        a = 19;
        b = 29;//修改的是当前被调函数当中的局部变量,也就是主调函数传递进来的实际参数的副本数据
        printf("add2 a = %d, b = %d 
    ", a, b);
    
        return a + b;
    }
    
    //01.在我看来,传值和传址都是一样的:
    //  只不过一个被赋予了普通变量的解析特点->其他
    //  一个呗赋予了指针变量的解析特点而已-->数组
    int main01(void)
    {
        int a = 10;
        int b = 20;
    
        printf("%d 
    ", add(a, b));
        printf("%d 
    ", add(11, 12));//函数的参数除了数组以外,都是副本(区别于指针变量接收,还是普通变量接收)
        printf("main a = %d, b = %d 
    ", a, b);
    
        system("pause");
    }
    
    //02.参数传递特点:
    //  add_debug(1, 2, 3);-->实参太多
    //      C语言参数过多只会发出警告,结果不保证绝对正确,参数刚好合适,能够保证结果正确
    //  add_debug(1);------->实参太少
    //      直接发生变异报错
    //  注:函数参数进栈的顺序是从右往左,提取函数参数的数据是从上往下进行提取的
    //      例如:(int a, int b);
    //          进栈顺序:b--->a
    //      区分:函数形式参数的进栈顺序和函数局部变量的进栈顺序
    //03.函数参数进栈的顺序严格区分:Release环境下进行的测试
    //      函数形式参数的进栈顺序:
    //          数据进栈:从右往左,依次进栈,
    //          数据映射:从左往右
    //          举例:传递数据1, 2, 3
    //              (int a, int b)
    //          数据进栈:           数据映射:
    //              栈底: 3       ->    丢掉|编译器预置数据
    //                      2       ->     b
    //              栈顶:1        ->     a
    //      函数局部变量的进栈顺序:
    //          从下往上-->代码进栈-->扫描局部变量的时候,变量由下往上进行声明的
    int add_debug(int a, int b)
    {
        printf("a = %p, b = %p 
    ", &a, &b);
        printf("a = %d, b = %d 
    ", a, b);
        a = 1;
        b = 2;
        int x = 3;
        int y = 4;
        printf("x = %p, y = %p 
    ", &x, &y);
        printf("x = %d, y = %d 
    ", x, y);
    
        printf("
    ");
    }
    
    int main02(void)
    {
        //printf("%d 
    ", add_debug(1));
        //printf("%d 
    ", add_debug(1, 2));
        //printf("%d 
    ", add_debug(3, 12));
        printf("error = %d 
    ",add_debug(1, 2, 3, 4, 5) );
    
        system("pause");
    }
    
    //04.参数传递的注意事项:
    //  C语言编译器中,主调函数传递给被调函数的实际参数如果过多:
    //      多得数据会被忽略掉,
    //      参数个数如果一致,类型一致,书序一致能够保证结果正确
    //  C语言编译器中,实参和形参的类型要尽量一致,个数也要一致
    //      由于C语言编译器过于宽泛,所以不怎么严格
    int add_test(int a, int b)//int a = 11.0赋值的操作,赋值回自动完成类型转换
    {
        return a + b;
    }
    
    int main03(void)
    {
        printf("%d", add_test(11.9, 2, 3, 5, 10, 12));
    
        system("pause");
    }
    
    //05.小数类型在进行整数的过程当中:
    //      只会进行取整运算,不涉及到四舍五入的情景
    int add_test1(int a, int b)//return也会完成数据类型的转换
    {
        return 13.9;
    }
    
    int main04(void)
    {
        printf("%d 
    ", add_test1(1, 2));
    
        //int a = 10;
        //a + 1 = 9;
    
        system("pause");
    }
    ///return.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.C语言编译器当中的函数特点:
    //      如果函数表明了需要返回值类型,需要返回值
    //      你如果不通过return关键字正确的返回值,那么编译器不会进行报错
    //      但是程序最终的结果不正确结果自负
    int addx()
    {
        return 1;
    }
    
    int main05(void)
    {
        printf("%d 
    ", addx());
    
        system("pause");
    }
    
    //02.函数形式参数和返回值详解:
    //  (1).都存在有副本机制:
    //      副本数据可能的存储位置(寄存器-->缓存器-->栈内存-->堆内存)
    //  (2).都存在数据类型转换:
    //      自动类型转换(小-->大)+强制类型转换(大-->小)
    int getnum()
    {
        int num;
    
        printf("%p 
    ", &num);
    
        num = 10;
    
        return 10.9;//return有副本机制,在寄存器,缓存,内存,堆内存(编译器根据数据特点决定)
        system("notepad");//当前的函数块儿语句,由于处于return关键字之后,所以不会有被执行到的机会
        //int data = num;//副本机制模拟
        //范式副本机制,都会通过赋值,赋值就会发生自动类型转换|强制类型转换特点
    }
    
    void show()
    {
        system("notepad");
    
        return;
    }
    
    //03.结束多层循环的方式特点:
    //  goto:结束多层循环,但是函数并未弹栈(还未出栈)
    //  return:结束多层循环,函数发生弹栈(直接出栈)
    void showx()
    {
        for (int i = 1; i < 100; i++)
        {
            if (i % 13 == 0)
            {
                printf("%d ", i);
                return;//循环内部,结束循环-->区别于goto循环的跳转特点
            }
        }
    }
    
    int main06(void)
    {
        //return;//main函数意味着退出
    
        printf("%d 
    ", getnum());
    
        showx();
    
        system("pause");
    }
    
    //04.&getnum();所涉及到的问题分析:
    //  1.函数返回的副本数据的原本可能存在于:
    //      寄存器-->缓存器-->栈内存-->堆内存
    //  1.getnum();这个函数返回的是一个数据
    //      具体的一个数据,不涉及到变量概念,不涉及到内存概念
    //      所以不能通过取地址符进行操作(数据:是右值,不是左值)
    int main07(void)
    {
        //printf("%d 
    ", &getnum());//不是左值,右值,右值存在于寄存器内部
    
        system("pause");
    }
    
    int add(int a, int b)//add(int a, int b)-->int
    {
        return a + b;
    }
    
    int main08(void)
    {
        printf("%d 
    ", add(add(add(1, 2), 3), 4));
    
        system("pause");
    }

    内容概要(05):过程.c
    内容概要:函数执行过程

    #include <stdio.h>
    #include <stdlib.h>
    
    int main01(void)
    {
        printf("main 上 
    ");
        void print1();//C语言建议添加声明,添加了声明只有一定不会出错,没有声明可能会出错
        print1();
        printf("%d 
    ", add(-1, 0));//参数多了可以编译,但是不能保证结果正确,参数少了不可以编译正确
        printf("main 下 
    ");
    
        system("pause");
    }
    
    int add(int a, int b)
    {
        return a + b;
    }
    
    void print1()
    {
        printf("print1 上 
    ");
        printf("print1 下 
    ");
    }

    程序片段(06):go.c
    内容概要:函数参数的运算顺序

    #include <stdio.h>
    #include <stdlib.h>
    
    void show(int a, int b)
    {
        printf("a = %d, b = %d 
    ", a, b);
    }
    
    int main01(void)
    {
        int a = 5;
        show(a, a++);//6,5
    
        system("pause");
    }
    
    //01.函数形式参数和局部变量特点详解:
    //  1.所以测试环境均为Release环境(标准)
    //  2.分特点详解:
    //      形式参数:
    //          数据进栈顺序:决定实参执行顺序
    //                  从右往左
    //          数据映射顺序:
    //                  从左往右
    //              数据进栈:       数据映射:
    //                  3                丢失(多余)|垃圾(少了)
    //                  2       ->         b
    //                  1       ->         a
    //          数据地址顺序:
    //                  先进栈的形式参数位于高地址
    //                  后进栈的形式参数位于地地址
    //      局部变量:
    //          由于代码是从下往上进行进栈的
    //          所以变量也是从下往上进行压栈的
    //              先压栈的变量位于高地址
    //              后压栈的变量位于地地址
    //          压栈过程当中只是第一次初始化数据
    //              高地址到低地址的压栈过程
    //          压栈之后的执行过程决定最终的数据特点
    //              低地址往高地址的执行特点(正好适应程序的从上往下执行特点)
    int add(int a, int b)
    {
        printf("&a = %d, &b = %d 
    ", &a, &b);
        printf("a = %d, b = %d 
    ", a, b);
        int x = 1;
        int y = 2;
        printf("&x = %d, &y =%d 
    ", &x, &y);
        printf("x = %d, b = %d 
    ", x, y);
        return a + b;
    }
    
    int main02(void)
    {
        //printf("%d 
    ", add(1, 2));
        printf("%d 
    ", add(1, 2, 3));
    
        system("pause");
    }

    程序片段(07):main.c
    内容概要:CodeBlocks测试

    #include <stdio.h>
    #include <stdlib.h>
    
    
    void show(int a, int b)
    {
        printf("a=%d,b=%d", a, b);
    }
    
    int main()
    {//效果一致
        int a = 5;
        show(a, a++);
    
        getchar();
    }
    

    程序片段(08):可变参数.c
    内容概要:可变参数

    #define _CRT_SECURE_NO_WARNINGS
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>//标准参数:模拟可变参数必需的头文件
    
    int add(int num, ...)//...代表可变参数
    {
        int res = 0;//结果
    
        va_list argp;//存储参数开始的地址
        va_start(argp, num);//从首地址开始,读取num后面的数据
        for (int i = 0; i < num; i++)
        {
            res += va_arg(argp, int);//读取一个数据并且按照int类型进行二进制数据的解析
        }
        va_end(argp);//结束读取
    
        return res;
    }
    
    int main01(void)
    {
        printf("%d 
    ", add(3, 1, 2, 3));
        printf("%d 
    ", add(4, 1, 2, 3, 4));
        printf("%d 
    ", add(5, 1, 2, 3, 4, 5));
    
        system("pause");
    }
    
    int main02(void)
    {
        printf("%d, %d, %d 
    ", 1, 2, 3);
        printf("%d, %s, %c, %d 
    ", 1, "123", 'A', 4);
    
        system("pause");
    }
    
    //01.可变参数使用方式一:
    //  1.将第一个参数作为确定可变参数列表当中所存储的参数总个数
    //  2.可变参数使用流程总结:
    //      (1).包含头文件:
    //          #include <stdarg.h>
    //      (2).确定函数声明:
    //          void vatest(int count, ...);
    //      (3).进行参数使用:
    //          va_list argp;//存储可变参数列表的首地址(类似于数组的特点)
    //          va_start(argp, count);//从首地址开始,读取count个参数
    //          va_arg(argp, type);//按照type类型读取当前可变参数列表当中读取到的位置所在的数据
    //          va_end(argp);//结束可变参数列表的读取状态
    void go(int num, ...)
    {
        va_list argp;//存储可变参数列表开始的首地址
        va_start(argp, num);//从首地址开始,读取num个的数据
        for (int i = 0; i < num; i++)
        {
            char str[50];
            //sprintf(str, "%s", va_arg(argp, char *));
            //system(str);//只要调用一次va_arg就从可变参数列表当中读取一个参数
            system(va_arg(argp,char *));
            //读取一个二进制数据并且按照char *类型解析
        }
        va_end(argp);//结束读取
    }
    
    int main03(void)
    {
        go(3, "notepad", "calc", "tasklist & pause");
    
        system("pause");
    }
    
    //02.可变参数的使用方式二:
    //  1.不采用可变参数前置参数作为读取结束条件,而是按照可变参数列表的结束特点进行读取
    //  2.不定长可变参数列表的使用特点:
    //      (1).引入头文件:
    //          #include <stdarg.h>//支持可变参数列表的使用
    //      (2).函数声明特点:
    //          void vatest(int start, ...);//可变参数列表的函数声明
    //          va_list argp;//存储可变参数列表的首地址(类似于数组原理)
    //          vastart(argp, start);//暂定可变参数列表当中参数的读取个数
    //          int argvalue = start;//确定可变参数列表的首个参数
    //          do
    //          {
    //              int value=argvalue;//使用可变参数列表当中的数据
    //              argvalue = va_arg(argp, int);//不断的按照指定类型进行读取
    //          }while(argvaue != -1);
    //          va_end(argp);结束读取
    void showint(int start, ...)
    {
        va_list argp;//存储参数开始的地址
        va_start(argp, start);//从首地址开始读取数据,暂定为读取start个数据
        int argvalue = start;//第一步初始化
        do
        {
            printf("
     %d", argvalue);
            argvalue = va_arg(argp, int);//不断读取
        } while (argvalue != -1);
        va_end(argp);//结束读取
    }
    
    int main04(void)
    {
        //showint(1, 2, 3, 4, -1);
        showint(1, 2, 3, 4, 5, -1);
    
        system("pause");
    }           

    程序片段(09):C声明.c+函数声明.c+int.c+全局与局部冲突
    内容概要:C语言函数声明+全局变量与局部变量

    ///C声明.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.使用函数的特点:
    //  1.()用于对函数进行标识
    //  2.进行函数调用必须明确调用的类型:
    //      区分变量访问和函数调用("()")
    //02.C语言的编译器特点:
    //  1.由于VC2015这个编译器当中自动包含了库目录和附加依赖项
    //      所以使用C语言函数的时候,可以没有声明语句,因为C语言编译器
    //      VC2015会自动到静态库目录和附加依赖项当中去进行查找,
    //      自动查找所需调用的函数,参数多了,少了都可以进行调用(前期,可以;后期,不可以)
    //  2.函数调用触发了C语言编译器VC2015的自动定位功能
    //03.库函数的查找:
    //  分为系统库函数和定义库函数的查找
    //      C语言编译器VC2015支持自动查找
    //      C++编译器不支持自动查找
    int main01(void)
    {
        printf("Hello China! 
    ");//()是个函数
        printf;//引用函数必须要进行声明
    
        system("pause");
    }
    
    int main02(void)
    {
        add(2, 3);
        print();
        //add;
    
        system("pause");
    }
    
    int print(){}
    
    int add(int a, int b)
    {
        return a + b;
    }
    ///函数声明.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.C语言当中的声明和定义特点:
    //  1.声明可以有多个,但是定义只能有一个
    //  2.函数声明的时候可以不用指明形参的名称
    //      但是定义的时候必须指定形参的名称
    //      并且函数声明的形参名称和函数实现的形参名称可以不同
    //  3.函数的声明和定义与变量的声明和定义类似
    int add(int a, int b);//声明
    int add(int x, int y);//声明
    int add(int h, int j);//声明
    int add(int k, int l);//声明
    
    int main03(void)
    {
        printf("%d", add(1, 2));
    
        system("pause");
    }
    
    int add(int a, int b)
    {
        return a + b;//函数的定义
    }
    
    //int add(int a, int b)
    //{//声明可以有多个,但是定义只能有一个
    //  return a + b;//函数的定义
    //}
    ///int.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //全局变量:int a = 10;
    //int a;//全局变量当做声明看待,如果没有初始化,将会被编译器默认赋予0
    int a;
    //int a = 9;//int a;//全局变量声明,int a = 10;//全局变量定义,声明可以有多个,定义只能有一个
    
    int main04(void)
    {
        printf("%d 
    ", a);
    
        system("pause");
    }
    
    //01.局部变量和全局变量的使用总结:
    //  1.是否具备声明和定义之间的区别:
    //      函数和全局变量都有区别
    //      局部变量没有区别(都当做定义来对待)
    //  2.全局变量的生命周期:
    //      程序代码一旦加载进代码区就已经存在了
    //      全局变量优先于main函数的存在
    //  3.全局变量的作用域:
    //      从当前文件的定义位置开始,到跨文件的范围
    //      内都可以进行访问的到
    int main05(void)
    {
        //局部变量没有声明和定义的区别
        //int a = 10;//变量重名,局部变量
    
        //int a;
        ////int a = 10;//局部变量
        //int a;
        //int a;
        //int a;
    
        a = 9;
    
        system("pause");
    }
    
    void go()
    {
        a = 11;
    }
    ///全局与局部冲突.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.全局变量和局部变量内容总结:
    //  1.全局变量很容易被局部变量覆盖
    //  2.全局变量可以被多个函数所共享,方便于读写操作
    //  3.全局变量在如果只是进行声明了,但是没有被定义
    //      那么系统会为其定制一个默认的初始化值0
    //  4.全局变量可以在跨文件的情况下进行调用:
    //      容易出现全局变量重合(类型相同,名称相同)      
    //  5.局部变量和全局变量重名的情况之下,会覆盖掉
    //      全局变量
    //  6.当局部代码块儿当中存在和全局变量相同的变量
    //      那么局部代码块儿的操作将屏蔽对全局变量的操作
    //      相当于对全局变量的操作无效
    int a;
    int a;
    int a = 3;
    
    int main06(void)
    {
        printf("%d 
    ", a);
    
        system("pause");
    }
    
    int main07(void)
    {
        int a = 10;
        printf("%d 
    ", a);//局部变量覆盖全局变量,重名
        {
            printf("%d 
    ", a);
            int a = 13;
            printf("%d 
    ", a);//内部块儿语句会屏蔽外部变量
        }
        printf("%d 
    ", a);//局部变量覆盖全局变量,重名
    
        system("pause");
    }

    程序片段(10):test.cpp
    内容概要:声明与定义差别

    #include <stdio.h>
    #include <stdlib.h>
    
    //01.函数的声明和定义详解:
    //  1.函数的声明可以有多个,定义只能有一个
    //  2.函数的声明可以没有参数名称,但是必须有参数类型
    //  3.函数声明的参数名称可以和函数的定义的参数名称不一致
    //      但是要求类型必须一一对应
    
    int add(int a, int b);//声明要与定义相匹配
    int add(int x, int y);//声明的变量名可以省略,可以和定义的变量名不一致,但是要求类型必须一致
    
    int add(int a, int b)
    {
        return a + b;
    }
    
    int main01(void)
    {
        add(1, 2);
    
        system("pause");
    }

    程序片段(11):baidu.c+stack.c
    内容概要:函数调用流程简单递归

    ///baidu.c
    #include <Windows.h>
    
    //01.动态库(Dll)知识+递归调用知识:
    //  (1).Dll注入技术可以让任何程序挂掉
    //  (2).针对于像360这样的安全软件
    //      需要采用sys层面的技术进行破坏
    //      因为360安全卫士是基于驱动层面开发
    //  (3).如何导出动态库(Dll)?
    //      1).在原始函数声明之前添加
    //          _declspec(dllexport)
    //      2).配置项目属性(配置类型)
    //          动态库(.dll)
    _declspec(dllexport) void go()
    {
        Sleep(1);
        go();
    }
    ///stack.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //线性递归001:将一个整数进行逆序输出
    //  递归函数的规律总结:
    //      是否需要返回值?
    //          如果有累变(加,减,乘,除)效果,就需要返回值类型,否则一般情况之下是不需要返回值类型的
    //      是否需要形式参数?
    //          如果涉及到递归函数当中每层递归函数调用的数据使用,只是数值意义上的使用,就需形式参数
    //              数据使用等同于数据关联,等同于递归函数调用层当中的数据传递,形参变量数据传递
    //          是否需要类似于for循环结构的循环初始化条件?如果有,就需要形式参数,如果没有,则无需
    //      是否逐渐逼近类似于for循环的循环终止条件?
    //          递归入口+递归出口
    //      是否涉及到数据的打印显示顺序?
    //          打印语句如果需要顺序,就写于递归调用之前;
    //          打印语句如果需要逆序,就谢宇递归调用之后.
    void revInt(unsigned int value)//类似于for循环的循环初始化条件
    {
        unsigned int quotient = value / 10;//空间复杂度1+时间复杂度1
        if (quotient != 0)//类似于for循环的循环判断条件
            revInt(quotient);//类似于重复一次for循环的循环执行体
        putchar(value % 10 + '0');//打印顺序为逆序(由于运行时堆栈的即时打印特点决定)-->这样打印的原因是因为跨平台性可移植性比较好!
    }
    
    //线性递归002:输入9,就顺序|逆序打印从1~9之间的整数
    void printInt1(int value)
    {
        //putchar(value + '0');//逆序打印
        if (value - 1 > 0)//时间复杂度2
            printInt1(value - 1);
        putchar(value + '0');//顺序打印
    }
    
    //线性递归003:打印任意一个区间[value1,value2]之间的整数
    //  要求一:(顺序|逆序)
    //  要求二:从value1-->value2|value2-->value1
    void printInt2(int value1, int value2)
    {
        printf("%d 
    ", value1);
        if ((value1 + 1) <= value2)
            printInt2(value1 + 1, value2);
    }
    
    //线性递归004:打印字符串当中的每一个字符(反转效果)
    //  1.严格区分字符数组和字符指针数组之间的区别
    //  2.putchar();和printf();函数之间的区别
    //      putchar();不具备处理字符指针所指向的实体的作用
    //      printf();具备处理字符串指针所指向的实体的作用
    //  3.putchar();每次只会打印单个字符,所有的字符拼装在
    //      一起之后,就是一个字符串
    //  4.putchar();遇到字符就直接打印字符本身,不会出现变故
    //      放在括号内与括号外是有区别的(是否具备判断效果)
    //      决定最后一次打印的特点
    void printStr(char *str)//类似于for循环的循环初始化条件
    {
        if (*str)//类似于for循环的循环判断条件
        {
            //printStr(str + 1);//类似于for循环的循环趋于结束的条件
            printStr(++str);//简化形式
            putchar(*str);//类似于for循环的循环执行体-->putchar();不具备处理字符指针所指向的实体的作用
        }//如果不将putchar(*str);放在括号的内部,那么最后一层递归函数在进行打印的时候会将NUT|('')|0给打印出来
        //也就是最终多打印了一个不可见字符
    }
    
    //线性递归005:打印任意一个整数的阶乘结果
    unsigned int calFact1(unsigned int value)//int表明递归函数的一层函数调用就能返回该阶乘结果,unsigned int num表明类似于for循环的循环初始化条件,或者说要做一件事情,直接所需的参数
    {//如同:我要求取某个数的阶乘,你就得给我这个数据,我就根据这个数据算出一个阶乘结果反馈给你
        if (0 == value || 1 == value)//类似于无限循环的结束条件,也就是递归函数的出口,结束最后一层递归函数的调用,不用再进行递归调用,而且不用再执行最后一层递归函数剩余的语句(直接出结果)
            return 1;//由于return关键字的特殊性,所以最后一层递归函数的执行依赖于它-->return直接终止函数,所以不会在执行一层递归调用以及一层递归调用之后的语句
        calFact1(value - 1);//让无限循环不断的执行下去,至于循环的终止条件我们无需关注,因为上面一段儿已经决定了
        return value * calFact1(value - 1);//对于该行语句,不用关注其执行流程,只需关注,value=value*value!,只是用于一次递归函数的调用就能完成意向功能,剩余递推关系让计算机去做,我们不关注
    }
    
    unsigned int calFact2(unsigned int value)
    {
        if (0 == value || 1 == value)
            return 1;
        else//原理:一次求解,绝对有结果,至于结果的递推关系我们无需去关注,只需要关注的是一次递归函数的调用到底能够完成什么样儿的功能,至于如何递推,如果计算,那都是计算机的事情
            return value * calFact2(value - 1);
    }
    
    //线性递归006:将一个正整数转化为其的二进制表现形式打印出来
    void printIntToBin1(unsigned long value)//使用long类型意味着更好的程序跨平台性(可移植性)-->不像int类型(16位占用2个字节(short),32位以上占用4个字节(long))-->long始终占用4个字节
    {
        unsigned long quotient = value / 2;//空间复杂度1+时间复杂度1
        if (0 != quotient)
            printIntToBin1(quotient);//不断的执行打印除以2之后的余数(二进制位)
        putchar((value % 2) + '0');//余数逆置,一次递归调用意味着逆序打印一个二进制位,即使商为0,也需要打印出这个商为0情况之下的余数0
    }
    
    void printIntToBin2(unsigned long value)
    {
        unsigned long remainder = value % 2;
        if (value / 2 > 0)//时间复杂度2
            printIntToBin2(value / 2);
        //putchar(0 + i);
        putchar(remainder ? '1' : '0');
    }
    
    //线性递归007:循环转递归剖析
    //  1.任何一个循环都可以转换为递归
    //  2.任何一个递归都可以转化为循环+栈
    void loopToRecursion(long value)
    {
        printf("%d, %p 
    ", value, &value);
        if (value < 9)
            loopToRecursion(value + 1);
    }
    
    //01.递归的分类:
    //  1.函数调用方式:
    //      直接调用自己-->直接递归-->简单递归
    //      间接调用自己-->间接递归-->复杂递归
    //  2数据结构模型:
    //      线性递归:f(n)=f(n-1)+n;
    //      树状递归:f(n)=f(n-1)+f(n-2);
    //      图状递归:
    //02.递归的要点:
    //  1.递归的满足要点:
    //      递归入口+递归出口
    //  2.递归的函数要点:
    //      运行时堆栈
    //03.所涉及到的知识点:
    //  任何一个(0~1)之间的整数加上一个('0');
    //  那么该表达式所获得的最终结果就是该
    //  整数所对应的ASCII码值
    int main01(void)
    {
        //revInt(1234);
    
        //printInt1(9);
    
        //printInt2(-55, 55);
    
        //printStr("123456789");
        //char str[10] = "123456789";//区分字符数组和字符指针数组
        //printStr(str);
    
        //printf("%d 
    ", calFact1(10));
        //printf("%d 
    ", calFact2(10));
    
        //printIntToBin1(106);
        //printIntToBin2(106);
    
        //loopToRecursion(0);
    
        system("pause");
    }
    
    
    //04.采用无线循环打印一段儿字符串
    int main02(void)
    {
        //system("notepad");//同步函数,一次只打开一个记事本,需要等待用户结束这个记事本在往下执行
        printf("12345");
        main02();
    }
    
    //05.输入一个整数,就打印整数个字符串
    void intPrintStr(char *str, unsigned long value)
    {
        if (0 == value)
            return;
        if (value - 1 > 0)
            intPrintStr(str, --value);
        printf("%s 
    ", str);
    }
    
    //06.控制Notepad的执行次数
    void printNontepad(unsigned long value)//void:只打印数据,不需要返回 value:for循环的初始化条件
    {
        if (0 == value)//value:判断for循环是否启用循环执行体
        {
            return;
        }
        else
        {//5->4->3->2->1:五个映射对-->执行五次
            system("notepad");//这句话放在前面还是后面都是一样的
            printNontepad(value - 1);//重复执行一次回退的递归循环层
        }
    }
    
    //07.输入任意一个正整数N,用递归实现从1~N之间的累加
    unsigned long addNum(unsigned long value)
    {
        if (1 == value)
            return 1;
        return addNum(value - 1) + value;
    }
    
    int main03(void)
    {
        //intPrintStr("notepad", 5);
    
        //printNontepad(5);
    
        //printf("%d 
    ", addNum(100));
    
        system("pause");
    }

    程序片段(12):线性递归.c+树状递归.c+汉诺塔.c
    内容概要:递归

    ///线性递归.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.递归运算常用解析思想:
    //  1.数学归纳法
    //  2.例如:对等差数列的描述
    //      f(0)=0;
    //      f(n)=f(n-1)+x;
    //      注:关于关系式的递归推导我们不用关心,因为计算机内部自己会去进行推导
    //          记住!计算机最大的用处不是思考,而是计算
    //02.main函数的特点:
    //  如果为main函数定义一个int类型的变量,
    //      那么编译器会自动为该int类型的变量默认初始化一个1(默认初始化)
    //      至于手动初始化需要通过命令行对程序进行启动
    void main01(void)
    {
        main01();//通过递归实现的死循环
    }
    
    
    //03.通过递归模拟循环实现循环次数的控制:
    void loopToRecursion(unsigned int value)
    {
        if (value >= 5)
            return;
        else
            loopToRecursion(value + 1);
        system("notepad");
    }
    
    //04.求取0到任何一个正整数的之间的所有正整数的和?
    //  f(100)=100+f(99);
    //  f(100)=100+99+f(98);
    //  f(100)=100+99+98+f(97);
    //  f(n)=n+f(n-1);
    unsigned long countInt(unsigned long value)
    {
        if (value == 1)
            return 1;
        return value + countInt(value - 1);
    }
    
    void uLongToBin(unsigned long value)
    {
        unsigned long   quotient = value / 2;
        if (quotient != 0)
            uLongToBin(quotient);
        putchar(value % 2 ? '1' : '0');
    }
    
    int main02(void)
    {
        //loopToRecursion(0);
    
        //printf("%lu 
    ", countInt(100));
    
        uLongToBin(100);
    
        system("pause");
    }
    ///树状递归.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //斐波那契数列:
    //  实际问题:
    //      1对兔子,2个月以后可以生育,从可以开始生育之后,每个月都能生育一对兔子
    //  数学描述:
    //      1------->1
    //      2------->1
    //      3------->2
    //      4------->3
    //      5------->5
    //      6------->8
    //      f(n)=f(n-2)+f(n-1);
    //  数学描述的特点:
    //      f(n-2):
    //          描述的是这个月较上个月能够多增加的兔子数目
    //          相差2的原因是因为一对兔子只有相隔两个月才具备生育一对兔子的能力;
    //              也就才会生育一对兔子
    //      f(n-1):
    //          描述的是上个月所有的兔子总计数目
    //      f(n):
    //          本月总共的兔子数目
    unsigned long countRabbit1(unsigned long month)
    {
        if (1 == month || 2 == month)
            return 1;
        return countRabbit1(month - 2) + countRabbit1(month - 1);
    }
    
    void countRabbit2(unsigned long month)
    {
        if (1 == month || 2 == month)
            printf("1 
    ");
        else
        {
            int f1 = 1;
            int f2 = 2;
            int f3 = f1 + f2;
            for (int i = 3; i < month; i++)
            {//通过循环轮替的方式向前进行推进计算(计算机处理计算问题)
                f3 = f1 + f2;
                f1 = f2;
                f2 = f3;
            }
            printf("f3= %lu 
    ", f3);
        }
    }
    
    
    //02.树状递归内容总结:
    //  1.树状递归速度很慢,递归很慢,函数的调用和返回都需要时间
    //  2.任何递归都可以转换为循环加栈
    //      递归=循环+栈
    int main01(void)
    {
        printf("%lu 
    ", countRabbit1(40));
    
        countRabbit2(40);
    
        system("pause");
    }
    ///汉诺塔.c
    #include <stdio.h>
    #include <stdlib.h>
    
    //01.递归解决问题的思想:
    //      1.明确需要解决的问题是什么?
    //          确定递归函数的声明
    //      2.明确解决问题的重复步骤是什么?
    //          确定递归函数的实现
    //      3.明确递归的入口和出口条件?
    //          什么时候开始递归;
    //          什么时候结束递归
    void hanoiTower(unsigned long num, char x, char y, char z)//类似于for循环的循环初始化条件
    {//只需要打印结果,不需要累变特点-->void;函数的实际意义-->hannoTower(unsigned long num, char x, char y, char z);
        if (1 == num)
        {//类似于for循环的循环判断条件-->递归终止继续执行的条件
            //printf("%c-->%c 
    ", 'A', 'C');//直接搬动
            printf("%c-->%c 
    ", x, z);
            return;
        }//递归状态时刻被保留与堆栈当中-->当前函数在执行时所能访问的内容只有运行时堆栈当中的内容
        //类似于for循环的循环执行体内容-->通用问题化解方式
        hanoiTower(num - 1, x, z, y);//A-->B                                                   
        printf("%c-->%c 
    ", x, z);//A-->C
        hanoiTower(num - 1, y, x, z);//B-->C
    }
    
    int main04(void)
    {
        hanoiTower(4, 'A', 'B', 'C');
    
        system("pause");
    }
  • 相关阅读:
    Backbone Events 源码笔记
    IIS8集成模式下打开静态资源被aspx处理程序处理,StaticFileModule失效问题分析
    Orchard 与 ABP架构比较 (aspnetboilerplate)
    Orchard EventBus 事件总线及 IEventHandler作用
    Orchard 事件通知小坑
    推荐一个国内编程语言排名网站
    po_文件格式[转]
    关于poedit打开po文件乱码的问题
    JAVA和.NET工作流相关项目收集
    php里的二进制安全
  • 原文地址:https://www.cnblogs.com/new0801/p/6176832.html
Copyright © 2011-2022 走看看