C3 函数与变量 第56章

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 126

第5,6章 函数和变量

计算机与信息技术学院
信息科学研究所
赵帅锋
shfzhao@bjtu.edu.cn
例子:产生100随机数[0-999]
#include <iostream>
#include <cstdlib> 需要掌握库函数
#include <iomanip> 否则写不出程序
int main()
{
for( int i=0; i<100;i++ )
{
cout << setw(4) << rand()%1000;
if( i%10 == 9 )
{
cout << endl;
}
}
return 0;
}

结果。。。每次都相同。
并不“随机!”

shfzhao@bjtu.edu.cn
例子:产生100随机数[0-999]
#include <iostream> 查找相关说明: 需要设置初始条件:seed:
#include <cstdlib> 使用:srand( unsigned int _seed);
#include <iomanip> 但如果seed相同,结果依旧相同,
#include <ctime> 需要一个随机的_seed
int main() 使用time(),获取当前时间…
{
time_t x = time( 0 ); 需要掌握库函数!
srand(x%10000); time(): 返回从1970年1月1日0时0分0秒算起到
现在所经过的秒数,UTC时间.
for( int i=0; i<100;i++ )
{
cout << setw(4) << rand()%1000;
if( i%10 == 9 )
{
cout << endl;
}
}
return 0;
}

shfzhao@bjtu.edu.cn
写程序,必须掌握库函数
写程序,必须掌握库函数

不够!
需要自己写函数,建立自己的函数库
判断素数
// 输入整数,判断是否素数.

#include <iostream>
using namespace std;
main( )
{
int m;
cin >> m;
if( m == 1 )
{
cout << “Input is invalid” << m;
}

shfzhao@bjtu.edu.cn
判断素数
for( int i = 2; i < m; i++ )
{
if( ( m % i ) == 0 )
{
cout << m << “isn’t a prime.”;
return 0;
}
}
cout << m << “is a prime.” ;
return 0;
}

shfzhao@bjtu.edu.cn
判断素数: 改进
// 输入整数,判断是否素数.
// 稍微改进
#include <iostream>
#include <cmath>
using namespace std;
main( )
{
int m;
cin >> m;
if( m == 1 )
{
cout << “Input is invalid” << m;
}

shfzhao@bjtu.edu.cn
判断素数: 改进
// 没有必要循环那么多次 整数m不可能分解为
两个超过sqrt(m) 的因子
for( int i = 2; i <=sqrt(m); i++ ) 的乘积
{
if( ( m % i ) == 0 )
{
cout << m << “isn’t a prime.”;
return 0;
}
}
cout << m << “is a prime.”;
return 0;
}

shfzhao@bjtu.edu.cn
判断素数:使用函数

判断素数:输入一个整数,判断是否是素数。
这是一个独立的功能,可以封装为一个函数.
习惯于用函数写程序: 使用函数
#include <stdio.h>
函数声明.
int isPrime( int k );
main( )
{
int n;
cin >> n;
isPrime( n );
函数调用:n为实参
}

shfzhao@bjtu.edu.cn
习惯于用函数写程序: 使用函数
/* 给定一个整数,判断是否是素数*/
int isPrime( int m )
{
if( m == 1 ){
cout << “Input is invalid” << m;
return -1;
} 使用return,可以
for( int i = 2; i <= sqrt(m); i++ ){
随时返回。
if( (m % i) == 0 ){
cout << m << “isn’t a prime.”;
return 0;
}
} 返回值的数据类
型,和函数类型
cout << m << “is a prime.”;
相同
return 1;
}

shfzhao@bjtu.edu.cn
习惯于用函数写程序:函数内不做输入输出

函数用于完成特定的功能,函数内一般不做输入和输出。
输入输出在main()或者专门的(用于输入输出的)函数中

函数的输入来自于函数参数,输出为函数的返回值。
----在学习指针(pointer)和引用(reference)后,函数的输出也
可以从函数参数返回。
习惯于用函数写程序: 函数无输入输出

#include <iostream>
或者:包含头文件函数声明.
#include <cmath>
using namespace std;
int isPrime( int n );
main( )
{
int n; 函数调用:n为实参
cin >> n;
int k = IsPrime( n );
if( k == -1 ){
cout << “Input is invalid :” << n;
} 主函数做输出: 不必考虑算法
else{
cout << n << ( k ? ”is”:”isn’t” ) << “a prime.”;
}
}

shfzhao@bjtu.edu.cn
用函数写程序
/* 给定一个整数,判断是否是素数*/
int isPrime( int m )
{
if( m == 1 )
{
return -1;
}

for( int i = 2; i <= sqrt(m); i++ )


{
if( ( m % i) == 0 )
{
return 0;
}
}
return 1;
}

shfzhao@bjtu.edu.cn
习惯于用函数写程序
 至少现阶段,整个程序是变成了如下两种形式:
函数声明
main() 函数体
算法实现 main

输入
main 输入

输入 函数调用
算法
实现
函数调用 输出

输出
main()结束
输出

main()结束 函数体
main()结束 算法实现

shfzhao@bjtu.edu.cn
推[必]荐[须]使用第三种方法
 开始写代码时,未必明确函数的接口需求,写过程中才确定。因此难以开
始就写函数;
 接口:名字/输入/功能/输出
 写main()中时,可专注于题目要求的输入和输出,算法/函数明确接口即
可。然后假设已经有函数了,在main()之上定义函数(做函数声明),然
后直接使用;
 在main()结束后[满足了题目的要求],再集中精力实现函数;

 C/C++的代码框架也是这个样子的:
 文件开始的头文件中,几乎都是函数声明语句;

shfzhao@bjtu.edu.cn
习惯于用函数写程序:函数共用

完成的函数,不仅可以给本程序使用,还可以给其他程序、其他
人使用:
一个程序包含多个文件的程序:按功能/模块保存函数;多人合
作使用: 把函数保存在一个独立的源文件(.cpp)中,用头文件
(.h)集中做函数声明;
Demo:
 isprime.cpp/h
习惯于用函数写程序: 函数无输入输出

#include <iostream>
或者:包含头文件函数声明.
#include “isprime.h”
main( )
{
int n;
cin >> n; 函数调用:n为实参
int k = IsPrime( n );
if( k == -1 ){
cout << “Input is invalid :” << n;
} 主函数做输出: 不必考虑算法
else{
cout << n << ( k ? ”is”:”isn’t” ) << “a prime.”;
}
}

