目录
标识符的作用域
1、函数原型作用域
2、局部作用域
3、类作用域
4、命名空间作用域(文件作用域)
标识符的可见性
对象的生存期
1、静态生存期
2、动态生存期
例.变量的生存期与可见性
总结
类的静态成员
1、静态数据成员
例.具有静态数据成员的Point类
2、静态函数成员
1、友元函数
例.使用友元函数计算两点间的距离
2、友元类
例.B类是A类的友元类
共享数据的保护
常对象
常成员函数
常数据成员
常引用
多文件结构
C++程序的一般组织结构
外部变量
外部函数
编译预处理
1、#include指令
2、#define和 #undef指令
3、条件编译指令
4、defined操作符
是指一个标识符在程序正文中有效的区域。C++中包括以下几种作用域:
即声明函数原型时形式参数的作用范围,是C++程序中最小的作用域。
double area(double r);
上例中的左右括号之间就是r的作用域。
(1)函数形参列表中形参的作用域,从形参列表中的声明处开始,到整个函数体结束之处为止。
(2)函数体内声明的变量,其作用域从声明处开始,一直到声明所在的块结束的大括号为止。(所谓块,就是一对大括号括起来的一段程序)
具有局部作用域的变量也称为局部变量。
一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的、不属于前面所述各个作用域的标识符,都属于该命名空间作用域。格式如下:
namesapce 命名空间名{
命名空间内的各种声明(函数声明、类声明、...)
}
在命名空间内部可以直接引用当前命名空间中声明的标识符,若需要应用其他命名空间的标识符,需要使用下面的语法:
using 命名空间名::标识符名;
using namespace 命名空间名;
前一种形式将指定的标识符暴露在当前的作用域内,使得在当前作用域中可以直接引用该标识符;后一种形式将指定命名空间内的所有标识符暴露在当前的作用域内。
具有命名空间作用域的变量也称为全局变量。
即标识符的有效范围,表示从内层作用域向外层作用域“看”时能看到什么。程序运行到某一点,能够引用到的标识符,就是该处可见的标识符。
可见性的一般规则如下:
1、标识符要声明在前,应用在后。
2、在同一作用域中,不能声明同名的标识符。
3、在没有互相包含关系的不同的作用域中声明的同名标识符,互不影响。
4、如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见。
即寿命,指对象(包括简单变量)从诞生到消失的这段时间。
如果对象的生存期与程序的运行期相同,则称它具有静态生存期,该对象称为静态变量。包括以下两种:(1)命名空间作用域中的对象。
(2)使用关键字static。
局部作用域中静态变量地特点是,它并不会随着每次函数调用而产生一个副本,也不会随着函数返回而失效。
注:定义时未指定初值的基本类型静态生存期变量,会被赋予0值初始化,而对于动态生存期变量,不指定初值意味着初值不确定。另,若定义数组时未初始化,初值也不确定;若定义数组时初值个数少于数组大小,其余自动为0。
除了以上两种情况,其余的对象都具有动态生存期,称为局部生存期对象。局部生存期对象诞生于声明点,结束语声明所在的块执行完毕之时。
#include
using namespace std;
int i = 1;//静态全局变量
void other() {
static int a = 2, b;//静态局部变量:全局寿命、局部可见,只有第一次进入函数时被初始化
int c = 10;//动态局部变量:局部寿命、局部可见,每次进入函数时都初始化
a += 2;
i += 32;
c += 5;
cout << "---OTHER---" << endl;
cout << "i:" << i << "a:" << a << "b:" << b << "c:" << c << endl;
b = a;
}
int main() {
static int a;//静态局部变量:全局寿命、局部可见
int b = -10, c = 0;//动态局部变量:局部寿命、局部可见
cout << "---MAIN---" << endl;
cout << "i:" << i << "a:" << a << "b:" << b << "c:" << c << endl;
c += 8;
other();
cout << "---MAIN---" << endl;
cout << "i:" << i << "a:" << a << "b:" << b << "c:" << c << endl;
i += 10;
other();
return 0;
}
运行结果如下:
静态局部变量——全局寿命、局部可见
动态局部变量——局部寿命、局部可见
全局变量都是静态的,所以——全局寿命、全局可见
如果某个属性为整个类所共有,不属于任何一个对象,则采用static关键字来声明为静态成员。静态成员在每个类只有一个副本,由该类的所有对象共同维护和使用,从而实现了同一类的不同对象之间的数据共享。
静态数据成员具有静态生存期。由于静态数据成员不属于任何一个对象,因此可以通过类名对它进行访问,一般的用法是“类名::标识符”。在类的定义中只对静态数据成员进行引用性声明,然后在命名空间作用域中必须使用类名进行定义性声明。之所以类的静态数据成员需要在类定义之外再加以定义,是因为需要以这种方式专门为它们分配空间。
#include
using namespace std;
int i = 1;//静态全局变量
class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {
count++;
}
Point(Point &p) :x(p.x), y(p.y) {
count++;
}
~Point() { count--; }
int getX() { return x; }
int getY() { return y; }
void showCount() {
cout << "count=" << count << endl;
}
public:
int x, y;
static int count;
};
int Point::count = 0;//定义时不用加static
int main() {
Point a(4, 5);
a.showCount();
Point b(a);
b.showCount();
return 0;
}
注:在int Point::count=0;这条语句中,虽然这个静态数据成员是私有类型,在这里却可以直接初始。除了这种场合,在其他地方就不允许直接访问了。
就是使用static关键字声明的函数成员,性质与静态数据成员一样。静态成员函数可以直接访问该类的静态数据和函数成员,而访问非静态成员必须通过对象名。因此通过静态函数成员访问非静态成员是相当麻烦的,一般情况下,它主要用来访问同一个类中的静态数据成员,维护对象之间共享的数据。
#include
using namespace std;
class A {
public:
static void f(A a);
private:
int x;
};
void A::f(A a) {
cout << x;//错误!
cout << a.x;
}
类的友元
友元关系提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。通俗来说,友元关系就是一个类主动声明哪些其他类或函数是它的朋友,进而给它们提供对本类的访问特许。从一定程度上来讲,友元是对数据隐蔽和封装的破坏。
友元类的所有成员函数都自动成为友元函数。
友元函数是在类中用关键字friend修饰的非成员函数。友元函数可以是一个普通的函数,也可以是其他类的成员函数。虽然它不是本类的成员函数,但是在它的函数体中可以通过对象名访问类的私有和保护成员。
#include
#include
using namespace std;
class Point {
public:
Point(int x=0,int y=0):x(x),y(y){}
int getX() { return x; }
int getY() { return y; }
friend float dist(Point &p1, Point &p2);//友元函数声明
private:
int x, y;
};
float dist(Point &p1, Point &p2) {//友元函数实现
double x = p1.x - p2.x;
double y = p1.y - p2.y;
return (float)(sqrt(x*x + y*y));
}
int main() {
Point p1(1, 1), p2(4, 5);
cout << "The distance is:" << dist(p1, p2) << endl;
return 0;
}
若A类为B类的友元类,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护成员。声明的语法形式为:
class B{
...
friend class A;//声明A为B的友元类
...
};
#include
using namespace std;
class A {
public:
void display() { cout << x << endl; }
int getX() { return x; }
friend class B;//B类是A类的友元类
private:
int x;
};
class B {
public:
void set(int i);
private:
A a;
};
void B::set(int i) {
a.x = i;//由于B是A的友元,所以在B的成员函数中可以访问A类对象的私有成员
}
关于友元,还有几点需要注意:
(1)友元关系是不能传递的。
(2)友元关系是单向的。
(3)友元关系是不能继承的。
对于既需要共享又需要防止改变的数据应该声明为常量。常量的重要特性:必须初始化,而且不能被更新。
它的数据成员值在对象的整个生存期间不能被改变;不能通过常对象调用普通的成员函数。
不会改变目的对象的数据成员的值;常、非常数据都可用;目的对象都被视为常对象。
任何函数都不能对其赋值,只能在初始化列表中初始化。
引用的对象不能更新,且自动化为常对象;非const引用只能绑定普通对象,const引用都可以绑定。
常引用做形参:为了追求高效率,且不双向传递。对于在函数中无需改变其值的参数,不宜使用普通引用方式传递,因为那会使得常对象无法被传入,采用传值方式或传递常引用的方式可避免这一问题;复制构造函数的参数一般也宜采用常引用传递。
在规模较大的项目中,往往需要多个源程序文件,每个源程序文件称为一个编译单元,C++语法要求一个类的定义必须出现在所有使用该类的编译单元中,一般将类的定义写在头文件中,私用该类的编译单元则包含这个头文件。通常一个项目至少划分为3个文件:类定义文件(*.h文件)、类实现文件(*.cpp文件)、类使用文件(*.cpp主函数文件)。
指令include有两种书写方式:
#include<文件名>表示按照标准方式搜索要嵌入的文件,该文件位于编译环境的include子目录下。一般要嵌入系统提供的标准文件时采用这样的方式,如对标准头文件iostream的包含。
#include"文件名"表示首先在当前目录下搜索要嵌入的文件,如没有,再按照标准方式搜索。一般对用户自己编写的文件采用这种方式。
决定一个声明放在源文件还是头文件中的一般原则是:将需要分配空间的定义放在源文件中,而将不需要分配空间的声明放在头文件中。
即除了在定义它的源文件中可以使用外,还能被其他文件使用的变量。命名空间作用域中定义的变量(全局变量)默认都是外部变量,但在其他文件中如果需要使用这一变量,需要用extern关键字加以声明。
//源文件1
int i=3;
//源文件2
extern int i;//声明一个在其他文件中定义的外部变量i
void fun(){
i++;
}
对外部变量的声明可以是定义性声明,即在声明的同时定义(分配内存、初始化),也可以是引用性声明(引用在别处定义的声明)。在命名空间作用域中,不用extern关键字声明的变量,都是定义性声明;用extern关键字声明的变量,如果同时指定了初值,则是定义性声明,否则是引用性声明。
在所有类之外声明的函数,都是具有命名空间作用域的。
用 #define来定义符号常量,如:
#define PI 3.1415926
用 #define定义空符号,如:
#define MYHEAD_H
定义它的目的,仅仅是表示“MYHEAD_H已经定义过”这样一种状态。
#undef的作用是删除由 #define定义的宏,是指不再起作用。
可以限定程序中的某些内容要在满足一定条件的情况下才参与编译。
它是一个预处理操作符,而不是指令,因此不要以#开头。使用形式为:
defined(标识符)
若“标识符”在此前经 #define定义过,并且未经 #undef删除,则上述表达式为非0,否则为0。