Contents

c++(five)

C++程序的结构

作用域与可见性

  1. 作用域

    作用域讨论的是标识符的有效范围;

  2. 可见性

    可见性是标识符是否可以引用的问题

作用域

作用域是一个标识符在程序正文中有效的区域。

C++的作用域

  1. 函数原型作用域
  2. 块作用域(局部作用域)
  3. 类作用域
  4. 命名空间作用域
  5. 文件作用域

函数原形作用域

  1. 函数原型中的参数,其作用域始于“(”,结束于")"。

  2. 例如,设有下列原型声明:

    double Area(double width, double length); 
    
    length = 50.0;   //错误
    // width,length的作用域仅在于此,不能用于程序正文其它地方,因而可有可无
    
  3. 等价于以下函数原型声明:

    double Area(double , double );
    

块作用域

  1. 标识符的声明出现在一对花括号括起来的一段程序块内,其作用域从声明处开始,一直到到块结束处为止。例如:

    void fun(int a)
    {  int b(a);
       cin>>b;  //b的作用域
       if (b>0)
       {
         int c;  //c的作用域
         ......
       }
       cout << c;  //错误
       cout << b;  //正确
     } 
    

类作用域

  1. 类的作用域是指类定义和相应的成员函数的定义范围。一个类的所有成员位于这个类的作用域内。在该范围内,一个类的成员函数对数据成员具有无限制的访问权
  2. 在类的作用域外对数据成员的访问是受限制的,这就是类的封装作用
  3. 把类看成是一组有名成员的集合,除了个别例外情况外,类的作用域作用于特定的成员名
  4. 类X的一个成员m 在下列情况下局部于类X,或者说具有类作用域
    1. m出现在类X的成员函数内,该成员函数中没有声明同名的局部作用域的标识符,那么在函数内可以直接访问成员m。
    2. 在x.M这样的表达式中,其中x为X类的对象。
    3. 在prt->m这样的表达式中,其中prt为指向X类的一个对象的指针
    4. 在X::m这样的表达式中用于访问类的静态成员
class  Myclass
{
 public:
	void  f1 ( )  {  m = 5 ; }
	void  f2 ( ) 
	{
	    int   m;
	    m = 2;       //Myclass::m被屏蔽
	 }    
int getm() 
    {
        return m;
     }
 private:
	int m;
};
void main()
{
     Myclass  c;
     
     c.m = 10 ;    //错误
    c.f1 ( );
     cout << c.getm ( ) << endl;
    c.f2 ( );
     cout << c.getm ( ) << endl;
}

//运行结果
// 5
// 5

命名空间作用域

  1. 一个大型的程序通常由不同模块构成,不同的模块甚至有可能是由不同人员开发的。不同模块中的类和函数之间有可能发生重名,这样可能引发错误,要解决这类歧义问题就必须使用到命名空间

  2. namespace ns1 //指定命名中间nsl 
    { 	int a 
    	double b; 
    } 
    
  3. 一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的变量,函数都属于该命名空间作用域。如果要引用,则用命名空间::标识符名

  4. namespace ns1 //指定命名空间nsl 
    { 	int a 
    	double b; 
    } 
        ns1::a
    
  5. 还有以下两种都是将标识符暴露在当前的作用域。

    1. using  命名空间::标识符名。
      
    2. using  命名空间
      
  6. 命名空间可以嵌套。

#include <iostream>
using namespace std;
namespace first
{  int x = 5;
  int y = 10;
}
namespace second
{  double x = 3.1416;
  double y = 2.7183;
}
int main () {
  using first::x;
  using second::y;
  cout << x << endl;
  cout << y << endl;
  cout << first::y << endl;
  cout << second::x << endl;
  return 0;
}
#include <iostream>
namespace first 
{    int a=10;
    int b=20;
    namespace second
    {   
        double a=1.02;
        double b=5.002;
        void hello();
    }   
    void second::hello()
    {   
    std::cout <<"hello world"<<std::endl;
    }}
int main()
{    using namespace first;
    std::cout<<second::a<<std::endl;
    second::hello();
}

全局命名空间和匿名命名空间

注:在匿名命名空间中声明的名称也将被编译器转换,与编译器为这个匿名命名空间生成的唯一内部名称绑定在一起

例:

  namespace __UNIQUE_NAME_ 
    {
         char c;
         int i;
         double d;
     }
     using namespace __UNIQUE_NAME_;
#include <iostream>
using namespace std;
int i;	//在全局命名空间中的全局变量
namespace Ns {
	int j;	//在Ns命名空间中的全局变量}
int main() {
	i = 5;	//为全局变量i赋值
	Ns::j = 6;	//为全局变量j赋值
	{using namespace Ns; //使得在当前块中可以直接引用Ns命名空间的标识符
		int i;		//局部变量,局部作用域
		i = 7;
		cout << "i = " << i << endl;//输出7
		cout << "j = " << j << endl;//输出6
	}
	cout << "i = " << i << endl;	//输出5
	return 0;}

文件作用域

  1. 不在前述各个作用域中出现的声明,具有文件作用域(全局作用域),这样声明的标识符的作用域开始于声明点,结束于源文件尾
  2. 程序编译的单位是源文件,包含多个函数。全局变量为本文件中所有函数所共用
  3. 具有文件作用域的变量称为全局变量

例:

int p=1, q=5;    /*全局变量*/  //范围:从int p=1, q=5;  到结尾
float f1 (int a)
{   int b, c;
       
}
char  c1, c2;      /*全局变量*/  //范围:从char  c1, c2;  到结尾
char  f2 (int x, int y)
{   int i,  j;
        } 
main()
{   int m, n;
         }
#include <iostream.h>
int  i = 2,  j=3;      //文件作用域
void main 
{   int  i = 5;     //块作用域
     {  int   i;      //块作用域
         i = 7;
cout<<i, j= <<i << j <<endl;  
     }
     cout<<i, j = <<i << j;   
}
void  func ( )
{    . . .
      cout << i, j = << i << j;
}

可见性

  1. 可见性是从对标识符的引用的角度来谈有效性

  2. 可见性表示从内层作用域向外层作用域“看”时能看见什么

  3. 在某一点能够引用到的标识符,就是该处可见的标识符

    https://cdn.jsdelivr.net/gh/adan-ning/images/202404061355198.png

  4. 作用域可见性的一般规则

    1. 标识符应声明在先,引用在后。
    2. 在同一作用域中,不能声明同名的标识符
    3. 如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层可见
    4. 对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见,即内层屏蔽
    5. 在没有互相包含关系的不同的作用域中声明的同名标识符互不影响
    int   id = 3;      //文件作用域
    void main 
    {   
         id = 5;
         {  
             int   id;
             id = 7;
       cout<<id= <<id << endl;  //输出7
         }
         cout<<id = "<< id << j;   //输出5
    }
    

对象的生存期

对象从诞生到结束的这段时间就是它的生存期。在对象生存期内,对象将保持它的状态(State, 即数据成员的值),直到被更新为止

对象的生存期可以分为静态生存期和动态生存期两种。

静态生存期

如果对象的生存期与程序的运行期相同,我们称它具有静态生存期。

  1. 这种生存期与程序的运行期相同

  2. 在文件作用域中声明的对象具有这种生存期

  3. 在函数内部声明静态生存期对象,要冠以关键字static ,称为静态变量

    例:

    static  int   i ; 
    

例:

#include <iostream.h>
int i = 5;               //文件作用域
int main( )
{
     cout<<"i="<< i <<endl;
     return 0;
}

//i 具有静态生存期

动态生存期

  1. 块作用域中声明的对象是动态生存期的对象(习惯称局部生存期对象)
  2. 动态生存期程序诞生于声明点,结束于该标识符的作用域结束处
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.h>
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:
	globClock.ShowTime( ); //对象的成员函数//具有类作用域
	globClock.SetTime(8,30,30);	
    Clock   myClock (globClock);   //声明具有块作 //用域的对象myClock
	cout<<"Second time output:"<<endl;	
	myClock.ShowTime ;	//引用具有块作用//域的对象myClock
}
//程序的运行结果为:
//First time output:
//0:0:0
//Second time output:
//8:30:30

数据与函数

**“数据结构+算法=程序设计”,它是结构化程序设计的基础。数据的组织就是数据结构,函数是来实现算法的。 **

  1. 数据存储在局部对象中,通过参数传递实现共享——函数间的参数传递。
  2. 数据存储在全局对象中。可以被整个程序中所有函数共享
  3. 在面向对象程序设计中,数据描述的是对象的属性、状态
  4. 函数是算法的实现,是用来处理数据、改变对象状态的
  5. 将数据和使用数据的函数封装在类中。类的数据成员被类的成员函数共享
#include<iostream.h>
int global;
void f( )
{  global=5;}
void g( )
{  cout<<global<<endl;}
int main( )
{  f( );
   g( );      //输出“5”
   return 0;
}
#include<iostream.h>
class Application
{ public:
     void f ; void g ;
  private:
     int global;
};
void Application::f 
{  global=5;}
void Application::g 
{  cout<<global<<endl;}
int main 
{
  Application  MyApp;
   MyApp.f ;
   MyApp.g ;
   return 0;
}

局部变量

  1. 局部变量:就是具有块作用域的变量
  2. 如果把数据存储在局部变量中,函数在不同的块之间只能通过参数传递来共享数据

全局变量

  1. 全局变量:具有文件作用域
  2. 在整个程序中,除了在定义有同名局部变量的块中之外,都有可以进行直接访问。将数据存放在全局变量中,不同的函数在不同的地方对同一全局变量进行访问,就实现了函数之间的数据共享
#include<iostream.h>
int global;
void f( )
{  global=5;}
void g( )
{  cout<<global<<endl;}
int main( )
{  f( );
   g( );      //输出“5”
   return 0;
}

数据成员

  1. 类的成员
    1. 数据成员
    2. 函数成员
  2. 类中的数据成员可以被同一类中任何一个函 数访问
  3. 在类内部的函数之间实现了数据的共享,另一方面这种共享是受限制的,可以设置适当的访问控制属性,把共享只限制在类的范围之内,这样对类外来说,类的数据成员仍然是隐藏的,达到了共享与隐藏两全
#include<iostream.h>
class Application
{ public:
     void f ; void g ;
  private:
     int global;
};
void Application::f 
{  global=5;
}
void Application::g 
{  cout<<global<<endl;
}
int main 
{
  Application  MyApp;
   MyApp.f ;
   MyApp.g ;
   return 0;
}
#include<iostream.h>
class Clock	//时钟类声明
{
public:	//外部接口
void SetTime(int NewH, int NewM, int NewS);   
      //三个形参均具有函数原型作用域
  void ShowTime( );
private:	//私有数据成员
	int Hour,Minute,Second;
}; //在类的成员函数之间共享

类的静态成员

  1. 静态成员的引用

    例:抽象出某公司全体雇员的共性,设计如下雇员类:

    class  employee
    {
     private:
       int EmpNo;
       int ID;
       char *name;  //以字符指针指向字符串首地址
            // 其他数据成员与函数成员略
    }
    

静态数据成员

  1. 实例属性

  2. 类属性

    描述类的所有对象的共同特性的一个数据项,对于任何对象实例,它的属性值是相同的。在C++语言中是通过静态数据成员来实现“类属性”的

  3. 用关键字static声明:在类的定义中仅仅对静态数据成员进行引用性声明,必须在文件作用域的某个地方使用类名限定定义性声明,这时也可以进行初始化。

  4. 该类的所有对象维护该成员的同一个拷贝

  5. 必须在类外定义和可以进行初始化,用(::)来指明所属的类

  6. 一般通过类名进行访问, 类名::标识符。也可以通过对象名.标识符访问。(这两种方式都必须是公开权限)

include <iostream>
using namespace std;
class A
{
public:
	static int x;
};
int A::x;
void main()
{
	cout<<A::x<<endl;
	A a;
	cout<<a.x<<endl;
}

例5-4 具有静态数据成员的 Point类

#include < iostream.h>
class 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;}
	  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 :: GetC ( );  //错误
    Point  A(4,5);	
	cout<<"Point A,"<<A.GetX  
                                        <<","<<A.GetY ;
	A.GetC ;	
	Point  B(A);	
	cout<<"Point B,"<<B.GetX <<","<<B.GetY ;
	B.GetC ;	
}    