shfzhao@bjtu.edu.cn
用函数写程序
// isprime.cpp
/* 给定一个整数,判断是否是素数*/
int isPrime( int m )
{
if( m == 1 )
{
return -1;
} // 文件isprime.h

for( int i = 2; i < sqrt(m); i++ ) int isprime( int k );


{
if( ( m % i) == 0 )
{
return 0;
}
}
return 1;
}

shfzhao@bjtu.edu.cn
给其他程序用

验证哥德巴赫猜想 : Goldbach conjecture


写main()函数,和isprime.cpp/h组成一个project;
哥德巴赫猜想: Goldbach conjecture
#include <iostream> 函数isPrime,
包含头文件
#include <cmath> 判断整数是否
#include “isprime” 素数
using namespace std; for( int i = m; i <= n; i+= 2 )
int main( ) {
{ for( j = 3; j < i/2; j++ )
int m, n, j; {
// 找第一素数: 枚举所有的可能;
cin >> m >> n; if( isPrime( j ) == 1
&& isPrime( i-j ) == 1 )
// 确保m和n是偶数; cout << i << "=" << j <<
m = ( m%2 ) ? m+1:m; "+" << i-j << endl;
n = ( n%2 ) ? n-1; n; }
}
return 0;
}
shfzhao@bjtu.edu.cn
c/c++ 程序组织
程序的组织
 不可能把所有的代码,都写到一个main()内:
 代码重用;
 合作开发;
 C/C++的组织方法:
 应用程序由函数s构成;
 库函数;__predefined functions;
 自己(团队)完成的函数;
 其他人完成的函数(比如:开源项目);
 函数可以存在与不同的文件中,应用由多个文件构成,每个文件包
含多个函数;
 一般实现一类功能的模块,集中放在一个/多个文件中,组成函
数哭:比如实现音乐处理、播放等(ALSA)。。。;

shfzhao@bjtu.edu.cn
C应用程序的组织
应用程序----------|-头文件(.h)
|--头文件1.h
|--头文件2.h
| ……
|--头文件m.h
|-源文件(.c/cpp)
|--源文件1.c/cpp
|--源文件2.c/cpp
| ……
|--源文件n.c/cpp

Dev-cpp中,称为一个“项目”project,“.dev”

shfzhao@bjtu.edu.cn
函数:完成特定功能的代码块
认识函数
Introduction to functions
 C/C++是一种结构化程序设计(structured programming)语言,所谓
结构化,是指代码“一块块”的,每块是实现一个完整的功能(模块功能
);把这些一块块代码封装为函数以利于重用,是结构化的核心;
 C/C++是一堆函数构成的。C/C++程序员在不断地写函数,用自己写的
函数和别人写的函数(包括库函数)写函数,然后用这些函数构成了复杂
的程序;
 函数抽象为:
 I-P-O : Input-Process-Output;
 Input: 函数参数列表;
 Processs: 函数实现(计算);
 Output: 函数“计算”的结果;一般用return返回。还可以:
 指针; pointer;
 引用; reference

shfzhao@bjtu.edu.cn
函数定义 Function Definition
Implementation of function
[]表示可能有,
定义形式:
也可能无。
[存贮类型] [类型标识符] 函数名( [形参表列] )
{
变量定义语句;
准备了几个变
函数声明语句; {
量,用于存放
执行语句; } 调用者传来的
}
就是一条复合语句 数据
int max( int a, int b)
{
return a>b ? a : b;
}
void OutStar( void ) /* void 可写,可不写, void必写 */
{
cout << “*********************”;
}

shfzhao@bjtu.edu.cn
函数名
[存贮类型] [类型标识符] 函数名( [形参表列] )

 用于标识不同的函数,在 c/c++中,不同含义的函数,应该给予不同的
名字;
 sin(), cos(), rand(), srand(), sqrt(), pow(), log(),…
 自己可以任意为自己的函数取名
 最好有一定的意义;
 实际:函数名是函数代码在计算机内存中的开始地址;
 在C中每个函数名字唯一:
 C++中:
 位于不同的namespace,可以重名;
 同一个namespace,一定条件下也可以重名:重载:overloading;

shfzhao@bjtu.edu.cn
函数的形参列表
[存贮类型] [类型标识符] 函数名( [形参表列] )
formal-parameter-list;
 函数也相当于一个小的程序: 程序由人使用,函数由函数使用(比如,
main()函数中,使用sin(),当然也可以使用自己写的函数,比如
isPrime( ));
 形参:函数输入:
 函数的caller是其他函数,它也是分配一块空间(定义一个变量),用
于接收调用函数的输入。这个变量定义在函数名后面的小括号中,就
是形参列表:列表表示可以输入多个变量;
 形参必须有数据类型(形参就是一个变量);
 就是数学意义上的函数自变量;

shfzhao@bjtu.edu.cn
函数类型
[存贮类型] [类型标识符] 函数名( [形参表列] )
 函数类型
 就是函数所求的值的数据类型;最接近与数学意义上的函数;
 double sin( double x );
 可以是void型,表示函数没有返回值。表示函数的功能是完成一件
任务,是一个过程的概念,比如:
void outStart()
{
cout << “\n*********************”;
}
 如果不写,表示返回类型为:int
 main函数实际上有返回类型:int
 main()函数的实际定义为:
 int main( int argc, char *argv[] )

shfzhao@bjtu.edu.cn
函数值如何返回? 使用return
 函数返回
 执行完被调用函数的最后一条语句时,自动返回。
 函数类型一定是void;
 若函数定义(声明)中指明了函数的返回类型(函数类型),则必须
使用return语句:return 表达式; ;
 对于void函数,程序执行完最后一条语句后,推荐使用return;
 return: 从函数中返回,即中断函数的执行,立即返回
 语法:return 表达式; 表达式有值,该值,其类型和函数类型相同
 在函数中,任何时刻都可以使用return语句返回;

 在函数调用中,return后的表达式的值, 赋值给函数外的”=”左边的变量
中。

shfzhao@bjtu.edu.cn
函数声明 function declaration
 函数调用规则:
 任何函数在调用之前,必须声明,格式为:
 [存贮类型] [类型标识符] 函数名( [形参表列] );
 被调用函数定义在调用函数之前,可以免去说明(比如,前面的例子):
 函数的返回值是整型的,可以免去类型说明。
 关于形参
 形参列表部分:可以只写数据类型,不写变量: 主要用于表示形参的数
