C++ 第5章 数据的共享与保护

 

5.1 标识符的作用域与可见性

5.1.1 作用域

函数原形的作用域

  • 函数原型中的参数,其作用域始于 "(",结束于")"。
  • 例如:double area(double radius);  标识符radius的作用(或称有效)范围就在函数area形参列表的左右括号之间.
    在程序的其他地方不能引用这个标识符。因此标识符radius的作用域称做函数原型作用域。

块作用域(局部作用域):在块中声明的标识符,其作用域自声明处起,限于块中,例如:


类作用域:

  • 类作用域作用于特定的成员名。
  • 类X的成员M具有类作用域,对M的访问方式如下:
    • 如果在X的成员函数中没有声明同名的局部作用域标识符,那么在该函数内可以访问成员M。
    • 通过表达式x.M或者X::M访问。
    • 通过表达式prt->M

文件作用域:

  • 不在前述各个作用域中出现的声明,具有文件作用域
  • 这样声明的标识符的作用域开始于声明点,结束于文件尾。

命名空间作用域:具有命名空间作用域的变量也称为全局变量。

5.1.2 可见性

  • 可见性是从对标识符的引用的角度来谈的概念
  • 可见性表示从内层作用域向外层作用域“看”时能看见什么。
  • 如果标识在某处可见,则就可以在该处引用此标识符。
  • 标识符应声明在先,引用在后。
  • 如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见。
  • 对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。

 同一作用域中的同名标识符:

  • 在同一作用域内的对象名、函数名、枚举常量名会隐藏同名的类名或枚举类型名。
  • 重载的函数可以有相同的函数名。

例:作用域与可见性

#include<iostream>
using namespace std;
int i;   //文件作用域
int main()
{   i=5;
     {  int i;  //块作用域
         i=7;
         cout<<"i="<<i<<endl;  //输出7
     }
     cout<<"i="<<i;   //输出5
     return 0;
}

5.2 对象的生存期

5.2.1 静态生存期

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

5.2.2 动态生存期(局部生存对象)

  • 块作用域中声明的,没有用static修是的对象是动态生存期的对象(习惯称局部生存期对象)。
  • 开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。
#include<iostream>
using namespace std;
void fun();
void main()
{   fun();
     fun();
}
void fun()
{   static int a=1;
     int i=5;
     a++;
     i++;
     cout<<"i="<<i<<",a="<<a<<endl;
}

运行结果:

i=6, a=2
i=6, a=3
i是动态生存期
a是静态生存期
例:变量的生存期与可见性

#include<iostream>
using namespace std;
int i=1;  // i 为全局变量,具有静态生存期。

void other(void)
{
  static int a=2;
  static int b;
       // a,b为静态局部变量,具有全局寿命,局部可见。
       //只第一次进入函数时被初始化。
  int c=10;   // C为局部变量,具有动态生存期,
                    //每次进入函数时都初始化。
  a=a+2; i=i+32; c=c+5;
  cout<<"---OTHER---
";
  cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;
  b=a;
}

void main(void)   
{ static int a;  // 静态局部变量,有全局寿命,局部可见。 没有初始化默认为0
   int b=-10;  // b, c为局部变量,具有动态生存期。
  int c=0;
  void other(void);
  cout<<"---MAIN---
";
  cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;
  c=c+8;  other();
  cout<<"---MAIN---
";
  cout<<" i: "<<i<<" a: "<<a<<" b: "<<b<<" c: "<<c<<endl;
  i=i+10; other();  
}

运行结果:

---MAIN---
 i: 1 a: 0 b: -10 c: 0
---OTHER---
 i: 33 a: 4 b: 0 c: 15
---MAIN---
 i: 33 a: 0 b: -10 c: 8
---OTHER---
i: 75 a: 6 b: 4 c: 15

例:具有静态和动态生存期对象的时钟程序

#include<iostream>
using namespace std;
class Clock    //时钟类声明
{public:    //外部接口
    Clock();
    void SetTime(int NewH, int NewM, int NewS);   //三个形参均具有函数原型作用域
    void ShowTime();
    ~Clock(){}
private:    //私有数据成员
    int Hour,Minute,Second;
};
//时钟类成员函数实现
Clock::Clock()    //构造函数
{    Hour=0;
    Minute=0;
    Second=0;
}
void Clock::SetTime(int NewH, int NewM, int NewS)
{    Hour=NewH;
    Minute=NewM;
    Second=NewS;
}
void Clock::ShowTime()
{    cout<<Hour<<":"<<Minute<<":"<<Second<<endl;
}
Clock globClock;    //声明对象globClock,
                                //具有静态生存期,文件作用域