静态成员函数

  1. 可以使用类名或对象名来调用公有的静态成员函数。

  2. 对普通成员函数只能通过对象名来调用

  3. 静态成员函数

    使用static关键字声明的函数成员

  4. 静态成员函数可以直接访问该类的静态(数据和函数)成员;而访问非静态(数据和函数)成员,要访问非静态成员必须通过参数传递方式得到对象名,然后通过对象名来访问

静态成员函数举例

#include<iostream.h>
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;}
void main 
{
 Application::f ;
 Application::g ;
 }
  1. 静态成员属于类,非静态成员属于对象
  2. 静态成员函数只能引用属于该类的静态数据成员或静态成员函数。
  3. 由于静态成员不是对象成员,所以在静态成员函数 f( ) 的实现中不能直接引用类中声明的非静态成员
  4. 在 f( ) 的实现中,可以通过对象 a 来访问 x —— a.x
class A
{ public:
        static void f(A a);
    private:
        int x;
};
void A::f(A a)
{
    cout<<x;        //对x的引用是错误的
    cout<<a.x;    //正确
}

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

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( );	//输出对象号,类名引用
}

友元

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

友元函数

  1. 友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问privateprotected 成员。
  2. 可以是一个普通函数,或其他类的成员函数,不是本类的成员函数。
  3. 作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。
  4. 访问对象中的成员必须通过对象名

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

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

