0%

Cpp基础(8)数据的共享与保护

标识符的作用域与可见性

作用域是一个标识符在程序正文中的有效区域。作用域可以分为以下几类:

  • 函数原型作用域:函数原型中的参数,其作用域始于”(“,结束语”)”,如:
1
double area(double radius)
  • 局部作用域:

    • 函数的形参、在块中声明的标识符
    • 其作用域自声明处起,限于块中
  • 类作用域:

    • 类的成员具有类作用域,其范围包括类体和非内联成员函数的函数体。
    • 如果在类作用域以外访问类的成员,要通过类名(访问静态成员),或者该类的对象名、对象引用、对象指针(访问非静态成员)
  • 文件作用域:
    • 不在前述各个作用域中出现的声明,就具有文件作用域,这样声明的标识符其作用域开始于声明点,结束语文件尾。
  • 可见性:
    • 可见性是从对标识符的引用的角度来谈的概念
    • 可见性表示从内层作用域向外层作用域“看”时能看见什么
    • 如果标识在某处可见,就可以在该处引用此标识符
    • 如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见
    • 对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。
1
2
3
4
5
6
7
8
9
10
11
12
13
#include<iostream>
using namespace std;
int i; //全局变量,文件作用域
int main(){
i = 5;
{
int i; //局部变量,局部作用域
i = 7;
cout << "i = " << i << endl; //输出7
}
cout << "i = " << i << endl; //输出5
return 0;
}

对象的生存期

静态生存期

  • 静态生存期与程序的运行期间相同;
  • 在文件作用于中声明的对象具有这种生存期
  • 在函数内部声明静态生存期对象,要冠以关键字static