据类型: 只是一个占位符:placeholders for data sent in
 double sin( double x);

 double sin( double y );

 double sin( double ); // 甚至不写变量名

 函数被调用前,必须知道函数长的啥样。
 函数名、参数类型、返回类型;
 Function name, parameter list, return type;
shfzhao@bjtu.edu.cn
函数声明: function declaration
 最通常的做法:集中声明(所有的头文件)。
 把源文件中所有定义的函数,在文件中第一个函数定义语句前,
声名全部的函数;
 把文件中其他文件中也要使用的函数集中在一起,建立一个头文
件,在使用的文件中使用#include包含,只在本文件中使用的函
数,在文件的开始位置声明。

shfzhao@bjtu.edu.cn
函数使用:函数调用: function call
 函数名(参数列表)
 参数列表,叫实参, 就是实际的值;
 argument list;
 比如:
 y = sin(2.5 ); /* 值*/
 x = 10;
y = sin( x ) + sqrt( x + y );
/* 变量,或者表达式,但终究是值*/

shfzhao@bjtu.edu.cn
else
 Parameter vs. Argument
 Terms often used interchangeably
 Formal parameters/arguments
 In function declaration

 In function definition’s header

 Actual parameters/arguments
 In function call

 Technically parameter is "formal" piece


while argument is "actual" piece*
 *Terms not always used this way

 Function definition placement


 Functions are “equals”; no function is ever “part” of another;
 NOT “inside” else function;

shfzhao@bjtu.edu.cn
函数的传值调用:call-by-value

 函数调用前,必须声明;
 传值调用call-by-value: 函数内不改变调用者的变量的值;
 函数调用时,从右往左计算表达式的值,传值;
参数传递: 传值调用call-by-value
 函数通过参数传递,实现不同条件下的运算,同时保证函数的可重用性。
 形式参数(形参)
 函数定义中的参数表列;例:double max( double x, double y )
 实际参数(实参)
 调用函数时,实际使用的参数: 字面量、变量、表达式;
 例:
 double a = 10, b = 20, z;
 z = max( 2.3, 5.15); // 直接使用值;
 z = max( a, b ); // 给变量,或者表达式,实际都计算值
 z = max( a+b, b+3.2 ); // 表达式;
 调用时:传值。
 把实际参数的值,赋值”=”给形参。
 实参不会改变(实参可以是常量,当然不涉及改变)
 函数的参数就是局部变量:只是在函数中有效!

shfzhao@bjtu.edu.cn
参数传递: 传值调用:call-by-value
#include <iostream>
void swap( int a, int b )
{
int t;
t = a;
a = b;
b = t; 结果:x=10, y= 20;
} 传值调用:把x和y的值给了
swap()的a和b,但x和y不改变.

main() 写成:swap(10,20),如何改变
{ 常量?
int x = 10, y = 20;
swap( x, y );
cout << x << y;
}

shfzhao@bjtu.edu.cn
参数传递顺序: 从后往前
 传递顺序:从右到左; 仔细看顺序:先b,然后a

 相当于执行:y = b = 20, x = a = 10;


#include <iostream
int F( int a, int b )
{
a *= 2; /* a = 30 * 2 = 60 */
b *= 5; /* b = 20 * 5 = 100 */
return a + b; /* 返回:160 */
} 实际执行:
++x; => x = 20;
main( ) F::b = x;
{ x+y=30
int x = 19, y = 10; F::a = 30
int z; F(30,20)
z = F( x+y, ++x ); /* z = 160, x = 20, y = 10 */
}

shfzhao@bjtu.edu.cn
函数的作用域

存贮类型;
namespace;
函数的存贮类型
 static
 内部函数(静态函数),该函数只能被它所在文件内的函数调用,其
他文件中的函数不可以调用。
 static int fun( int a, float b )
 实现封装。不同程序员设计的程序可以互相不干扰。
 extern
 外部函数,该函数可以被自己和其他文件使用。
 extern float fun( int a, float b )
 在要使用的文件中,做函数声明时,使用extern.
 若函数是extern,可以不写。即函数的缺省方式是extern.
 一般做法:不写static/extern,把希望extern的函数,写在头文件中,
让别人使用。---最好函数命名中,包含模块,比如:USB_xxx()

shfzhao@bjtu.edu.cn
函数调用例
建立一个头文件t1.h /* 文件 t1.c */
#include <iostream>
#include “t1.h”
/*文件 t1.h*/
static void C( float x, float y );
float A( int x );
float A ( int x) C()只是
int B( ); { 在t1.c中
… 使用。因
/*文件 t2.c */ } 此t1.h没
int B( ) 有声明.
#include <iostream>
{
#include “t1.h” …
}
main( ) static void C( float x, float y )
{ {
x = A( a ); …
} }

shfzhao@bjtu.edu.cn
总结:函数
 定义:[存贮类型] [函数类型] 函数名([形式参数])
 函数类型:
 不写,默认为int
 没有返回:void;
 通过return返回,也是value copy.
 存贮类型:
 static,只在本文件中,有效;
 extern,默认,不写;
 形参:
就是局部变量;
 传值调用;从右到左,一次计算值,copy过去;

 函数先定义,后使用;在使用前,必须先声明;
 说明性声明;头文件;
 定义性声明;
 return, 可以任意时刻调用。

shfzhao@bjtu.edu.cn
namespace

命名空间
定义了标志符(如变量,函数,类等)的声明范围(declarative
region)/作用范围/有效范围,避免名字冲突的;
namespace
 namespace
 定义了标志符(如变量,函数,类等)的声明范围(declarative
region)/作用范围/有效范围,避免名字冲突的;
 定义用法: 在header file and source file中
 在cpp中:
namespace namespace_name
{
//……
int isPrime( int m )
{
//…;
}
};

shfzhao@bjtu.edu.cn
namespace
 在header file中:
namespace namespace_name
{
// 声明变量;
// 声明函数;
int isPrime( int m );
// 申明类;
};
 用法:
 using declaration:
 namespace_name::isPrime();
 using directive:
using namespace namespace_name;
isPrime();
shfzhao@bjtu.edu.cn
namespace可以嵌套
例子:
namespace elements {
namespace fire {
int flame;
};
float water;
};

using namespace element::fire;

namespace myth{
using namespace elements;
};