友元类

  1. 若A类为B类的友元类,则A类的所有成员函数都能访问B类的私有成员和保护成员。
  2. 声明语法:将友元类名在另一个类中使用friend修饰说明。

一般的语法形式:

 class B
     {
        ……   //B类的成员声明
  friend class A ;  //声明A为B 的友元类
  ……
   }

通过友元类声明,友元类的成员函数可以通过对象名直接访问到隐藏的数据,达到高效协调工作的目的

友元类举例:

class A
{   
public:
     void Display    {cout<<x<<endl;}
     friend class B; 
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 ;
}

共享数据的保护

  1. 常量及格式:

    1. const <类型说明符> <变量名> = <常量或常量表达式>;

    2. <类型说明符> const <变量名> = <常量或常量表达式>;

      当一个变量被const修饰后,具有以下几个特点:

      1. 该变量只能读取不能修改。(编译器进行检查)

      2. 定义时必须初始化。

      3. C++中喜欢用const来定义常量,取代原来C风格的预编译指令define。

            const int var; // Error:常量 变量"var"需要初始化设定项
            const int var1 = 42;
            var1 = 43; // Error:表达式必须是可以修改的左值
        
  2. 常量

    注意,在使用const变量作为数组的下标时,变量的值一定要是一个常量表达式(在编译阶段就能计算得到结果)。

    const   int   sz = 42;
    int   iAr[sz];
    const  int  sz1 = size(); // size()必须是一个返回常量的函数
    int  iAr1[sz1];
    int  var = 42;
    const  int  sz2 = var;
    int  iAr2[sz2]; // error:sz2只有运行时才知道值
    

