Professional Documents
Culture Documents
C3 函数与变量 第56章
C3 函数与变量 第56章
C3 函数与变量 第56章
计算机与信息技术学院
信息科学研究所
赵帅锋
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;
}
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
shfzhao@bjtu.edu.cn
给其他程序用
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);
函数被调用前,必须知道函数长的啥样。
函数名、参数类型、返回类型;
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
Actual parameters/arguments
In function call
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
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;
};
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
函数的递归调用
当函数被调用时,直接或间接调用自身,被称为函数的递归调用
shfzhao@bjtu.edu.cn
函数的递归调用
#include <iostream>
Using namespace std;
main( )
{
Hanoi( 10, ‘A’, ‘B’, ‘C’ );
}
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
No loss of data
2
nd with demotion (e.g., doubleint)
匹配重载函数的规则:参数匹配规则+相容类型隐性转换规则
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 )
比如: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
默认参数与函数重载:拒绝两义性:
编译器不拒绝上述定义方式;
但如果使用:
int k;
Fun( k );
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
执行效果:
一般#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;
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;
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?