void main()    //主函数
{
    cout<<"First time output:"<<endl;    
    //引用具有文件作用域的对象:
    globClock.ShowTime(); //对象的成员函数具有类作用域
    globClock.SetTime(8,30,30);    
    Clock myClock(globClock); 
                       //声明具有块作用域的对象myClock
    cout<<"Second time output:"<<endl;    
    myClock.ShowTime();    //引用具有块作用域的对象
}

程序的运行结果为:

First time output:
0:0:0
Second time output:
8:30:30

5.3 类的静态成员

5.3.1 静态数据成员

  • 用关键字static声明
  • 静态数据成员是一个类的所有对象共同维护和拥有的一个成员,不是该类的每一个对象所独有的
  • 必须在类外定义和初始化用(::)来指明所属的类

例:具有静态数据成员的Point类

#include <iostream>
using namespace std;
class Point    //point类定义
{public:    
    Point(int x=0, int y=0):x(x),y(y) {//构造函数
        count++;  //在构造函数中对count累加,所有对象共同维护一个count
    } 
    Point(Point &p){//复制构造函数
        x=p.x; y=p.y; count++;
    }
    ~Point(){count--;}    
    int getX() {return x;}
    int getY() {return y;}
    void showCount() {//输出静态数据成员
         cout<<" Object id="<<count<<endl;
    }
private:    
    int x,y;
    static int count;//静态数据成员声明,用于记录点的个数
};

int Point::count=0; //静态数据成员定义和初始化,使用类名限定

void main()    
{    Point a(4,5);    //定义对象a,其构造函数会使count增1
    cout<<"Point A,"<<a.getX()<<","<<a.getY();
    a.showCount();    
    Point b(a);    //定义对象b,其构造函数会使count增1
    cout<<"Point B,"<<b.getX()<<","<<b.getY();
    b.showCount();    
}

上面例子有一个问题当你没有构造点的时候count=0,但是你没有办法在主函数中查到当前count的值。showCount没有对象,无法引用。

5.3.2 静态函数成员

  • 类外代码可以使用类名和作用域操作符来调用静态成员函数。
  • 态成员函数只能引用属于该类的静态数据成员或静态成员函数。
#include<iostream>

using namespace std;
class Application
{ public:
     static void f(); 
     static void g();
  private:
     static int global;
};
int Application::global=0;

void Application::f()
{
   global=5;
}
void Application::g()
{
   cout<<global<<endl;
}

int main()
{
   Application::f();
   Application::g();
   return 0;
}
class A
{
    public:
        static void f(A a);
    private:
        int x;
};
void A::f(A a)
{
    cout<<x; //对x的引用是错误的
    cout<<a.x;  //正确
}
//具有静态数据、函数成员的Point类

#include <iostream>
using namespace std;
class Point    //Point类声明
{public:    //外部接口
    Point(int xx=0, int yy=0) {X=xx;Y=yy;countP++;}    
    Point(Point &p);    //拷贝构造函数
    int GetX() {return X;}
    int GetY() {return Y;}
    static void GetC()
        {cout<<" Object id="<<countP<<endl;}    
private:    //私有数据成员
    int X,Y;
    static int countP;
}

Point::Point(Point &p)
{    X=p.X;
    Y=p.Y;
    countP++;
}
int Point::countP=0;    
void main()    //主函数实现
{    Point A(4,5);    //声明对象A
    cout<<"Point A,"<<A.GetX()<<","<<A.GetY();
    A.GetC();    //输出对象号,对象名引用
    Point B(A);    //声明对象B
    cout<<"Point B,"<<B.GetX()<<","<<B.GetY();
    Point::GetC();    //输出对象号,类名引用
}

5.4 类的友元

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

5.4.1 友元函数

  • 友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和 protected成员
  • 作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。
  • 访问对象中的成员必须通过对象名。

例:使用友元函数计算两点距离

#include <iostream>
#include <cmath>
using namespace std;
class Point    //Point类声明
{ public:    //外部接口
    Point(int xx=0, int yy=0) {X=xx;Y=yy;}
    int GetX() {return X;}
    int GetY() {return Y;}
    friend float Distance(Point &a, Point &b); //友元函数
  private:    //私有数据成员
    int X,Y;
};
double Distance( Point& a, Point& b)
{
      double dx=a.X-b.X;
      double dy=a.Y-b.Y;
      return sqrt(dx*dx+dy*dy);
}
int main()
{  Point p1(3.0, 5.0), p2(4.0, 6.0);
    double d=Distance(p1, p2);
    cout<<"The distance is "<<d<<endl;
    return 0;
}

