学习4-8期视频笔记
1、 形参与实参
//函数调用前,形参,也就是函数定义时()中的参数,值都不确定
//不确定的值,不会分配内存,只有调用函数的时候,才会新建一个变量
//接受实际参数的值,当函数调用结束时候,形参占据的内存会被回收
//实际参数是函数调用的时候,函数传递过来的确切值就是实际参数
//实际参数可以是常量,变量或者表达式
//形参与实参内存地址不同,占用不同的内存空间
#include<stdio.h>
#include<stdlib.h>
void go(int num) //int num 是一个形参,只有调用的时候,才会分配一个内存,函数调用的时候,形/参分配内存,新建一个变量,存储传递过来的实参
{
num = 100;
printf("\ngo=%d %x", num,&num);
}
void main()
{
int num = 10;
printf("num=%d %x", num,&num); //num=10
go(num);//num=100
go(3+5); //num=100
printf("\n num=%d %x", num, &num);//num=10
system("pause");
}
实参给形参赋值,会自动进行数据据类型转换
2、 局部变量与全局变量
#include<stdio.h>
#include<stdlib.h>
inty=40;//全局变量,不属于任何一个函数,可被任何一个函数调用
//局部变量调用完成,会被回收
void go()
{
int x = 100; //x为局部变量,只可以在该函数内使用
printf("\nx=%d %x", x, &x);
}
void main()
{
go();
//printf("num=%d%x", x, &x); //因为x为局部变量,这里不能输出x的地址和数值
system("pause");
}
变量是全局变量还是局部变量,看变量声明的位置
同名情况,局部变量会屏蔽全局变量(后来者居上),c++中可以通过(::+变量名) 访问全局变量
块语句内部变量,作用域是整个块
全局变量生存周期很长,一直占用内存,除非程序退出
缺点:容易与局部变量重名,很容易被屏蔽,失效。值容易被修改(注入)
注意:1、名字要容易理解,尽量不与局部变量重名
2、 避免占用内存较大的变量使用全局变量,节约内存
3、 避免全局变量被错误的修改
正规的软件工程,写一个函数要修改全局变量,一定要注释(哪块修改,修改为什么值)
全局变量没有初始化,会默认为0
3、 函数的声明和定义
同名定义只可以有一个,但是声明可以有多个。
函数在调用之后,c可以不声明,c++必须声明
c语言编译比较宽泛,有库的路径,可以自动定位,不需要函数的生命;
c++比较严谨,必须声明函数声明、库文件、头文件。
c语言,没有函数声明,有库可以调用;没有函数声明有函数实体,可以调用
c++,没有函数声明,有库不可以调用;没有函数声明有函数实体,实体在调用前可以调用,在调用后不可以调用
全局变量可以有多个(三个int a;但是不能有赋值),这时候系统会将其当作声明处理。
4、 函数
(1)函数定义在程序中是平行的,即不允许在一个函数内部再定义一个函数;
(2)函数名是用户自定义标识符,当函数值为整型时类型名可省略(不推荐省略);当函数不需要向调用处返回值时,使用void类型名
(3)形参表中的形参是用户自定义标识符,没有参数时,圆括号不能省略,此时函数为无参函数
#include<stdio.h>
#include<stdlib.h>
int add(int a,int b) //这里int a,int b只有调用的时候才会分配内存,函数运行完后会自动释放
{
a = 1;
b = 2;
return a + b;
}
void main()
{
int a = 10;
int b = 20;
printf("%d", add(a, b));//函数参数除了数组以外,其他都是副本
//c语言参数过多会警告,但会忽略多的,结果也不保证正确
system("pause");
}
//结果为3
注意:(1)如果函数类型为void以外的任何一种类型,则函数内部必须出现return语句
(2)return语句中表达式的类型应与函数值类型一致。若不一致,以函数值类型为准
函数调用不能取地址
5、 函数参数读取顺序
#include<stdio.h>
#include<stdlib.h>
void show (int a,int b)
{
printf(“a=%d,b=%d”,a,b)
}
void main()
{
inta=5;
show(a,a++);
}
//结果为a=6,b=5
//因为栈为先进后出,c语言是从下往上,从后往前压入栈中(这样才能保证顺序执行),所以这个调用函数内部是从右往左执行
6、 可变参数的函数
//第一个参数为总个数
//加法
#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);
}
void main()
{
printf("%d\n", add(3, 1, 2,3));//第一个值为后面参数的个数,后面为具体数值
printf("%d\n", add(4, 2, 2,3,5));
printf("%d\n", add(5, 1, 2,3,4,5));
system("pause");
}
//运行多个程序
#define_CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
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*));//读取一个数据按照int型解析
system(str);
}
va_end(argp);//结束读取
}
void main()
{
go(3, "notepad","calc", "tasklist");
system("pause");
}
//未知个数的输出
//以-1结尾,表示输出结束
void showint(int num, ...)
{
va_list argp;//存储参数开始的地址
va_start(argp, num);//从首地址开始,读取num后面的数据
int argvalue = num;
do
{
printf("%d", argvalue);
argvalue=va_arg(argp, int);//读取一个数据按照int型解析
} while (argvalue != -1);
va_end(argp);//结束读取
}
void main()
{
showint(1, 2, 3, 4, -1);
system("pause");
}
7、 递归
//打开notepad
void main()
{
system("<strong>start</strong> notepad");//并行打开无数个notepad
main();
}
//通过给目标程序注入dll使程序栈溢出
#include<Windows.h>
_declspec(dllexport) void go()
{
Sleep(1);
go();
}
//叠加从0-num
int add(int num)
{
if(num==0)
{printf(“%d”,sum)
}
else{returnnum+add(num-1)}
}
例子
树状递归 f(n)=f(n-1)+f(n-2)
线性递归 f(n)=f(n-1)+n
树状递归速度很慢,递归很慢,函数调用返回都需要时间(进栈出栈)
递归=循环+栈
int getn(int num)//递归
{
if(num==1||num==2)
{return1;}
else{returngetn(num-1)+getn(num-2);}
}
int getN(int num){ //循环
if(num==1||num==2)return 1;
else{
intf1=1;
intf2=1;
intf3;
for(inti=3;i<=num;i++){
f3=f1+f2;
f1=f2;
f2=f3;
}
}
returnf3;
}
8、 函数参数的生存周期
函数返回值有副本机制,返回时会另保存一份
printf(“%d”,add(1,2))l//这里打印的就是副本,内存中的数据就会被销毁
返回值(临时变量)存储在寄存器,原数据被销毁,返回副本数据(寄存器中的数据)
返回值如果是全局变量,则不会被回收