常引用

声明或定义的格式如下:

const <类型说明符> &<变量名> = …… [1]
<类型说明符> const &<变量名> = …… [2]

const引用就是指向const对象或常变量的引用。

普通引用不能绑定到const对象或常变量,但const引用可以绑定到非const 对象或常变量。

const int ii = 456;     
int &rii = ii; // error
int jj = 123; 
const int &rjj = jj; // ok

非const引用只能绑定到与该引用同类型的对象或变量。

const 用则可以绑定到不同但相关的类型的对象或绑定到右值。

例如:

const int &r = 100;//绑定到字面值常量
int i = 50;
const int &r2 = r + i; // 引用 r 绑定到右值
double dVal = 3.1415;
const int &ri = dVal; // 整型引用绑定到 double 类型编译器会把以上代码转换成如下形式的编码:
int temp = dVal; // create temporary int from double
const int &ri = temp; // bind ri to that temporary

常对象

数据成员的值在整个生存期内不能被改变。

常对象必须进行初始化,不能被更新。

定义常对象的一般形式为:

类名    const    对象名(实参列表);
const    类名    对象名(实参列表);
Time const t1(12,34,36); //定义t1为常对象

在所有的场合中,对象t1中的所有数据成员的值都不能被修改。凡希望保证数据成员不被改变的对象,可以声明为常对象。

如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式构造函数和析构函数)。

例: 常引用做形参