5.4.2 友元类

  • 若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
  • 声明语法:将友元类名在另一个类中使用friend修饰说明。

例:

class A
{   friend class B; //定义友元类,授权B可以访问A类中的私有成员
    public:
      void Display()
       {cout<<x<<endl;}
    private:
      int x;
}
class B
{   public:
      void Set(int i);
      void Display();
    private:
      A a;
};
void B::Set(int i)
{
   a.x=i;
}
void B::Display()
{
   a.Display();
}

友元关系是单向的

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

5.5 共享数据的保护

常类型:  常类型的对象必须进行初始化,而且不能被更新。

  • 常对象:必须进行初始化,不能被更新。
  • 常成员:常数据成员和常函数成员
  • 常引用:被引用的对象不能被更新。 
  • 常数组:数组元素不能被更新。 
  • 常指针:指向常量的指针。

5.5.1 常对象

常对象必须进行初始化,而且不能被更新。
const  类名   对象名  或  类名   const  对象名

class A
{
     public:
         A(int i,int j) {x=i; y=j;}
                     ...
     private:
         int x,y;
};
A const a(3,4); //a是常对象,不能被更新

5.5.2 用const修饰的类成员

1、常成员函数:使用const关键字修饰的函数
类型说明符 函数名(参数表)const;

#include<iostream>
using namespace std;
class R
{    public:
         R(int r1, int r2){R1=r1;R2=r2;}
         void print();
         void print() const;  //常成员函数 ,重载函数
      private:
         int R1,R2;
};
void R::print()
{     cout<<R1<<":"<<R2<<endl;
}
void R::print() const  //常成员函数,绝不改变成员的状态
{     cout<<R1<<";"<<R2<<endl;
}
void main()
{   R a(5,4);
     a.print();  //调用void print()
     const R b(20,52);  
     b.print();  //调用void print() const
}
#include<iostream>
using namespace std;
class R
{    public:
         R(int r1, int r2){R1=r1;R2=r2;}
         void print();
         void print() const;  //常成员函数 ,重载函数
      private:
         int R1,R2;
};
void R::print()
{     cout<<R1<<":"<<R2<<endl;
}
void R::print() const  //常成员函数,绝不改变成员的状态
{     cout<<R1<<";"<<R2<<endl;
}
void main()
{   R a(5,4);
     a.print();  //调用void print()
     const R b(20,52);  
     b.print();  //调用void print() const
}

2、常数据成员
 

#include<iostream>
using namespace std;
class A
{public:
    A(int i);
    void print();
    const int& r;
private:
    const int a;
    static const int b;   //静态常数据成员
};
const int A::b=10;  //b初始化后,就再也不许改变了
A::A(int i):a(i),r(a) {}
void A::print()
{    cout<<a<<":"<<b<<":"<<r<<endl; }
void main()
{/*建立对象a和b,并以100和0作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员赋初值*/
    A a1(100),a2(0);  
    a1.print();
    a2.print();
}

5.5.3 常引用

被引用的对象不能被更新。相当于是只读的,不能去修改,只能读取。
const  类型说明符  &引用名
例:

#include <iostream>
#include <cmath>
using namespace std;
class Point{//Point类定义
   public//外部接口
       Point(int x=0,int y=0):x(x),y(y){}
       int getX(){return x;}
       int getY(){return y;}
       friend float dist(const Point &p1,const Point &p2);
   private: //私有数据成员
       int x,y;
};
float dist(const Point &p1,const Point &p2){//只能访问,不能修改
    double x=p1.x-p2.x;
    double y=p1.x-p2.y;
    return static_cast<float>(sqrt(x*x+y*y));
}
int main(){
    const Point myp1(1,1),myp2(4,5);
    cout<<"The distance is:";
    cout<<dist(myp1,myp2)<<endl;
    return 0;
}
#include<iostream>
using namespace std;
void display(const double& r);
int main()
{   double d(9.5);
     display(d);
     return 0;
}
void display(const double& r)
//常引用做形参,在函数中不能更新 r所引用的对象。
{   cout<<r<<endl;   }

5.6 多文件结构和编译预处理命令

