zoukankan      html  css  js  c++  java
  • 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;
    }
  • 相关阅读:
    PAT Advanced 1067 Sort with Swap(0, i) (25分)
    PAT Advanced 1048 Find Coins (25分)
    PAT Advanced 1060 Are They Equal (25分)
    PAT Advanced 1088 Rational Arithmetic (20分)
    PAT Advanced 1032 Sharing (25分)
    Linux的at命令
    Sublime Text3使用指南
    IntelliJ IDEA创建第一个Groovy工程
    Sublime Text3 安装ftp插件
    Sublime Text3配置Groovy运行环境
  • 原文地址:https://www.cnblogs.com/alec7015/p/12445329.html
Copyright © 2011-2022 走看看