《C++面向对象程序设计(第4版)》学习笔记-5

在这里插入图片描述

此份笔记建议在完整阅读郑莉老师、董渊老师、何江舟老师所编写的《C++语言程序设计(第4版)》后食用,风味更佳!
最后,由于本人水平有限,笔记中仍存在错误但还没有被检查出来的地方,欢迎大家批评与指正。


第5章 数据的共享与保护

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

作用域讨论的是标识符的有效范围,可见性讨论的是标识符是否可以被引用。

5.1.1 作用域

1.什么是作用域

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

2.函数原型作用域
  • 函数原型作用域是 C ++ 程序中最小的作用域。
  • 在函数原型声明时形式参数的作用范围就是函数原型作用域。

实例:

double area(double radius);
  • 标识符 radius 的作用(或称有效)范围就在函数 area( ) 形参列表的左右括号之间。
  • 由于在函数原型的形参列表中起作用的只是形参类型,标识符并不起作用,因此是允许省去的。但考虑到程序的可读性,通常还是要在函数原型声明时给出形参标识符。
3.局部作用域
  • 函数形参列表中形参的作用域,从形参列表中的声明处开始,到整个函数体结束之处 为止。
  • 函数体内声明的变量,其作用域从声明处开始,一直到声明所在的块结束的大括号为止。
    • 所谓块、就是一对大括号括起来的一段程序。
  • 具有局部作用域的变量也称为局部变量

实例:

void fun(int a)
{
	int b=a;
    cin >> b;
    if(b>0)
    {
        int c;
        ...
    }
}
4.类作用域

类可以被看成是一组有名成员的集合,类 X 的成员 m 具有类作用域,对 m 的访问方式有如下 3 种:

  • 如果在 X 的成员函数中没有声明同名的局部作用域标识符,那么在该函数内可以直接访问成员 m 。也就是说 m 在这样的函数中都起作用。
  • 通过表达式X.m或者X::m。这正是程序中访问对象成员的最基本方法。X::m 的方式用于访问类的静态成员。
  • 通过 ptr -> m 这样的表达式 ,其中 ptr 为指 向 X 类的一个对象的指针。
5.命名空间作用域
5.1

  一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的、不属于前面所述各个作用域的标识符,都属于该命名空间作用域。

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

5.2

  在命名空间内部可以直接引用当前命名空间中声明的标识符,如果需要引用其他命名空间的标识符,需要使用下面的语法(两种方法):

(1)语法

命名空间名::标识符名

实例:

namespace SomeNs{
    class SomeClass{...};
    void someFun();
}

//这时在其他命名空间中需要引用命名空间someNS中的类SomeClaa或函数someFun()
SomeNS::SomeClass obj1;
SomeNS::someFun();

(2)语法

using 命名空间名::标识符名;
using namespace 命名空间名;
  • 前一种形式将指定的标识符暴露在当前的作用域内,使得在当前作用域中可以直接引用该标识符;
  • 后一种形式将指定命名空间内的所有标识符暴露在当前的作用域内,使得在当前作用域中可以直接引用该命名空间内的任何标识符。
5.3 命名空间允许嵌套
5.4 特殊的命名空间

(1)全局命名空间

  全局命名空间是默认的命名空间 ,在显式声明的命名空间之外声明的标识符都在一个全局命名空间中。

(2)匿名命名空间

  匿名命名空间是一个需要显式声明的没有名字的命名空间,声明方式如下:

namespace{
    匿名命名空间内的各种声明(类声明、函数声明)
}
  • 在包含多个源文件的工程中,匿名命名空间常常被用来屏蔽不希望暴露给其他源文件的标识符。这是因为每个源文件的匿名命名空间是彼此不同的,在一个源文件中没有办法访问其他源文件的匿名命名空间。
5.5 实例
#include<iostream>
using namespace std;
int i;                //在全局命名空间中的全局变量
namespace Ns{
    int j;
}

int main()
{
    i=5;
    Ns::j=6;
    {
        using namespace Ns;     //使得当前块中可以直接引用Ns命名空间的标识符
        int i=7;
        cout << "i=" << i << endl;
        cout << "j=" << j << endl;
    }
    cout << "i=" << i << endl;
    return 0;
}

//运行结果
//i=7
//j=6
//i=5

5.1.2 可见性