void display ( const  double& r );
void main 
{   double  d ( 9.5 );
     display(d);   
}
void display(const  double&  r)
/*常引用做形参,在函数中不能更新 r所
引用的对象。 */
{  r = 15.5;    //错误  
   cout<<r<<endl;   }

常对象举例

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

在C++的语法中,对基本数据类型的常量提供了可靠的保护。

int const n = 10 ;
     n = 20 ; //错误

用const修饰的对象成员

  1. 用const修饰的声明数据成员称为常数据成员

    有两种声明形式:

    const int  cctwl;
    int const  cctwl;
    

    不能省略数据类型,可以添加 public private等访问控制符。 说明:

    1. 任何函数都不能对常数据成员赋值。(除构造函数
    2. 构造函数对常数据成员进行初始化时也只能通过初始化列表进行。
    3. 常数据成员在初始化时必须赋值或称其必须初始化.
    4. 如果类有多个构造函数必须都初始化常数据成员。
  2. #include <iostream>
    using namespace std;
    class A
    {
    	int w,h;
    	const int cctw=5;//错误一
    };
    void main()
    {
    	A a;  //错误二
    	cout<<"sss";
    }
    

    错误一:不能对常数据成员在类中初始化、要通过类的构造函数。

    错误二:没有合适的默认构造函数可用。因为有常量cctwl没有初始化必须初始化所有常数据成员。

  3. #include <iostream>
    using namespace std;
    class A
    {
    	int w,h;
    	const int cctw;
    public:
    	const int cctwl;
    	A():cctw(5),cctwl(8){};
    };
    
  4. 多个构造函数下的常数据成员

    #include <iostream>
    using namespace std;
    class A
    {
    	int w,h;
    	const int cctw;
    public:
    	const int cctwl;
    	A():cctw(5),cctwl(8){};
    	A(int x,int y){w=x,h=y;}};//错误一
    

    错误一:每个构造函数都要初始化常数据成员,应改为:

    A()(int x,int y):
    cctw(5),cctwl(8)
    {w=x,h=y;}};
    
  5. 常成员函数

    1. 常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。

    2. 常成员函数说明格式

      类型说明符  函数名(参数表)const;
      
    3. const是函数类型的一个组成部分,因此在实现部分也要带const关键字。

    4. const关键字可以被用于参与对重载函数的区分,例:

      void print( );
      void print( ) const;
      
  6. 通过常对象只能调用它的常成员函数

  7. 常数据成员

    1. 使用const说明的数据成员。
    2. 构造函数对常数据成员进行初始化,只能通过初始化列表。
  8. 用const修饰的声明成员函数称为常成员函数:

    1. const是函数类型的一部分,在实现部分也要带该关键字
    2. const关键字可以用于对重载函数的区分
    3. 常成员函数不能更新任何数据成员,也不能调用该类中没有用const修饰的成员函数,只能调用常成员函数。
  9. const是函数类型的一部分,在实现部分也要带该关键字:

    #include <iostream>
    using namespace std;
    class A
    {
    	int w,h;
    public:
    	int getValue() const;
    	int getValue();
    	A(int x,int y)
    	{w=x,h=y;	}
    };
    int A::getValue() const
    {return w*h;}
    int A::getValue()
    {return h;}
    void main()
    {
    	A const a(3,4);
    	A c(2,6);
    	cout<<a.getValue()<<"    "<<c.getValue();
    }
    

常成员函数举例:

#include<iostream.h>
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是常对象
     b.print( );    //调用void print( ) const
}
//运行结果
//5:4
//20:52
#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;  //静态常数据成员在类外声明和初始化
A::A(int i):a(i),r(a)
{}
void A::print()
{
	cout<<a<<":"<<b<<":"<<r<<endl; 
}
void main()
{/*建立对象a1和a2,并以100和0作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员赋初值*/
    A a1(100), a2(0);  
    a1.print();
    a2.print();
}
//运行结果:
//100:10:100
//0:10:0

注意 :

  1. 常成员函数可以被其他成员函数调用
  2. 但是不能调用其他非常成员函数
  3. 可以调用其他常成员函数

https://cdn.jsdelivr.net/gh/adan-ning/images/202404061521617.png

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