shfzhao@bjtu.edu.cn
namespace 别名
 用来简化嵌套namespace的使用:
 比如:
 namespace MEF = myth::elements::fire;
 using MEF::flame;

shfzhao@bjtu.edu.cn
unnamed namespace
namespace {
int ice;
int fun( );
};

只能在当前定义的文件中使用: 相当于static;
在定义后面,可以用..

shfzhao@bjtu.edu.cn
函数嵌套调用

递归!
函数的嵌套调用
 在程序的执行过成中,当调用一个函数时,在该函数中又在调用另一个函数
 例:
#include <iostream>
void OutStart( )
{
cout << “*************”;
}
main( )
{
OutStar( );
}

shfzhao@bjtu.edu.cn
函数的嵌套调用

先调用的函数,最后执行完毕;最后调用的函数,最先执行完毕;

先进后出,后进先出;  栈!
main

shfzhao@bjtu.edu.cn
函数的递归调用:求n! = n * (n-1)!
#include <iostream> if( m == 0 )
double fact( int m); {
main( ) return 1;
{ }
double m; else
int n; {
cin >> n ; return (duble)m * fact( m-1);
m = fact( n ); }
cout << n << “!=” << m; }
}
double fact( int m )
{

shfzhao@bjtu.edu.cn
递归函数的规则
 其实:特别像高中数学中的数列:
已知 A1 = xxx , A2 = xxx, … An
= 10*An-1 + 8.7*An-2之类的。

 递归函数内部必然分成两个独立的
部分:一部分自己调用自己,为递
归调用,另一部分一定不会调用自
己;否则永远调用下去了。
 老和尚讲故事!
 不调用自己的部分,在于边界上:
此时不再需要自己调用自己计算了,
有比较明显的值。

shfzhao@bjtu.edu.cn
函数的递归调用
当函数被调用时,直接或间接调用自身,被称为函数的递归调用

void Hanoi( int n, char a, char b, char c )


{
if( n == 1 )
{
move( 1, a, c );
}
else
{
Hanoi( n-1, a, c, b );
move( n, a, c );
Hanoi( n-1, b, a, c );
}
}

shfzhao@bjtu.edu.cn
函数的递归调用
#include <iostream>
Using namespace std;

void move( int n, char a, char b );


void Hanoi( int n, char a, char b, char c );

main( )
{
Hanoi( 10, ‘A’, ‘B’, ‘C’ );
}

void move( int n, char a, char b )


{
cout << n << a << “”<< b;
}

shfzhao@bjtu.edu.cn
C++之:reference & call-by-reference

引用与引用调用
reference: 引用
 如果在函数中,需 #include <iostream>
要改变caller’s void swap( int* a, int* b )
{
argument? int t;
t = *a;
*a = *b;
 C的方法 *b = t;
 指针 }

 swap()修改x和y
main()
的值。 {
int x = 10, y = 20;
swap( &x, &y );
cout << x << y;
}

shfzhao@bjtu.edu.cn
reference: 引用
 引用定义:
 int m;
 int& n = m; // n 为引用
 从内存空间看引用:
 n就是m: n和m共享同样的存贮空间:m和n都代表了同一块内存;
 m = 10; 则 n = 10;
 n = 20; 则 m = 20;
 int m; 为变量m 分配一块内存;
 int &n = m;不分配内存,使变量n和m共享同一块内存;
 所以:引用定义的同时,必须初始化:和谁共享内存?;
 引用:
 本质是地址!

shfzhao@bjtu.edu.cn
reference: 引用
 用法:
 函数声明/调用时,如果形参定义为引用类型,则函数里面可以改变
caller’s argument的值:
 used to provide access to caller’s actual argument.
 caller’s data can be modified by called function!
 即:call-by-reference;
 例子 swap( );

shfzhao@bjtu.edu.cn
参数传递: 传值调用 call-by-value
#include <iostream>
void swap( int a, int b )
{
int t;
t = a;
结果:x=10, y= 20;
a = b;
传值调用:把x和y的值给了
b = t; swap()的a和b,但x和y不改变.
}
写成:swap(10,20),如何改变
常量?
main()
{
int x = 10, y = 20;
swap( x, y );
cout << x << y;
}

shfzhao@bjtu.edu.cn
call-by-reference
#include <iostream>
void swap( int& a, int& b )
{
int t;
t = a; 结果:x=10, y= 20;
a = b; Call-by-reference调用:把x和y的空间共享给了a和b;
b = t; swap()的a和b,就是main()中的x和y.
}
不能写成:swap(10,20):or( x+y, x*y );
函数形参数定义为reference,实参必须是变量
main()
{
int x = 10, y = 20;
swap( x, y );
cout << x << y;
}

shfzhao@bjtu.edu.cn
call-by-reference
 不希望在函数里面改变 caller的变量值:
 形参定义为:普通变量:使用call-by-value;
 如果定义为:reference,那么:
 使用const;
void sendConstRef( const int& par1, const int& par2 );
par1和par2都是const,在函数里面,不能修改par1和par2的值;

shfzhao@bjtu.edu.cn
C++之:重载函数 overloading

目的:相同功能的函数,使用相同的函数名字
overloading
 C中,求-2.5的绝对值:
 abs( -2.5 ) ,结果=2,因为abs()函数定义如下:
 int abs( int );
 调用了abs(-2.5)函数: 把-2.5转换为整数-2,再传入函数abs()中.
 可能想要的结果是:2.5
 必须使用 fabs( -2.5);
 C中,求绝对值有三个函数:
 int abs(int );
 long labs( long );
 double fabs( double );
 实际上,这些函数的“物理”含义相同;
 仅仅因为数据类型不同,需要定义不同的函数;

shfzhao@bjtu.edu.cn
overloading
 C++:overloading
 同样的功能,用同样的函数名字表示;
 函数名(Same function name)和返回类型相同: 因为是对同一个
功能的抽象;
 但形参不同: Different parameter lists
 即使在同一个namespace(命名空间)中,函数也可以同名!
 draw 圆形:
 在C中,定义多个不同名函数,比如:
 void draw_circle1( float x1, float y1, float x2, ,float y2);
 void draw_circle2( float x, float y, float r );
 在C++中,可以定义同名函数实现:
 void draw_circle( float x1, float y1, float x2, ,float y2);
 void draw_circle( float x, float y, float r );

shfzhao@bjtu.edu.cn
overloading
 which function gets called?
 draw_circle( 5.2, 6.7, 9.2, 10.7 );
 Calls four-parameter draw_circle();
 Draw_circle( 5.2, 6.7, 4.2 );
 Calls three-parameter draw_circle();
 似乎简单:
 仅靠函数参数个数不同,compiler可以区分调用哪个;
 此时每个函数都是一个“独立”的函数;

shfzhao@bjtu.edu.cn
overloading
 但:并不如上述描述的这么简单;
 C++ overloading:
 Same function name:简单
 Different parameter lists:不简单,可以是:
 形参个数不同,OK;
 形参数据类型不同,OK;
 average():
 double average(double n1, double n2); // 1
 double average(double n1, double n2, double n3); // 2
 double average( double n1, int n2 ); // 3
 which function gets called?
 average( 2.3, 5);
 average( 4, 5); Which ? 1 or 3 ?
shfzhao@bjtu.edu.cn
overloading
 which function gets called?
 1st: Exact Match
 Looks for exact signature
 Where no argument conversion required

 2nd: Compatible Match


 Looks for "compatible" signature where automatic type
conversion is possible:
 1
st with promotion (e.g., intdouble)

 No loss of data

 2
nd with demotion (e.g., doubleint)

 Possible loss of data

 匹配重载函数的规则:参数匹配规则+相容类型隐性转换规则

shfzhao@bjtu.edu.cn
overloading
 Avoid confusing overloading:
 比如:
 void fun( int n, double m ); // 1
 void fun ( double n, int m ); // 2
 void fun( int n, int m ); // 3
 calls:
 fun( 98, 99 ); ?
 fun( 5.3, 4 ) ; ?
 Fun( 3, 5.9 ) ;
 fun( 4.3, 5.2 ) ? // 1 or 2 ?
 你不知道, compiler也不知道.

shfzhao@bjtu.edu.cn
overloading
 重载函数的内部实现:
 名字粉粹name mangling;
 “看上去”相同的函数名字,最终携带了其所有的形参的数据类型:
 比如:函数名称+”_”形参的数据类型的简写…
 double average( double, double ) 

 double average_dd( double, double );

 double average( double, double, double ) 

 double average_ddd( double, double, double );

 比如:v/c/i/f/l/d/r分别表示:
void/char/int/float/long/double/reference;
 实际实现时,不同的compiler有差异:
 average_d_d;
 Average%d%d
 …

shfzhao@bjtu.edu.cn
overloading
 Overloading:
 与函数返回值无关;
 call-by-reference:
 函数的引用参数与非引用参数不能作为区别重载函数的依据
 如下两个overloading 函数:
int fun( int k, double x ); // 1
int fun( int k, double& x ); // 2
 如果调用:
int k = 10;
int m = fun( 10, 20 ); // 没有问题:一定调用 1.
double z = 20.2;
int n = fun( k, z ); // which ?

shfzhao@bjtu.edu.cn
C++之:default arguments

带缺省参数的函数:
default argument
 Allows omitting some arguments
 Specified in function declaration/prototype
 void showVolume( int length,
int width = 1,
int height = 1);
 Last 2 arguments are defaulted
 Possible calls:
 showVolume(2, 4, 6); //All arguments supplied
 showVolume(3, 5); //height defaulted to 1
 showVolume(7); //width & height defaulted to 1

shfzhao@bjtu.edu.cn
default argument
 默认参数的顺序规定:从右至左
void g(int a=3,int b); //error
void v(int a, int b=3);
void f()
{
v(3); //a=3,b=3,默认b=3
v(2,4); //a=2,b=4
}

shfzhao@bjtu.edu.cn
default argument
 默认参数与函数重载:拒绝两义性:

 void Fun( int, int k = 4 );

 void Fun( int );

 编译器不拒绝上述定义方式;

 但如果使用:

 int k;

 Fun( k );

 Which function will be called?

 If fact, The programmer can’t know which function will be


called, so the compiler can’t know.

shfzhao@bjtu.edu.cn
预处理命令

C/C++的源文件中,
#开始的指令,就是编译预处理命令;
编译预处理命令
 编译预处理命令:
 文件包含 #include
 宏定义 #define/#undef
 条件编译 #if #elseif #endif #ifdef #ifndef
 #else
 #endif

shfzhao@bjtu.edu.cn
包含#include
 一个源文件将另一个源文件的
全部内容包含进来。
 #include <iostream>
 在指定的目录中寻找文
件:系统定义的目录
 Dev-cpp中:工具->
编译器选项,可以看到
当前设置
 #include “isprime.h”
 在当前目录中寻找,然
后再到指定的目录中寻
找文件;

shfzhao@bjtu.edu.cn
包含#include
 执行效果:

f.c f.h f.c


#include “f.h”

 一般#include的都是头文件;而头文件中主要包括了函数声明语句;
 确保函数在使用之前,一定已经声明了;

shfzhao@bjtu.edu.cn
宏定义#define
 宏定义
 用一个指定的标识符(名字)代表一个字符串。
 定义形式:
 #define 标识符 字符串
 #define PI 3.14159
 基本应用方法:
 在编译预处理时,将程序中,所有出现标识符(PI)的地方,都使用字
符串(3.14159)完全替换(不做任何改变)。
 s = r * r * PI;在编译时,实际看到的就是:
 s = r * r * 3.14159;
 宏定义是起别名,不是变量定义和赋初值,不会涉及分配空间的问题。
 在双引号中出现和宏名相同的字符时,不做替换,即替换不考虑字符串
内的东西,字符串是一个整体。

shfzhao@bjtu.edu.cn
宏定义#define
 应用事项
 宏名一般使用全大写字母表示;
 #define PI 3.14159
 只是做替换,不做任何类型(错误)检查;
 #define PI 3.1al59

 宏定义中,最后没有“;”
 #define PI 3.14159;

 s = PI * r * r => s = 3.14159; * r * r

 作用范围为定义位置开始到文件结束;
 可以使用#undef终止宏定义的作用域(不常用)
#define PI 3.14159
…..
s = PI * r * r;

#undef PI

shfzhao@bjtu.edu.cn
宏定义#define
 在C++中,一般不这样用了,用法是:
 const double PI = 3.1415926;
 const: 表示PI是一个常量,程序运行过程中不被改变;
 优点:
 PI是一个变量,在调试程序的过程中,依然可以观察变量的值;
 C++中,和条件编译 #if 配合使用

shfzhao@bjtu.edu.cn
条件编译
 对源文件中的一部分代码进行编
译,即指示编译器的编译方法。
 包括:
 1. 头文件中:如下定义可以避免重复包含:
#ifndef _FILENAME_H_
 #if(n)def 标识符
#define _FILENAME_H_
程序段1
……
 #else #endif
程序段2
 #endif
 2.
#if 0
 #if 表达式
……
程序段1
#endif
 #else
程序段2 相当于注释掉了。

 #endif
shfzhao@bjtu.edu.cn
条件编译
例:
#ifndef _FILE3_H_
/* file1.c */
#define _FILE3_H_ #ifndef _FILE3_H_
#include <iostream> /* file3.h*/ #define _FILE3_H_
…… #include <iostream>
int a; // 定义了一个全局变量; …… 这一块编译器看
#endif 不到: 因为
int a;
#endif _FILE3_H已经
/*file2.h*/ 定义了
#include “file3.h”
#ifndef _FILE3_H_
/* file1.c */ #define _FILE3_H_
#include “file2.h”
#include <iostream>
#include “file3.h”
……
int a;
#endif

shfzhao@bjtu.edu.cn
宏定义#define
 宏可以带参数
 定义:#define 宏名(参数表) 字符串
 例:#define max( a, b ) ((a) > (b) ? (a) : (b))
 用的时候:
 float x, y, z;
 …
 z = max( x, y ); // 经过预处理程序处理后的,此处的代码是:
 z = ((x)>(y)?(x):(y));

 int a, b, c;
 …
 c = max( a+b, 3+b ); // 经过预处理程序处理后的,此处的代
码是:
 c = ((a+b)>(3+b)?(a+b):(3+b));

shfzhao@bjtu.edu.cn
带参数的宏和函数不同
float max( float x, float y)
{
return x > y ? x:y;
}
x = y * max( a+10, b+10 );
进入函数的是: x=a+10的值, y=b+10的值

 函数的实参数是先计算,然后通过“栈”传递,宏只是做简单的替换;
 函数调用是在程序运行时处理,宏做替换是在编译前处理;
 函数的参数有数据类型,宏没有数据类型定义
 函数在整个应用中只有一个,程序空间节省,但运行效率不高,有开销。
宏替换导致源文件中实际相同代码增加,程序较大,但运行效率高。

shfzhao@bjtu.edu.cn
C++之:内联函数: inline

在C++中,不再使用带参数的宏
inline
 在C中,采用#define,可以定义带参数的宏,在C++中采用inline取代;
 使用时,直接用函数体代码来替代对函数的调用,这一过程称为函数体
的内联展开,函数体代码嵌入在代码行中,减少了函数调用的开销;
 对于只有几条语句的小函数来说,与函数的调用、返回有关的准备

和收尾工作的代码往往比函数体本身的代码要大得多。因此,对于
这类简单的、使用频繁的小函数,将之说明为内联函数可提高运行
效率。
 保留函数调用形式,却消匿调用开销,从而提升频繁被调的小函数性能
 先声明后调用
 inline是一种“用于实现”的关键字,而不是一种“用于声明”的关键字
 与函数定义放在一起才能使函数成为内联函数,仅仅将inline放在函数
声明前面不起任何作用。。

shfzhao@bjtu.edu.cn
inline
 inline函数,一般把:函数声明和函数定义写在一起,放在头文件中 (普
通函数往往声明在头文件,定义在实现文件),比如在头文件中:
inline double max( double a, double b ) {
return a > b ? a:b;
}
 慎用内联函数: 内联是以代码膨胀复制为代价,仅仅省去了函数调用的开
销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函
数调用的开销较大,那么效率的收获会很少。每一处内联函数的调用都要
复制代码, 将使程序的总代码量增大,消耗更多的内存空间。以下情况不
宜使用内联:
 如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用
的开销大。

shfzhao@bjtu.edu.cn
变量
变量的作用域与变量的生命期;
局部变量与全局变量
封装;
变量及其作用域
 C应用程序由很多个文件构成,每个文件中包含了很多个函数,每个函
数中都会定义和使用变量,变量名可以相同吗?
 可以!变量应该有自己的作用范围。
 C的变量,按定义位置分两种:
1.局部变量: 在{} 内定义,作用域在{}内(比如函数)
2.全局变量:不在任何一个{}内,作用域:
在本文件内起作用:从定义位置开始,到结束;
可以在整个应用程序内起作用(多个文件中起作用:其他文件
extern方式,重新定义)
 变量生命期:变量所代表的存贮位置,是否在整个程序运行时间,始终
属于该变量?
 static 局部变量和全局变量,始终占据存贮空间;

shfzhao@bjtu.edu.cn
局部变量
 定义: #include <iostream>
 定义在函数内(在{}内)的变量
main( )
 作用范围: {
 在所定义的函数、复合语句中 int a = 10, b;
b = a + 20;
 当进入该复合语句后,变量存
{
在,出去后,变量不存在(空 int b;
间被回收) a += 10;
 局部作用域 b = a + 10;
cout << b;
 注意:
}
 出现同名变量时,使用最近使 cout << a << b;
处定义变量; }

shfzhao@bjtu.edu.cn
局部变量:
 局部作用域
 局部作用域针对函数内部的语句序列规定其名字的有效性
 函数内部的语句序列可能含有花括号对括起来的复合语句块(含if
switch for while do…while等),可看作嵌套的结构语句
 规则
 语句块内定义的数据不能被块外访问
 语句块内定义的数据可以被块内嵌套的语句块访问

shfzhao@bjtu.edu.cn
局部变量
 局部变量的存贮类型
 auto 自动变量(缺省)
 在进入{}后分配,退出后空间被回收; 生命周期和应用程
序等长,但的确是
 auto float a
局部变量:只能在{}
 static 局部静态变量 中被访问。
 程序开始后分配,程序退出后回收;
 static int a
 register 寄存器变量 一般不用,由编译
器处理。
 register int a;
 自动变量声明时,没有被初始化
 局部静态变量声明时,被初始化为0: 存贮空间被初始化为0.

shfzhao@bjtu.edu.cn
局部变量: 局部
#include <iostream>
main( )
{
int a, b, c=30; /* a, b, 局部变量没有被初始化,其值随机 */
a = 10;
b = 20;
{
int a, b; /* a , b 和外面的a, b不同 */
a = 30;
b = 40;
c = a + b; /* c的作用域到达了复合语句内*/
}
c = a + b;
}

shfzhao@bjtu.edu.cn
静态局部变量
#include <iostream>
int Fun( int a )
{ 1.被初始化为0, 实际是变
static int b = 0; /* 实际初始化为0 */ 量空间被初始化为全0
b += 10; /* 实际*/ 2.静态局部变量生命周期
return a + b;
和应用程序等长。第二次
}
进入函数时,保留上次的
main( ) 值。
{ 3.函数的参数:形参,就
int a = 10; 是一个局部变量。
a = Fun( a ); /* 20 */
a = Fun( a ); /* 40 */
a = Fun( a ); /* 70 */
}

shfzhao@bjtu.edu.cn
局部变量:函数形参
 函数的形参: double fact( int m )
 用于接收调用者给的值(实参: {
实际参数); double l = 1;
 为局部变量,虽然没有定义在{} int i;
内; for( i = 1; i <= m; i++ )
 在函数内,可以任意使用; {
l *= i;
}
return l;
}

shfzhao@bjtu.edu.cn
goto标号:函数作用域
 函数形参是局部变量:
 在整个函数内作用:函数作用域
 函数作用域:goto语句标号;
 goto语句的标号为一个名字(标识符)
 名字未声明而使用
 在C/C++中,所有名字都必须经编译器检查,但:goto标号虽可
能未定义,但可通过编译
 例子:

shfzhao@bjtu.edu.cn
goto语句与函数作用域
int fun( int a, float b )  把函数返回前要完成的代码,集
{ 中写在函数的最后;
//…
 避免fun_end后面的代码块被重
if( C1 ){
goto fun_end;
复调用;
}
// …  goto语句与标号语句之间若夹有
if( C2 ){
变量定义语句,则跳过变量定义
goto fun_end;
而使用变量便成为非法;
}
// …
fun_end:
//…..
return 1;
}

shfzhao@bjtu.edu.cn
全局变量
 定义:
 在函数之外定义的变量。全局变量又称为外部变量
 作用域:
 从变量定义处开始到该源文件结束处
 存贮类型:
 static,变量只能在本文件中使用: 文件作用域;
 缺省情况下,可以在其他文件中使用
 extern,说明变量为全局变量,但该变量已经在其他文件中作了定义,
该处可以使用。但该变量不分配存贮空间。
 全局变量给程序设计带来诸多弊病
 (1) 降低程序的清晰度
 (2) 降低函数的灵活性

shfzhao@bjtu.edu.cn
全局变量:文件作用域
 文件作用域:

 编译器以程序文件为编译单位
 作用于一个程序文件代码全体的数据或函数具有文件作用域,即静态
全局数据或静态全局函数的作用域

shfzhao@bjtu.edu.cn
全局变量
#include <iostream>
int a; /* 被初始化为0,作用范围为定义开始直到文件结束 */
int Fun( int x )
{
return a + x;
}

main( )
{
int b = 20;
a += 10; /* a = 10 */
b = Fun( b ); /* b = 30 */
}

shfzhao@bjtu.edu.cn
全局变量
 全局变量的作用域:
 从定义位置开始,都可以使用;
 可以通过extern方式,使其他文件中的函数也可以使用。
 说明该变量是全局变量,在其他文件中已经定义,在该文件中,不
再需要分配空间。

/* File1.c */
#include <iostream> /* File2.c*/
int a; #include <iostream>
int Fun( int x );
extern int a; /* 在其他文件中定义*/
main( ) int Fun( int x )
{ {
int a = 10; return a + x;
a += 10; }
a = Fun( a );
}
shfzhao@bjtu.edu.cn
全局变量
 若全局变量只在该文件中使用,不允许在其他文件中使用,则使用
static.

/* File1.c */ /* File2.c*/
#include <iostream> #include <iostream>
static int a;
int Fun( int x ); extern int a; /* Error */
int Fun( int x )
main( ) {
{ return a + x;
int a; }
a += 10;
a = Fun( a );
}

shfzhao@bjtu.edu.cn
函数和全局变量的应用
 求一元二次方程的根,要求:
 应用程序由两人完成,学生A写一个函数,求解一元二次方程,学生B
写主函数,根据A提供的函数,从键盘接收方程的参数,输出求两个
根。
 考虑应用程序的组织:
 多人合作,每人建立一个文件,来存放自己的源程序,因此,该应用
程序由多个文件构成。
 A是函数的提供者,有必要为自己写的函数建立一个头文件,做函数
声明性的说明。

shfzhao@bjtu.edu.cn
函数和全局变量的应用
 设计方法:
 函数可以使用形参从调用者获得方程的系数,但因函数只能返回一个
值,无法返回函数的两个根,因此需要使用全局变量,返回函数的根。
 根据系数不同,函数的根也不同,因此需要区分函数的执行情况,用
函数的返回作为函数执行情况的结果。

shfzhao@bjtu.edu.cn
函数和全局变量的应用
/* 文件 root.h
* 函数声明
*/

/* 方程为: ax*x + bx + c = 0
* 在文件中,做如下全局变量声明:
* extern float x1, x2;
* 函数返回:
* -1,无根,a = 0
* 0, 两个相等的实根, x1 = x2
* 1, 两个不等的实根, x1, x2
* 2, 一对个共轭复根, x1 +/- ix2
*/
int Root( float a, float b, float c );

shfzhao@bjtu.edu.cn
函数和全局变量的应用
/* 文件 root.cpp */ x1 = x2 = -b / ( 2 * a );
return 0;
#include <cmath>
}
#include “root.h “ else if( p > 0 )
float x1, x2; {
int Root( float a, float b, float c) p = sqrt( p );
x1 = ( -b + p )/(2. * a);
{
x2 = ( -b – p )/(2. * a);
float p;
return 1;
if( fabs(a) < 0.000001 ) }
{ else
return –1; {
p = sqrt( -p );
}
x1 = -b /( 2. * a );
p = b * b – 4 * a * c; x2 = p / ( 2. * a );
if( fabs(p) < 0.000001 ) return 2;
{ }
}

shfzhao@bjtu.edu.cn
函数和全局变量的应用
/* B同学文件: main.cpp*/
#include <iostream>
#include <cmath>

#include “root.h”
extern float x1, x2;
main( )
{
float a, b, c;

cin >> a >> b >> c ;

shfzhao@bjtu.edu.cn
函数和全局变量的应用
switch( Root( a, b, c))
{
case –1:
cout << “Error a = 0 \n”;
break;
case 0:
cout << “x1 = x2 = “ << x1;
break;
case 1:
cout << “x1 =“ << x1 << “\t x2 =” << x2;
break;
case 2:
cout << “ x1 = << x1 << “ + i “<< x2 << endl;
cout << “ x1 = << x1 << “ - i “<< x2 << endl;
break;
}
}

shfzhao@bjtu.edu.cn
尽可能不使用全局变量
 上述例子通过DEMO使用全局变量解决函数返回的问题。在C/C++中不
提倡使用全局变量;推荐采用如下方式解决:
 C/C++中,使用指针(参看指针章节);
 C/C++中,返回结构类型的变量(参看结构章节)
 C++中,使用reference;
 使用 reference代码修改如下:
 1。 root.h中,函数声明修改:
 From: int Root( float a, float b, float c );
 To: int Root( float a, float b, float c, float& x1, float& x2 );
 2. root.cpp中,删除全局变量定义,修改函数形参:
 From: float x1, x2;
int Root( float a, float b, float c)
 To: float x1, x2; // 删除这一行
int Root( float a, float b, float c, float& x1, float& x2 )
shfzhao@bjtu.edu.cn
尽可能不使用全局变量
 3. main.cpp中:
 删除:extern float x1, x2;
 在main()函数中,增加局部变量说明:
float a, b, c;
float x1, x2;

cin >> a >> b >> c ;


 调用修改:
 From: switch( Root( a, b, c ))

 To: switch( Root( a, b, c, x1, x2 ))

shfzhao@bjtu.edu.cn
总结:变量
 局部变量  全局变量
 定义在{}中;  定义在{}外;
 {}中生效;  从定义位置开始,到文件结束有
 没有初值,随机数; 效;
 加static,静态局部变量,初  0x00…清空的存贮区间;
值为0;在整个应用程序存在  加static,表示只在本文件中有
其间都有效。…值保留,直到 效(和函数相同);
在该{}中,再次改变;  有extern的用法…不分配空间
 函数的形参,也是局部变量;  全局存贮空间中;
 动态存贮空间中  局部特性:也就在一个应用中有
 全局特性:在{}内,老大; 效而已。

shfzhao@bjtu.edu.cn
Continue……

空间
程序的存贮空间
 应用程序可使用内存
DLL
 程序区
 存放程序;
 静态存贮区 静态链接
main()
 程序运行中,分配的固定的存贮空间。 代码

 存放:全局变量和静态局部变量:初始
化为0x00; IsPrime()
代码
 动态存贮区
 程序运行中,根据需要进行动态分配。 printf()
代码
 存放:
静态存贮区
 自动(局部)变量
 形参 动态存贮区

 其他
shfzhao@bjtu.edu.cn
程序的存贮空间
 两个程序使用
DLL
同一块DLL中 printf()代码
的代码

main() main()
代码 代码

fact() IsPrime()
代码 代码

printf()地址 printf()地址

静态存贮区 静态存贮区

动态存贮区 动态存贮区

shfzhao@bjtu.edu.cn
作用域
局部作用域
 局部作用域
 局部作用域针对函数内部的语句序列规定其名字的有效性
 函数内部的语句序列可能含有花括号对括起来的复合语句块(含if
switch for while do…while等),可看作嵌套的结构语句
 规则
 语句块内定义的数据不能被块外访问
 语句块内定义的数据可以被块内嵌套的语句块访问

shfzhao@bjtu.edu.cn
函数作用域
 函数作用域作用于函数内部全域,名字未声明而可有效使用,故只针对
goto语句的标号,形参数也是函数作用域
 goto语句的标号为一个名字(标识符)
 凡名字都必须经编译器检查
 goto标号是函数作用域,goto往前往后跳转至指定标号,其标号虽可能未定
义,但可通过编译
 注意:goto语句与标号语句之间若夹有变量定义语句,则跳过变量定义而
使用变量便成为非法,switch中的case分情况标号属于同类问题

shfzhao@bjtu.edu.cn
函数原型作用域
 函数原型作用域:函数声明中的参数有效性
 参数名字与声明中可见的外部名字无关(参数名字独立)
 函数定义之参数作用域属于函数作用域
#include<iostream> 虽然可以这样写,但不建议这样用:
using namespace std; default argument写常量更合理.
int c=5;
int g(int a); //参数a与别的函数声明中的参数a无关
int f(int a, int b=g(c), int c=c); //参数c与全局变量c互相独立
int main() {
cout<<f(1)<<“\n”; //结果为51
}
int f(int a, int b, int c){ //参数为函数作用域
return a+b*c;
}
int g(int a){ 如果想这样表达,可以函数调用写为:
return 2*a; f (1, g(c) )
}

shfzhao@bjtu.edu.cn
文件作用域
 文件作用域:

 编译器以程序文件为编译单位
 作用于一个程序文件代码全体的数据或函数具有文件作用域,即静态
全局数据或静态全局函数的作用域

shfzhao@bjtu.edu.cn
可见性
 可见性:在同一个作用域中,外层语句块名字被内层语句块的相同名字遮
蔽,引出可见性问题,例如
char i=‘A’;
void f(){
double i=3;
for(int i=1; i<=10; i++)
{
a+=i*2; //访问int i(double i遭屏蔽)
//此处希望访问double i恐不能
} //int i作用域结束
cout<<i<<“\n”; //double i
cout<<::i<<“\n”; //char i
}
 内层语句块可通过全局域名::访问全局数据,但访问中层数据恐不能,须
寻求名空间方式帮助

shfzhao@bjtu.edu.cn
生命期
 生命期:
 指数据驻留内存空间的生存期
 静态生命期——在全局数据区驻留
 数据一旦创建就不会消失(与程序共存亡),有全局数据,全局静态数据,
局部静态数据
 局部生命期——在栈数据区驻留
 随函数调用和返回,形成函数作用域的数据生命期
 随语句块开始和结束,形成局部作用域的数据生命期
 动态生命期——在堆数据区驻留
 随new创建和delete销毁,人为决定其生存期,或称动态内存数据

shfzhao@bjtu.edu.cn
Thanks

Any question?

You might also like