在这里插入图片描述

  • 可见性是从对标识符的引用的角度来谈的概念;
  • 可见性表示从内层作用域向外层作用域“看”时能看见什么;
  • 程序运行到某一点,能够引用到的标识符,就是该处可见的标识符。

作用域可见性的一般规则:

  • 标识符要声明在前,引用在后。
  • 在同一作用域中,不能声明同名的标识符。
  • 在没有互相包含关系的不同的作用域中声明的同名标识符,互不影响。
  • 如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见。

另外:

  • 作用域和可见性的原则不只适用于变量名,也适用于其他各种标识符,包括常量名、用户定义的类型名、函数名、枚举类型的取值等。

5.2 对象的生存期

5.2.1 静态生存期

1.什么是静态生存期

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

2.几点了解一下
  • 在命名空间作用域中声明的对象都是具有静态生存期的。
  • 如果要在函数内部的局部作用域中声明具有静态生存期的对象 ,则要使用关键字 static 。
  • 定义时未指定初值的基本类型静态生存期变量 ,会被赋予 0 值初始化,而对于动态生存期变量,不指定初值意味着初值不确定。
3。局部作用域中静态变量的特点

  它并不会随着每次函数调用而产生一个副本,也不会随着函数返回而失效。

  也就是说,当一个函数返回后,下一次再调用时,该变量还会保待上一回的值,即使发生了递归调用,也不会为该变量建立新的副本,该变量会在每次调用间共享。

5.2.2 动态生存期

1.什么是动态生存期

  除了上述两种情况,其余的对象都具有动态生存期。

2.局部生存期对象
  • 在局部作用域中声明的具有动 态生存期的对象,习惯上也称为局部生存期对象。
  • 局部生存期对象诞生于声明点,结束于声明所在的块执行完毕之时。

5.2.3 类的成员对象的生存期

  • 类的成员对象也有各自的生存期。 不用 static 修饰的成员对象,其生存期都与它们所属对象的生存期保持一致。

5.3 类的静态成员

在结构化程序设计中程序模块的基本单位是函数,因此模块间对内存中数据的共享是通过函数与函数之间的数据共享来实现的,其中包括两个途径参数传递和全局变量。

面向对象的程序设计方法兼顾数据的共享与保护。

类中的数据成员可以被同一类中的任何一个函数访问。这样一方面在类内部的函数之间实现了数据的共享,另一方面这种共享是受限制的,可以设置适当的访问控制属性。把共享只限制在类的范围之内,对类外来说,类的数据成员仍是隐藏的,达到了共享与隐藏两全。

对象与对象之间也需要共享数据。

静态成员是解决同一个类的不同对象之间数据和函数共享问题的。

5.3.1 静态数据成员

1.实例属性

一个类的所有对象具有相同的属性” ,是指属性的个数、名称、数据类型相同, 各个对象的属性值则可以各不相同,这样的属性在面向对象方法中称为“实例属性”.

  • 在类的每一个对象中都拥有一个复本;
  • 实例属性正是每个对象区别于其他对象的特征。
2.类属性
  • 某个属性为整个类所共有,不属于任何一个具体对象,该属性就是类属性;采用 static 关键字来声明为静态成员。
  • 静态成员在每个类只有一个副本,由该类的所有对象共同维护和使用,从而实现了同一类的不同对象之间的数据共享。
  • 类属性是描述类的所有对象共同特征的一个数据项,对于任何对象实例,它的属性值是相同的。
3.静态数据成员具有静态生存周期。
4.静态数据成员的访问

(1)类体中(包括类的成员函数)

  直接用变量名访问。

(2)在类外

类名::标识符
5.实例
#include<iostream>
using namespace std;

class Point{
public:
    Point(int xx=0,int yy=0):x(xx),y(yy){
        count++;
    }
    Point(Point &p):x(p.x),y(p.y){
        count++;
    }
    int getX(){
        return x;
    }
    int getY(){
        return y;
    }
private:
    int x,y;
    static int count;     //声明静态数据成员(引用性声明)
};

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

int main()
{
    Point p1(2,4);        //count=1
    Point p2(p1);         //count=2
    return 0;
}
  • 在类的定义中仅仅对静态数据成员进行引用性声明,必须在命名空间作用域的某个地方使用类名限定定义性声明,这时也可以进行初始化。

5.3.2 静态函数成员

