• 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;
    }
  • 相关阅读:
    单调栈问题解析
    Linux进程状态切换
    Shell编程小例子
    Linux之shell编程
    树的遍历框架
    你真的会求1-100有多少个素数吗
    java实现LRU算法
    从上到下打印二叉树 III
    从上到下打印二叉树I
    模拟盘电路板调试过程中出现的问题
  • 原文地址:https://www.cnblogs.com/alec7015/p/12445329.html
Copyright © 2020-2023  润新知