5.6.1 C++程序的一般组织结构

  • 一个工程可以划分为多个源文件,例如
    • 类声明文件(.h文件)
    • 类实现文件(.cpp文件)
    • 类的使用文件(main()所在的.cpp文件)
  • 利用工程来组合各个文件。

例: 多文件的工程

//文件1,类的定义,Point.h
class Point { //类的定义
public:          //外部接口
       Point(int x = 0, int y = 0) : x(x), y(y) { count++; }
       Point(const Point &p);
       ~Point() { count--; }
       int getX() const { return x; }
       int getY() const { return y; }
       static void showCount();          //静态函数成员
private:         //私有数据成员
       int x, y;
       static int count; //静态数据成员
};
//文件2,类的实现,Point.cpp
#include "Point.h"
#include <iostream>
using namespace std;
int Point::count = 0;    //使用类名初始化静态数据成员
Point::Point(const Point &p) : x(p.x), y(p.y) {
       count++;
}
void Point::showCount() {
       cout << "  Object count = " << count << endl;
}
//文件3,主函数,5_10.cpp
#include "Point.h"
#include <iostream>
using namespace std;
int main() {
       Point a(4, 5);  //定义对象a,其构造函数使count增1
       cout <<"Point A: "<<a.getX()<<", "<<a.getY();
       Point::showCount();      //输出对象个数
       Point b(a);    //定义对象b,其构造函数回使count增1
       cout <<"Point B: "<<b.getX()<<", "<<b.getY();
       Point::showCount();      //输出对象个数
       return 0;
}
 

5.6.2 外部变量与外部函数

外部变量:

  • 如果一个变量除了在定义它的源文件中可以使用外,还能被其它文件使用,那么就称这个变量是外部变量。
  • 文件作用域中定义的变量,默认情况下都是外部变量,但在其它文件中如果需要使用这一变量,需要用extern关键字加以声明。

外部函数:

  • 在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。
  • 这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。也可以在声明函数原型或定义函数时用extern修饰,其效果与不加修饰的默认状态是一样的。
  • 将变量和函数限制在编译单元内
  • 使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。
  • namespace {         //匿名的命名空间
             int n;
             void f() {
                         n++;
             }
       }
    这里被“namespace { …… }”括起的区域都属于匿名的命名空间。

5.6.3 标准C++库

标准C++类库是一个极为灵活并可扩展的可重用软件模块的集合。
标准C++类与组件在逻辑上分为6种类型:

  • 输入/输出类
  • 容器类与抽象数据类型
  • 存储管理类
  • 算法
  • 错误处理
  • 运行环境支持

5.6.4 编译预处理

  • #include 包含指令
    • 将一个源文件嵌入到当前源文件中该点处。
    • #include<文件名>  
      • 按标准方式搜索,文件位于C++系统目录的include子目录下
    • #include"文件名"
      • 首先在当前目录中搜索,若没有,再按标准方式搜索。
  • #define 宏定义指令
    • 定义符号常量,很多情况下已被const定义语句取代。
    • 定义带参数宏,已被内联函数取代。
  • #undef
    • 删除由#define定义的宏,使之不再起作用。

条件编译指令——#if 和 #endif:
#if  常量表达式       //当“ 常量表达式”非零时编译    
   程序正文  
#endif
......

条件编译指令——#else:
#if   常量表达式
             //当“ 常量表达式”非零时编译
       程序正文1
#else
       //当“ 常量表达式”为零时编译
       程序正文2
#endif

条件编译指令——#elif
#if   常量表达式1
       程序正文1  //当“ 常量表达式1”非零时编译
#elif  常量表达式2
       程序正文2  //当“ 常量表达式2”非零时编译
#else
       程序正文3  //其它情况下编译
#endif


条件编译指令
#ifdef   标识符
       程序段1
#else
       程序段2
#endif
如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1,否则编译程序段2。

#ifndef   标识符
       程序段1
#else
       程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。

5.7 综合实例-个人银行账户管理程序
5.8 深度探索


实验课:
clinet.h

clinet.cpp

Client.cpp




 

//文件2,类的实现,Point.cpp
#include "Point.h"
#include <iostream>
using namespace std;
int Point::count = 0;    //使用类名初始化静态数据成员
Point::Point(const Point &p) : x(p.x), y(p.y) {
       count++;
}
void Point::showCount() {
       cout << "  Object count = " << count << endl;
}
原文地址:https://www.cnblogs.com/alec7015/p/12445329.html