1.抛出问题

  在5.3.1的实例中,要输出静态数据成员 count 的一个方式就是在类中设置一个输出函数。但是有一个问题,通过这个方式,要输出 count 只能通过 Point 类的某个对象来调用此函数。那么在所有对象声明之前,count 的是初始值 0,如何输出这个初始值呢?显然由于尚未声明任何对象,无法通过对象来调用此输出函数。由于 count 是为整个类所共有的 ,不属于任何对象,因此我们自然会希望对 count 的访问也不要通过对象。

2.解决问题

  将输出函数设置为静态成员函数。

3.静态成员函数
  • 所谓静态成员函数就是使用 static 关键字声明的函数成员。
  • 同静态数据成员一样,静态成员函数也属于整个类,由同一个类的所有对象共同拥有,为这些对象所共享。
  • 调用方式
    • 静态成员函数可以通过类名或对象名来调用;
    • 非静态成员函数只能通过对象名来调用。
    • 虽然静态成员函数可以通过类名和对象名两种方式调用,但一般习惯于通过类名调用。
4.静态成员函数访问类的静态成员与非静态成员的方式
  • 静态成员函数可以直接访问该类的静态数据和函数成员;
  • 而访问非静态成员,必须通过对象名。
class A{
public:
    static void f(A a);
private:
    int x;
};

void A::f(A a){
    cout << x;       //错误的访问方式
    cout << a.x;     //正确的访问方式
}
  • 可以看到,通过静态函数成员访问非静态成员是相当麻烦的。一般情况下,它主要用来访问同一个类中的静态数据成员,维护对象之间共享的数据。
  • 之所以在静态成员函数中访问类的非静态成员需要指明对象,是因为对静态成员函数的调用是没有目的对象的,因此不能像非静态成员函数那样,隐含地通过目的对象访问类的非静态成员。

5.4 类的友元

一些思考

首先,使用一个 Point 类,每一个 Point 类的对象/实例就代表了一个点;

接着,我们会想计算两点间的距离,这样需要一个函数来实现。那么,这个函数需要如何设计?

如果将计算距离的函数设计为类外的普通函数,就不能体现这个函数与“点”之间的联系,而且类外的函数也不能直接引用“点”的坐标(私有成员),这样计算时就很不方便。

那么设计为 Point 类的成员函数又如何呢?从语法的角度这不难实现,但是理解起来却有问题。因为距离是点与点之间的一种关系,它既不属于每一个单独的点,也不属于 Point 类。

也就是说无论设计为非静态成员还是静态成员,都会影响程序的可读性。

这时,我们希望设计的函数,既能体现点与计算两点间距离函数的联系,又能方便的使用 Point 类中的私有数据成员。于是,引入友元机制。

1.友元关系
  • 友元关系提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。
  • 通俗地说,友元关系就是一个类主动声明哪些其他类或函数是它的朋友,进而给它们提供对本类的访问特许。
  • 通过友元关系,一个普通函数或者类的成员函数可以访问封装于另外一个类中的数据。
  • 从一定程度上讲,友元是对数据隐蔽和封装的破坏。但是为了数据共享,提高程序的效率和可读性,很多情况下这种小的破坏也是必要的,关键是一个度的问题,要在共享和封装之间找到一个恰当的平衡。
2.语法
  • 在一个类中,可以利用关键字 friend 将其他函数或类声明为友元。
  • 如果友元是一般函数或类的成员函数,称为友元函数;
  • 如果友元是一个类,则称为友元类;友元类的所有成员函数都自动成为友元函数。

5.4.1 友元函数

1.关于友元函数
  • 友元函数是在类中用关键字 friend 修饰的非成员函数。
  • 友元函数可以是一个普通的函数,也可以是其他类的成员函数。
  • 虽然友元函数不是本类的成员函数,但是在它的函数体中可以通过对象名访问类的私有和保护成员。
2.实例
#include<iostream>
#include<cmath>

class Point{
public:
    Point(double xx,double yy):x(xx),y(yy){}
    friend double getDistance(Point &p1,Point &p2);
private:
    double x,y;
};

double getDistance(Point &p1,Point &p2){
    double x=p1.x-p2.x;
    double y=p1.y-p2.y;
    return sqrt(x*x+y*y);
}

int main()
{
    Point myp1(2,5),myp2(4,8);
    cout << getDistance(myp1,myp2) << endl;
    return 0;
}

5.4.2 友元类

