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 \n", 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 \n", 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 \n", 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 \n", 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 \n", 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 \n", 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 \n", &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 \n", &a ,a);
change(a);
printf("%d \n", 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 \n", a, b);
a = 19;
b = 29;//修改的是当前被调函数当中的局部变量,也就是主调函数传递进来的实际参数的副本数据
printf("add2 a = %d, b = %d \n", a, b);
return a + b;
}
//01.在我看来,传值和传址都是一样的:
// 只不过一个被赋予了普通变量的解析特点->其他
// 一个呗赋予了指针变量的解析特点而已-->数组
int main01(void)
{
int a = 10;
int b = 20;
printf("%d \n", add(a, b));
printf("%d \n", add(11, 12));//函数的参数除了数组以外,都是副本(区别于指针变量接收,还是普通变量接收)
printf("main a = %d, b = %d \n", 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 \n", &a, &b);
printf("a = %d, b = %d \n", a, b);
a = 1;
b = 2;
int x = 3;
int y = 4;
printf("x = %p, y = %p \n", &x, &y);
printf("x = %d, y = %d \n", x, y);
printf("\n");
}
int main02(void)
{
//printf("%d \n", add_debug(1));
//printf("%d \n", add_debug(1, 2));
//printf("%d \n", add_debug(3, 12));
printf("error = %d \n",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 \n", 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 \n", addx());
system("pause");
}
//02.函数形式参数和返回值详解:
// (1).都存在有副本机制:
// 副本数据可能的存储位置(寄存器-->缓存器-->栈内存-->堆内存)
// (2).都存在数据类型转换:
// 自动类型转换(小-->大)+强制类型转换(大-->小)
int getnum()
{
int num;
printf("%p \n", &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 \n", getnum());
showx();
system("pause");
}
//04.&getnum();所涉及到的问题分析:
// 1.函数返回的副本数据的原本可能存在于:
// 寄存器-->缓存器-->栈内存-->堆内存
// 1.getnum();这个函数返回的是一个数据
// 具体的一个数据,不涉及到变量概念,不涉及到内存概念
// 所以不能通过取地址符进行操作(数据:是右值,不是左值)
int main07(void)
{
//printf("%d \n", &getnum());//不是左值,右值,右值存在于寄存器内部
system("pause");
}
int add(int a, int b)//add(int a, int b)-->int
{
return a + b;
}
int main08(void)
{
printf("%d \n", add(add(add(1, 2), 3), 4));
system("pause");
}
内容概要(05):过程.c
内容概要:函数执行过程
#include <stdio.h>
#include <stdlib.h>
int main01(void)
{
printf("main 上 \n");
void print1();//C语言建议添加声明,添加了声明只有一定不会出错,没有声明可能会出错
print1();
printf("%d \n", add(-1, 0));//参数多了可以编译,但是不能保证结果正确,参数少了不可以编译正确
printf("main 下 \n");
system("pause");
}
int add(int a, int b)
{
return a + b;
}
void print1()
{
printf("print1 上 \n");
printf("print1 下 \n");
}
程序片段(06):go.c
内容概要:函数参数的运算顺序
#include <stdio.h>
#include <stdlib.h>
void show(int a, int b)
{
printf("a = %d, b = %d \n", 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 \n", &a, &b);
printf("a = %d, b = %d \n", a, b);
int x = 1;
int y = 2;
printf("&x = %d, &y =%d \n", &x, &y);
printf("x = %d, b = %d \n", x, y);
return a + b;
}
int main02(void)
{
//printf("%d \n", add(1, 2));
printf("%d \n", 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 \n", add(3, 1, 2, 3));
printf("%d \n", add(4, 1, 2, 3, 4));
printf("%d \n", add(5, 1, 2, 3, 4, 5));
system("pause");
}
int main02(void)
{
printf("%d, %d, %d \n", 1, 2, 3);
printf("%d, %s, %c, %d \n", 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("\n %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! \n");//()是个函数
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 \n", 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 \n", a);
system("pause");
}
int main07(void)
{
int a = 10;
printf("%d \n", a);//局部变量覆盖全局变量,重名
{
printf("%d \n", a);
int a = 13;
printf("%d \n", a);//内部块儿语句会屏蔽外部变量
}
printf("%d \n", 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 \n", 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')|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 \n", 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 \n", calFact1(10));
//printf("%d \n", 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 \n", 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 \n", 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 \n", 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 \n");
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 \n", f3);
}
}
//02.树状递归内容总结:
// 1.树状递归速度很慢,递归很慢,函数的调用和返回都需要时间
// 2.任何递归都可以转换为循环加栈
// 递归=循环+栈
int main01(void)
{
printf("%lu \n", 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 \n", 'A', 'C');//直接搬动
printf("%c-->%c \n", x, z);
return;
}//递归状态时刻被保留与堆栈当中-->当前函数在执行时所能访问的内容只有运行时堆栈当中的内容
//类似于for循环的循环执行体内容-->通用问题化解方式
hanoiTower(num - 1, x, z, y);//A-->B
printf("%c-->%c \n", x, z);//A-->C
hanoiTower(num - 1, y, x, z);//B-->C
}
int main04(void)
{
hanoiTower(4, 'A', 'B', 'C');
system("pause");
}