数据共享与保护
1. 标识符的作用域
按照由小到大的顺序,作用域分为:函数原型作用域->块作用域->类作用域->文件作用域->命名空间作用域。
函数原型作用域 | 函数原型中的形参表 |
---|---|
局部作用域 | 函数定义的形参、函数内部定义的变量 |
类作用域 | 类体、类外实现的成员函数等 |
文件作用域 | 除上述情况外 |
我们知道,在声明函数原型声明时,可以只给参数类型,而不给参数名,因为在参数列表中不需要使用这个参数名,但是为了完整性可用性,一般还是要加上参数名。
文件作用域起始于定义点,在整个文件结束之前都有效。
2. 标识符的可见性
所谓可见性,是指从内层作用域向外看时,能看到什么。如果该标识符在某处可见,就可以在该处引用该标识符。
层数的划分如下所示:
可见性的规则是:
1. 如果某标识符在外层中声明了,且内层中没有同名标识符,那么该标识符在内层中是可见的;
2. 如果内层中有同名标识符,那么该标识符在内层中被屏蔽,也就不可见。
3. 对象的生存期
对象的生存期,就是从对象诞生开始到它的消亡存在的期间。
3.1 静态生存期
静态生存期是指与程序的运行期相同,也就是说,在程序开始到结束期间,一直存在于内存中。
1. 在文件作用域中生成的对象具有静态生存期;
2. 在函数内部以static关键字声明的对象也具有静态生存期。
3.2 动态生存期
动态生存期是指从对象声明开始,到命名该标识符的作用域的结束处,也就是紧紧包围着它的大括号。
块作用域中声明的,且没有用static关键字标识的对象具有动态生存期。
来看以下示例程序:
//
// main.cpp
// VisableAndLively
//
// Created by Evelyn on 2018/8/7.
// Copyright © 2018年 Evelyn. All rights reserved.
//
#include <iostream>
using namespace std;
//全局变量,具有静态生存期
int i = 1;
void other() {
//静态局部变量,具有静态生存期,局部可见性,
//只在第一次进入函数时被初始化,之后一直存在,
//即使程序执行流程离开其所在的作用域,依然存
//于内存中,下次进入该函数时还能保持原来的值。
static int a = 2, b;
//局部变量,动态生存期,局部可见性,程序执行流
//程离开其所在作用域后就消亡,再次进入时被重新
//初始化
int c = 0;
a += 2;
i += 32;
c += 5;
cout << "------other()----------" << endl;
cout << "i: " << i << " a: " << a << " b: " << b;
cout << " c: " << c << endl;
b = a;
}
int main(int argc, const char * argv[]) {
//静态局部变量,具有静态生存期,局部可见性,静态
//变量有默认初始值,int型是0,char型是''等,而动态
//变量是没有默认初始值,如果不给赋值就引用,会报错。
static int a;
//局部变量,动态生存期,拒不可见
int b = -10;
int c = 0;
cout << "------main()----------" << endl;
cout << "i: " << i << " a: " << a << " b: " << b;
cout << " c: " << c << endl;
c += 8;
other();
cout << "------main()----------" << endl;
cout << "i: " << i << " a: " << a << " b: " << b;
cout << " c: " << c << endl;
i += 10;
other();
return 0;
}
程序的运行结果如下:
------main()----------
i: 1 a: 0 b: -10 c: 0
------other()----------
i: 33 a: 4 b: 0 c: 5
------main()----------
i: 33 a: 0 b: -10 c: 8
------other()----------
i: 75 a: 6 b: 4 c: 5
4. 类的静态成员
4.1 类的静态数据成员
有的时候,类具有的某种属性,不单独属于每一个对象,这时把该属性作为类的对象的属性来存储就不太合适了,c++提供了类的静态数据成员的机制来解决这个问题。
例如之前已经用到过的Point类,假设这个类有一个属性count表示所有点的个数,显然这个属性不适合于作为每一个点的属性,这时我们可以构造一个静态成员count来保存这个属性:
class Point {
public:
//default constructor
Point();
//normal constructor
Point(int x, int y);
//copy constructor
Point(const Point& p);
//析构函数
~Point();
void showCount();
private:
int x, y;
//静态数据成员
static int count;
};
//静态数据成员的初始化必须在类外
int Point::count = 0;
Point::Point(int newX, int newY) {
x = newX;
y = newY;
count++;
};
Point::Point():Point(0, 0) {};
Point::Point(const Point& p) {
x = p.x;
y = p.y;
count++;
cout << "calling copy constructor of point...\n";
};
Point::~Point() {
count--;
};
void Point::showCount() {
cout << "Point number is : " << count << endl;
};
int main(int argc, const char * argv[]) {
Point p1(1, 2), p2(4, 5);
p1.showCount();
p2.showCount();
return 0;
}
我们可以看到,类的动态数据成员和静态数据成员的区别:
1. 每个点都有x值和y值,但是每个点不单独存储count,只在类中存储一份。
2. 动态数据成员在初始化对象时赋值,静态数据成员在类外进行初始化。
4.2 类的静态函数成员
考虑上一个例子,我们知道,类的对象p1和p2都可以访问类的静态成员count,但是当没有定义类的对象时,我们想知道count的值是多少,应该怎么办呢?首先,count是私有成员,无法从外部访问;其次,showCount函数也必须通过对象来调用它,因此,上面的例子是无法完成这个功能的。为了对类的静态数据成员进行操作,c++提供了静态函数成员的机制。
我们对上述程序做简单修改:
class Point {
public:
//default constructor
Point();
//normal constructor
Point(int x, int y);
//copy constructor
Point(const Point& p);
//析构函数
~Point();
//静态函数成员
static void showCount();
private:
int x, y;
//静态数据成员
static int count;
};
class Line {
public:
//default constructor
Line();
//normal constructor
Line(Point p1, Point p2);
//copy constructor
Line(const Line& l);
private:
Point p1, p2;
};
//静态数据成员的初始化必须在类外
int Point::count = 0;
Point::Point(int newX, int newY) {
x = newX;
y = newY;
count++;
};
Point::Point():Point(0, 0) {};
Point::Point(const Point& p) {
x = p.x;
y = p.y;
count++;
cout << "calling copy constructor of point...\n";
};
Point::~Point() {
count--;
};
//static 关键字只能出现在类的声明中,实现时不能添加
void Point::showCount() {
cout << "Point number is : " << count << endl;
};
int main(int argc, const char * argv[]) {
//直接通过类名访问静态函数成员
Point::showCount();
// insert code here...
Point myp1(1, 2), myp2(4, 5);
Point::showCount();
//也可以通过对象名来访问静态函数成员
myp2.showCount();
return 0;
}
5. 类的友元
5.1 友元函数
类的友元函数是指不属于类的成员函数的函数,需要在类声明中使用friend关键字声明,在它的函数体中能够通过对象名访问private和protected成员。其作用是为了增加灵活性,提高效率。
例如,假设有一个功能需要频繁的计算两个点之间的距离,这时候如果使用getX()和getY()就需要不断地在函数之间切换,造成比较大的开销,如果我们定义一个友元函数如下:
class Point {
public:
//default constructor
Point();
//normal constructor
Point(int x, int y);
//copy constructor
Point(const Point& p);
//析构函数
~Point();
static void showCount();
//在类声明中使用friend关键字声明友元函数,参数使用
//引用而非传对象名,因为引用就是指针,也就是效率高
friend double distance(Point& a, Point& b);
private:
int x, y;
//静态数据成员
static int count;
};
class Line {
public:
//default constructor
Line();
//normal constructor
Line(Point p1, Point p2);
//copy constructor
Line(const Line& l);
private:
Point p1, p2;
};
//静态数据成员的初始化必须在类外
int Point::count = 0;
Point::Point(int newX, int newY) {
x = newX;
y = newY;
count++;
};
Point::Point():Point(0, 0) {};
Point::Point(const Point& p) {
x = p.x;
y = p.y;
count++;
cout << "calling copy constructor of point...\n";
};
Point::~Point() {
count--;
};
void Point::showCount() {
cout << "Point number is : " << count << endl;
};
//
double distance(Point &a, Point &b) {
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
int main(int argc, const char * argv[]) {
Point::showCount();
// insert code here...
Point myp1(1, 2), myp2(4, 5);
Point::showCount();
myp2.showCount();
cout << distance(myp1, myp2);
return 0;
}
5.2 友元类
同友元函数类似,友元类就是把一个类声明为另一个类的友元。这样前一个类的所有成员都能访问后一个类的私有成员。
另外,友元关系是单向的。
6. 常类型
6.1 常对象
- 常对象就是使用const关键字声明的类的对象,必须被初始化,并且初始化之后不能改变对象的值。
- 常数据成员是对象的成员,也可以是类(static)的成员,如果是对象的成员,必须在初始化对象时对其赋值,如果是类的成员,需要在类外进行初始化。
- 常函数成员是专门用来处理常对象的函数,在声明时会在后面加const关键字,一个函数如果被声明为常函数,编译器就会对他进行检查,确保其中的操作没有改变对象的属性时才能正确通过编译。
- 常引用实现函数调用时数据传递的机制,即使用引用提高数据传递效率,又使用const限定符保证数据不会双向传递。