1.关于友元类
  • 若 A 类为 B 类的友元类,则 A 类的所有成员函数都是 B 类的友元函数,都可以访问 B 类的私有和保护成员。
  • 声明友元类,是建立类与类间的联系,实现类之间数据共享的一种途径。
2.语法
class B{
    ...
    friend class A;
    ...
};
3.实例
class A{
public:
    friend class B;
private:
    int x;
};

class B{
public:
    void setX(int i);
private:
    A a;
};

void B::setX(int i){
    a.x=i;      //B是A的友元类,所以在B类的成员函数中可以通过A类对象访问A类对象的私有成员
}

int main()
{
    ...
}

5.4.3 关于友元需要注意的几点

  • 第一,友元关系是不能传递的;
    • B 类是 A 类的友元,C 类是 B 类的友元 ;C 类和 A 类之间,如果没有声明,就没有任何友元关系,不能进行数据共享。
  • 第二,友元关系是单向的;
    • 如果声明 B 类是 A 类的友元,B 类的成员函数就可以访问 A 类的私有和保护数据,但 A 类的成员函数却不能访问 B 类的私有、保护数据。
  • 第三,友元关系是不被继承的。
    • 如果类 B 是类 A 的友元,类 B 的派生类并不会自动成为类 A 的友元。

5.5 共享数据的保护

对于既需要共享又需要防止改变的数据应该声明为常量。因为常量在程序运行期间是不可改变的,,可以有效地保护数据。

5.5.1 常对象

1.什么是常对象
  • 常对象的数据成员值在对象的整个生存期间内不能被改变。
  • 也就是说,常对象必须进行初始化,而且不能被更新。
2.声明常对象的语法
const 类型说明符 对象名;
  • 在声明常对象时 ,把 const 关键字放在类型名后面也是允许的,不过人们更习惯于把 const 写在前面。
3.实例
class A{
public:
    A(int xx,int yy):x(xx),y(yy){}
private:
    int x,y;
};

const A a(2,8);
4.思考:语法如何保障常对象的值不被改变?

改变对象的数据成员的两条途径:

(1)通过对象名访问其成员对象

  由于常对象的数据成员都被视同为常量,这时语法会限制不能赋值。

(2)在类的成员函数中改变数据成员的值

  语法只规定不能通过常对象调用普通的成员函数,只能调用常成员函数。

5.思考:基本数据类型的常量也可以看作一种特殊的常对象。

  首先想到的一点,基本数据类型也是一种类;每一种数据类型都包括了数据本身的属性,以及对数据的操作。例如,我们说不可以对常量进行赋值,赋值用“=”,这就是一种操作。同理我们来看常对象,语法里限制了通过常对象调用普通的成员函数,成员函数相当于是该对象的一种操作。接着再反过来看,C++中,基本数据类型就是一种特殊的类,那么当它是常量(常对象)时,自然要遵守语法规定,不能进行赋值(调用普通的成员函数)。这样一看,感觉有那么点意思。

5.5.2 用 const 修饰的类成员

5.2.2.1 常成员函数
1.声明常成员函数的语法
类型说明符 函数名(参数表) const;
2.定义常成员函数的语法
类型说明符 类名::函数名(参数表) const{
    函数体
}
3.需要注意的地方
  • const 是函数类型的一个组成部分,因此在函数的定义部分也要带 const 关键字。

  • 如果将一个对象说明为常对象,则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数

    • 这就是C ++ 从语法机制上对常对象的保护,也是常对象唯一的对外接口方式。
  • 无论是否通过常对象调用常成员函数,在常成员函数调用期间 ,目的对象都被视同为常对象。因此常成员函数不能更新目的对象的数据成员,也不能针对目的对象调用该类中没有用 const 修饰的成员函数。

    • 这就保证了 在常成员函数中不会更改目的对象的数据成员的值。
  • 关键字 const 可以用于对重载函数的区分。

    • void showSth();
      void showSth() const;
      
    • 如果仅以 const 关键字为区分对成员函数重载 ,那么通过非 const 的对象调用该函数,两个重载的函数都可以与之匹配,这时编译器将选择最近的重载函数——不带 const 关键字的函数。

  • 在适当的地方使用 const 关键字,是能够提高程序质量的一个好习惯。 对于无须改变对象状态的成员函数,都应当使用 const。

5.2.2.2 常数据成员
1.关于常数据成员
  • 如果在一个类中说明了常数据成员,那么任何函数中都不能对该成员赋值。
  • 构造函数对该数据成员进行初始化,就只能通过初始化列表。