C++程序的一般组织结构(多文件结构)

  1. 一个源程序一般至少分为三个源文件:
    1. 类声明文件(.h文件)
    2. 类实现文件(.cpp文件)
    3. 类的使用文件(main( )所在的.cpp文件)
  2. 利用工程来组合各个文件。

例:具有静态数据、函数成员的Point类,多文件组织

//文件1,类的声明,point.h
# include < iostream.h>
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;  //静态数据成员
};

//文件2,类实现,point.cpp
# include “point.h”
Point::Point(Point &p)
{	X=p.X;
	Y=p.Y;
	countP++;
}

//文件3,主函数,fmain.cpp
# include “point.h”
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( );	//输出对象号,类名引用

分析:

https://cdn.jsdelivr.net/gh/adan-ning/images/202404061525459.png

编译预处理命令

  1. #include 指令(文件包含指令)

    1. 将另一个源文件嵌入到当前源文件中该点处

    2. #include <文件名>

      1. 按标准方式搜索,文件位于C++系统目录的include子目录下
    3. #include “文件名”

      1. 首先在当前目录中搜索,若没有,再按标准方式搜索。
    4. #include指令可以嵌套使用

      有一头文件myhead.h,在中又可嵌套文件包含指令:

      #include “file1.h”
      #include “file2.h”
      
  2. #define宏定义指令

    1. 定义符号常量,已被const定义语句取代。

      例:

      #define PI  3.14159
      
    2. 定义带参数宏,已被内联函数取代。

  3. #undef

    1. 删除由#define定义的宏,使之不再起作用
  4. #define定义空符号

    # define LETTER
    
  5. 条件编译指令  

    1. #if 和 #endif

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

       #if   常量表达式
                   //当“ 常量表达式”非零时编译
             程序正文1
      #else
             //当“ 常量表达式”为零时编译
             程序正文2
      #endif
      
      # define LETTER 1
      main( )
      { char str[20] =Clanguage,c;
       int i;
       i = 0;
       While((c = str[i])!=‘\0)
       { i++;
        # if LETTER
        if (c>=a&&c<=z)
            c = c-32;
        # else
        if (c>=A&&c<=Z)
            c = c+32;
        # endif
      
    3. 条件编译指令 #elif

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

      #ifdef 标识符
      	程序正文1
      #else
          程序正文2
      #endif
      
      
      #ifdef 标识符
        程序正文1
      #endif
      

      如果“标识符”经**#defined**定义过,且未经undef删除,则编译程序正文1,否则编译程序正文2。如果没有则编译程序正文2,如果没有程序正文2,则#else可以省略:

      例:

      # ifdef COMPUTER_A
      # define INTEGER_SIZE 16
      # else
      # define INTEGER_SIZE 32
      # endif 
      

      即如果COMPUTER_A在前面已被定义过,则编译命令行:

      # define INTEGER_SIZE 16
      //否则,编译
      # define INTEGER_SIZE 32
      
    5. 条件编译指令 #ifndef

      #ifndef 标识符
      	程序正文1
      #else
          程序正文2
      #endif
      

      如果“标识符”未被定义过,则编译程序正文1,否则编译程序正文2。如果没有程序正文2,则#else可以省略:

      #ifdef 标识符
        程序正文1
      #endif
      
  6. defined(标识符)

    define是一个预处理操作符,而不是指令,因此不要以#开头。

    **格式: **

    define(标识符)
    

    功能:

    若“标识符”在此前经**#defined**定义过,且未经undef删除,则上述表达式为非0,否则上述表达式的值为0。

    例:下面两种写法完全等效

    #ifndef MYHEAD_H     
    #define MYHEAD_H
    ...
    #endif 
    
    
    #if ! Define(MYHEAD_H)
    #define MYHEAD_H
    ...
    #endif
    

    注意:由于文件包含指令可以嵌套使用,在设计程序时要避免多次重复包含同一个头文件,否则会引起变量及类的重复定义

    // main.cpp
    #include “file1.h”
    #include “file2.h”
    void main( )
    {
     ……
    }
    
    //file1.h
    #include “head.h”
    ……
    
    //file2.h
    #include “head.h”
    ……
    //head.h
    ……
    class Point 
    {
    ……
    }
    ……
    
    //head.h
    #ifndef HEAD_H
       #define HEAD_H
          ……
      class Point
      {
         ……
       }
         ……
    #endif