动态生存期

  • 开始于程序执行到声明点时,结束语命名该标识符的作用域结束处
  • 块作用域中声明的,没有用static修饰的对象时动态生存期的对象(习惯称局部生存期对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
using namespace std;
int i = 1; //i为全局变量,具有静态生存期
void other() {
static int a = 2;
static int b;
//a, b为静态局部变量,具有全局寿命,局部可见
//只第一次进入函数时被初始化
int c = 10; //C为局部变量,具有动态生存期,
//每次进入函数时都初始化
a += 2; i += 32; c += 5;
cout << "---OTHER---\n";
cout << "i: " << i << " a; " << a << " b: " << b << " c: " << c << endl;
b = a;
}

int main() {
static int a; //静态局部变量,有全局寿命,局部可见(实际中尽量不使用重名的变量)
int b = -10; //b,c为局部变量,具有动态生存期
int c = 0;
cout << "---MAIN---\n";
cout << "i: " << i << " a; " << a << " b: " << b << " c: " << c << endl;
c += 8; other();
cout << "---MAIN---\n";
cout << "i: " << i << " a; " << a << " b: " << b << " c: " << c << endl;
i += 10; other();
return 0;
}

类的静态成员

静态成员:在定义前面加了static关键字的成员

1
2
3
4
5
6
7
8
9
10
11
class CRectangle
{
private:
int w,h;
static int nTotalArea;
static int nTotalNUmber; //静态成员变量
public:
CRectangle(int _w,int _h);
~CRectangle();
static void PrintTotal(); //静态成员函数
};

基本概念

  • 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享

​ 注意:sizeof运算符不会计算静态成员变量,如下例中sizeof(Myclass)等于4

1
2
3
4
class Myclass{
int n;
static int s
}
  • 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。
  • 因此静态成员不需要通过对象就能访问

如何访问静态成员

  1. 类名::成员名
1
CRectangle::PrintTotal();
  1. 对象名.成员名(并不意味着静态成员或静态成员函数只作用于该对象,,它们是被所有的CRentangle对象所共享的)
1
CRectangle r;  r.PrintTotal();
  1. 指针->成员名
1
CRectangle *p = &r;  p->PrintTotal();
  1. 引用.成员名
1
CRectangle &ref = r;  int n = ref.nTotalNumber;
  • 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
  • 静态成员函数本质上是全局函数
  • 设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

示例:考虑一个需要随时知道矩阵总数和总面积的图像处理程序,可以用全局变量来记录总数和总面积,同静态成员将这两个变量封装进类中,就更容易理解和维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<iostream>
using namespace std;

class CRectangle
{
private:
int w, h;
static int nTotalArea;
static int nTotalNUmber; //静态成员变量
public:
CRectangle(int _w, int _h);
~CRectangle();
static void PrintTotal(); //静态成员函数
};
CRectangle::CRectangle(int _w, int _h)
{
w = _w;
h = _h;
nTotalNUmber++;
nTotalArea += w * h;
}
CRectangle::~CRectangle()
{
nTotalNUmber--;
nTotalArea -= w * h;
}
void CRectangle::PrintTotal()
{
cout << nTotalNUmber << ", "<<nTotalArea << endl;
}

int CRectangle::nTotalNUmber = 0;
int CRectangle::nTotalArea = 0;
//必须在定义类的文件中对静态成员变量进行一次说明或初始化。
//否则编译能通过,链接不能通过

int main()
{
CRectangle r1(3, 3), r2(2, 2);
//cout<<CRectangle::nTotalNUmber; //Wrong,私有成员不能在类外访问
CRectangle::PrintTotal();
r1.PrintTotal(); //与上一句等价
return 0;
}

注意事项

  • 在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数(因为其可能访问非静态成员变量),例如下面的定义就是错误的。
1
2
3
4
void CRectangle::PrintTotal()
{
cout << w <<", "<< nTotalNUmber << ", "<<nTotalArea << endl;
} //Wrong,因为PrintTotal是静态成员函数,而w属于非静态成员变量

我们回过头来再看之前的CRectangle类的写法,其实它是有严重缺陷的,那么这个缺陷是如何产生的呢?

问题就出在我们忽略了复制构造函数,在程序需要它时,会调用自动生成的复制构造函数,自然就不会对nTotalNUmbernTotalArea作相应的增加。

  • 在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象

    • 调用一个以CRectangle类对象作为参数的函数时
    • 调用一个以CRectangle类对象作为返回值的函数时
  • 临时对象在消亡时会调用析构函数,减少nTotalNUmbernTotalArea的值,可是这些临时对象在生成时却没有增加nTotalNUmbernTotalArea的值。

解决办法:为CRectangle类写一个复制构造函数

1
2
3
4
5
6
CRectangle::CRectangle(CRectangle &r)
{
w=r.w; h=r.h;
nTotalNUmber++;
nTotalArea += w * h;
}

类的友元

  • 友元是C++提供的一种破坏数据封装和数据隐藏的机制
  • 通过将一个模块声明为另一个模块的友元,一个模块能引用到另一个模块中很是被隐藏的信息
  • 可以使用友元函数友元类
  • 为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元

友元函数

  • 友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体重能够通过对象名访问privateprotected成员。
  • 作用:增加灵活性,时程序员可以在封装和快速性方面做合理的选择。
  • 访问对象中的成员必须通过对象名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<iostram>
using namespace std;
class CCar; //提前声明CCar类,因为后面CDriver类要前向引用
class CDriver
{
public:
void ModifyCar(CCar *pCar); //改装汽车
};
class CCar
{
private:
int price;
friend int MostExpensiveCar(CCar cars[], int total); //类外函数声明为友元函数
friend void CDriver::ModifyCar(CCar *pCar); //其他类的成员函数声明为友元函数
};
void CDriver::ModifyCar(CCar *pCar) //求最贵汽车的价格
{
pCar->price += 1000; //汽车改装后价格增加
}
int MostExpensiveCar(CCar cars[], int total)
{
int tmpMax = -1;
for (int i = 0; i < total; ++i)
if (cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}

友元类

  • 若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员
  • 声明语法:将友元类名在另一个类中使用friend修饰说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CCar
{
private:
int price;
friend class CDriver; //声明CDriver为CCar的友元类
};
class CDriver
{
public:
CCar myCar;
void ModifyCar(){ //改装汽车
myCar.price += 1000; //CDriver是CCar的友元类->可以访问其私有成price
}
};

主要注意的是:类的友元关系是单向的

  • 如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数不能访问B类的私有、保护数据,即友元类的关系不能传递,不能继承。

this指针

在C++刚推出的时候,编译器在编译C++程序课程时先把一段C++程序翻译成C程序,然后再用C的编译去编译。比如说我们把下面的CCar的类的C++程序翻译成C程序,class对应C中的struct结构体,而C中没有成员函数,因此需要借助this指针来实现SetPrice的功能。

C++代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class CCar {
public:
int price;
void SetPrice(int p);
};
void CCar::SetPrice(int p) {
price = p;
}
int main() {
CCar car;
car.SetPrice(20000);
return 0;
}

C代码:

1
2
3
4
5
6
7
8
9
10
11
struct CCar {
int price;
};
void SetPrice(struct CCar *this, int p) {
this->price = p;
}
int main() {
struct CCar car;
SetPrice( &car, 20000 );
return 0;
}
  • this指针作用:非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Complex {
public:
double real, imag;
void Print() { cout << real << "+" << imag << "i"; }
Complex(double r,double i):real(r),imag(i) {}
Complex AddOne() {
this->real++;
this->Print();
return *this
}
};
int main() {
Complex c1(1, 1), c2(0, 0);
c2 = c1.AddOne(); //输出 2+1i
return 0;
}
  • 注意静态成员函数不能使用this指针,因为静态成员函数并不具体作用于某个对象。

常量const

  • 常量对象:如果不希望某个对象的植被改变,则定义该对象时在其前面加const关键字。

  • 常量成员函数:在类的成员函数说明后面const关键字

    • 常量成员函数执行期间不应该修改其作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。
    • 若有两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载,而不是冲突定义。
  • 常引用:引用前加const,不能通过常引用,修改其引用的变量;常引用经常作为函数的参数

    • 当传递一个对象的效率较低(因为需要调用复制构造函数生成一个新的对象),又要确保实际参数的值不能在函数内部被修改时,可以将参数的类型声明为常引用
1
2
3
4
5
6
7
8
class Rectangle{
public:
int w, h;
};
int getArea(const Rectangle &rect){
//rect.w = rect.h + 2; 非法
return rect.w * rect.h;
}