2.实例
#include<iostream>
using namespace std;

class A{
public:
    A(int x):a(x){}
private:
    const int a;
    static const int b;
};

const int A::b=10;
  • 类成员中的静态变量和j静态常量都应当在类定义之外加以定义;
  • 但C ++ 标准规定了一个例外:类的静态常量如果具有整数类型或枚举类型,那么可以直接在类定义中为为它指定常量值。

所以,上面的例子还可以这么写:

#include<iostream>
using namespace std;

class A{
public:
    A(int x):a(x){}
private:
    const int a;
    static const int b=10;
};

5.5.3 常引用

1.什么是常引用
  • 如果在声明引用时用 const 修饰,被声明的引用就是常引用。
  • 常引用所引用的对象不能被更新。
  • 如果用常引用作形参,便不会意外地发生对实参的改变。
2.声明常引用的语法
const 类型说明符 &引用名;
3.关于常引用的几点讨论
  • 非 const 的引用只能绑定到普通的对象 ,而不能绑定到常对象 ,但常引用可以绑定到常对象。
  • 一个常引用,无论是绑定到一个普通的对象,还是常对象,通过该引用访问该对象时,都只能把该对象当作常对象。
  • 这意味着,对于基本数据类型的引用,则不能为数据赋值;对于类类型的引用,则不能修改它的数据成员,也不能调用它的非 const 的成员函数。
4.实例
#include<iostream>
#include<cmath>

class Point{
public:
    Point(double xx,double yy):x(xx),y(yy){}
    friend double getDistance(const Point &p1,const Point &p2);
private:
    double x,y;
};

double getDistance(const Point &p1,const Point &p2){
    double x=p1.x-p2.x;
    double y=p1.y-p2.y;
    return sqrt(x*x+y*y);
}

int main()
{
    const Point myp1(2,5),myp2(4,8);    //这里加不加 const 感觉都可以
    cout << getDistance(myp1,myp2) << endl;
    return 0;
}

讨论:

  • 实例中,getDistance( )函数无需修改两个传入对象的值,因此将传参方式改为传递常引用。这样可以传入的两个实参被意外地修改。同时,采用引用传递的方式,可以节省时间,因为对一些大对象来说,传值传递耗时更多。这里要在说明一点,如果要引用的是常对象,那么getDistance( )就不能使用普通引用传递了,只能使用常引用传递或者传值传递(这个耗时)。
  • 复制构造函数的参数一般也宜采用常引用传递。

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

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

1.惯用的做法
  • 将类的定义写在头文件中,使用该类的编译单元则包含这个头文件。
  • 通常一个项目至少划分为3个文件:
    • 类定义文件(*.h 文件)
    • 类实现文件(*.cpp 文件)
    • 类的使用文件(*.cpp ,主函数文件)
2.实例
//文件1,类的定义,Point.h
class Point{
public:
    Point(int x=0,int y=0):x(x),y(y){}
    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(Point &p):x(p.x),y(p.y){
    count++;
}
void Point::showcount(){
    cout <<" object count =" << count << endl;
}
//文件3,主函数
#include "Point.h"
#include<iostream>
using namespace std;

int main()
{
    Point a(4,5);
    cout << "Point A: " << a.getX() << "," << a.getY();
    Point::showCount();
    
    Point b(a);
    cout << "Point B: " << b.getX() << "," << b.getY();
    Point::showCount();
    
    return 0;
}
3.指令include的两种写法

(1)#include<文件名>

  表示按照标准方式搜索要嵌入的文件,该文件位于编译环境的 include 子目录下 ,一般要嵌入系统提供的标准文件时采用这样的方式 ,如对标准头文件 iostream 的包含。

(2)#include"文件名"

  表示首先在当前目录下搜索要嵌入的文件,如果没有,再按照标准方式搜索,对用户自己编写的文件一般采用这种方式,如本例中类的定义文件 point.h。

4.C++多文件组织的好处

在这里插入图片描述
  从上图可以看到,两个 .cpp 的文件被分别编译生成各自的目标文件 .obj,,然后再与系统的运行库共同连接生成可执行文件 .exe。如果只修改了类的成员函数的实现部分,则只重新编译 point.cpp 并连接即可,其余的文件几乎可以连看都不用看。想一想,如果是一个语句很多、规模特大的程序,效率就会得到显著的提高。

5.决定一个声明放在源文件中还是头文件中的一般原则
  • 将需要分配空间的定义放在源文件中,例如函数的定义(需要为函数代码分配空间)、命名空间作用域中变量的定义(需要为变量分配空间)等;
  • 将不需要分配空间的声明放在头文件中,例如类声明、外部函数的原型声明、外部变量的声明、基本数据类型常量的声明等;
  • 内联函数比较特殊,由于它的内容需要嵌入到每个调用它的函数之中,所以对于那些需要被多个编译单元调用的内联函数,它们的代码应该被各个编译单元可见,这些内联函数的定义应当出现在头文件中。
  • 注意:
    • 如果误将分配了空间的定义写入头文件中,在多个源文件包含该头文件时,会导致空间在不同的编译单元中被分配多次,从而在连接时引发错误。

5.6.2 外部变量与外部函数

5.6.2.1.外部变量
1.什么是外部变量
  • 如果一个变量除了在定义它的源文件中可以使用外,还能被其他文件使用,那么就称这个变量是外部变量。
  • 命名空间作用域中定义的变量,默认情况下都是外部变量,但在其他文件中如果需要使用这一变量,需要用 extern 关键字加以声明。
2.实例
//源文件
int i=3;   //定义性声明
int main()
{
    ...
}
//源文件2
extern int i;   //引用性声明
void other()
{
    ...
}
3.注意
  • 对外部变量的声明可以是定义性声明,即在声明的同时定义(分配内存,初始化),也可以是引用性声明(引用在别处定义的变量)。
  • 在命名空间作用域中,不用 extern 关键字声明的变量 ,都是定义性声明;用 extern 关键字声明的变量,如果同时指定了初值,则是定义性声明,否则是引用性声明。
  • 外部变量可以有多处声明,但是对变量的定义性声明只能是唯一的。
5.6.2.2 外部函数

  在所有类之外声明的函数(也就是非成员函数),都是具有命名空间作用域的,如果没有特殊说明,这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。

5.6.2.3 将变量和函数限制在编译单元内

(1)方法一:使用关键字 static 修饰

(2)方法二:使用匿名的命名空间

5.6.3 编译预处理

预处理指令实际上不是 C++ 语言的一部分,它只 是用来扩充 C++ 程序设计的环境。

所有的预处理指令在程序中都是以" # "来引导,每一条预处理指令单独占用一行,不要用分号结束。预处理指令可以根据需要出现在程序中的任何位置。

1.#include 指令
  • #include 指令也称文件包含指令,其作用是将另一个源文件嵌入到当前源文件中该点处。
  • 两种格式
    • #include<文件名>
    • #include"文件名"
  • #include 指令可以嵌套使用
2.#define 和 #undef 指令
  • 用 #define 定义符号常量
    • 建议使用 const 修饰词定义常量
  • 用 #define 定义参数宏
    • 在C++中被内联函数取代
  • 用 #define 定义空符号
  • 用 #undef 删除由 #define 定义的宏,使之不再起作用。
3.条件编译指令

(1)形式一

#if 常量表达式
	程序段       //当“常量表达式”非零时编译本程序段
#endif

(2)形式二

#if 常量表达式
	程序段1      //当“常量表达式”非零时编译本程序段
#else
	程序段2      //当“常量表达式”为零时编译本程序段
#endif

(3)形式三

#if 常量表达式1
	程序段1
#elif 常量表达式2
	程序段2
	.
	.
	.
#elif 常量表达式n
	程序段n
#else
	程序段n+1
#endif

(4)形式四

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

(5)形式五

#ifndef 标识符
	程序段1
#else
	程序段2
#endif
4.defined 操作符
defined(标识符)
  • 若”标识符“在此前经 #define 定义过 ,并且未经 #undef 删除,则上述表达式为非0,否则上述表达式的值为 0。
  • 由于文件包含指令可以嵌套使用,在设计程序时要避免多次重复包含同一个头文件,否则会引起变量及类的 重复定义。
#ifndef 标识符
#define 标识符
	...
#endif

实例:

  • 用一个唯一的标识符来标记某文件是否已参加过编译,如果已参加编译,则说明该程序段是被重复包含的,编译时忽略重复部分。
//head.h
#ifndef HEAD_H
#define HEAD_H
	...
class Point{
    ...
};
	...	
#endif

猜你喜欢

转载自blog.csdn.net/Jason3633/article/details/91427289