C++函数和类

一、函数

函数的定义

函数是一个定义好的、可重用的功能模块

函数的构成:

  • 函数名
  • 函数参数
  • 函数返回值
  • 函数体

语法

C++函数和类

函数调用

调用函数需要先声明函数原型

  • 若函数定义在调用点之前,可以不另外声明
  • 若函数定义在调用点之后,必须要在调用函数前声明函数原型

函数原型:类型标识符 被调用函数名(含类型说明的形参表)

函数调用形式

函数名(实参列表)

 

编写一个求x的n次方的函数

#include <iostream>

using namespace std;



//计算x的n次方

double power(double x, int n) {

double val = 1.0;

while (n--) val *= x;

return val;

}



int main() {

cout << "5 to the power 2 is "
<< power(5, 2) << endl;

return 0;

}

数制转换

输入一个8位二进制数,将其转换为十进制数输出。

例如:从键盘输入1101

11012=1×23+1×22+0×21+1×20=1310

所以,程序应输出13

源代码:

#include <iostream>
using namespace std;

double power (double x, int n); //计算x的n次方

int main() {
    int  value = 0;
    cout << "Enter an 8 bit binary number  ";
    for (int i = 7; i >= 0; i--) {
      char ch;
      cin >> ch;
      if (ch == '1')
        value += static_cast<int>(power(2, i));
    }
    cout << "Decimal value is  " << value << endl;
    return 0;
}
double power (double x, int n) {
    double val = 1.0;
    while (n--)
      val *= x;
    return val;

编写程序求π的值
l π的计算公式如下:

C++函数和类

l 其中arctan用如下形式的级数计算:

C++函数和类

l 直到级数某项绝对值不大于10-15为止;π和x均为double型。

arctan函数

// 函数程序
#include <iostream>
using namespace std;
double arctan(double x) {
          double sqr = x * x; 
          double e = x;
          double r = 0;
          int i = 1;
          while (e / i > 1e-15) { 
                  double f = e / i;
                   r = (i % 4 == 1) ? r + f : r - f;
                   e = e * sqr;
                   i += 2;
          }
          return r;
}

// 主程序
int main() {
          double a = 16.0 * arctan(1/5.0);
          double b = 4.0 * arctan(1/239.0);
          //注意:因为整数相除结果取整,如果参数写1/5,1/239,结果就都是0
           cout << "PI = " << a - b << endl;
          return 0;
}

寻找并输出11~999之间的数M,它满足M、M2和M3均为回文数。
l 回文:各位数字左右对称的整数。

l 例如:11满足上述条件

n 112=121,113=1331。

分析:

用除以10取余的方法,从最低位开始,依次取出该数的各位数字。按反序重新构成新的数,比较与原数是否相等,若相等,则原数为回文。

源代码:

#include <iostream>

using namespace std;

//判断n是否为回文数

bool symm(unsigned n) {

      unsigned i = n;

      unsigned m = 0;

      while (i > 0) {

        m = m * 10 + i % 10;

        i /= 10;

  }

  return m == n;

}

int main() {

      for(unsigned m = 11; m < 1000; m++)

        if (symm(m) && symm(m * m) && symm(m * m * m)) {

          cout << "m = " << m;

          cout << "  m * m = " << m * m;

          cout << "  m * m * m = "

               << m * m * m << endl;

        }

      return 0;

}
运行结果:

m=11  m*m=121  m*m*m=1331

m=101  m*m=10201  m*m*m=1030301

m=111  m*m=12321  m*m*m=1367631

投骰子的随机游戏
每个骰子有六面,点数分别为1、2、3、4、5、6。游戏者在程序开始时输入一个无符号整数,作为产生随机数的种子。

每轮投两次骰子,第一轮如果和数为7或11则为胜,游戏结束;和数为2、3或12则为负,游戏结束;和数为其它值则将此值作为自己的点数,继续第二轮、第三轮...直到某轮的和数等于点数则取胜,若在此前出现和数为7则为负。

 

rand函数
l 函数原型:int rand(void);
l 所需头文件:<cstdlib>
l 功能和返回值:求出并返回一个伪随机数

srand函数
l void srand(unsigned int seed);

l 参数:seed产生随机数的种子

l 所需头文件:<cstdlib>

l 功能:为使rand()产生一序列伪随机整数而设置起始点。使用1作为seed参数,可以重新初化rand()。

源代码:

#include <iostream>
#include <cstdlib>
using namespace std;

enum GameStatus { WIN, LOSE, PLAYING };
int main() {
    int sum, myPoint;
    GameStatus status;
    unsigned seed;
    int rollDice();
    cout<<"Please enter an unsigned integer: ";
    cin >> seed; //输入随机数种子
    srand(seed); //将种子传递给rand()
    sum = rollDice(); //第一轮投骰子、计算和数
    switch (sum) {
      case 7:   //如果和数为7或11则为胜,状态为WIN
      case 11:
            status = WIN;
            break;
      case 2:   //和数为2、3或12则为负,状态为LOSE
      case 3:
      case 12:
            status = LOSE;
            break;
      default:  //其它情况,尚无结果,状态为 PLAYING,记下点数
            status = PLAYING;
            myPoint = sum;
            cout << "point is " << myPoint << endl;
            break;
    }
    while (status == PLAYING) { //只要状态为PLAYING,继续
        sum = rollDice();
        if (sum == myPoint)    //某轮的和数等于点数则取胜
            status = WIN;
        else if (sum == 7)    //出现和数为7则为负
            status = LOSE;
    } 
//当状态不为PLAYING时循环结束,输出游戏结果
    if (status == WIN)
       cout << "player wins" << endl;
    else
       cout << "player loses" << endl;
    return 0;
}

//投骰子、计算和数、输出和数
int rollDice() {
    int die1 = 1 + rand() % 6;
    int die2 = 1 + rand() % 6;
    int sum = die1 + die2;
    cout << "player rolled " << die1 << " + " << die2 << " = " << sum << endl;
    return sum;
}

运行结果:
Please enter an unsigned integer:23
player rolled 6 + 3 = 9
point is 9
player rolled 5 + 4 = 9
player wins

 

嵌套和递归

 

 

参数传递

  • 在函数被调用时才分配形参的存储单元

  • 实参可以是常量、变量或表达式

  • 实参类型必须与形参相符

  • 值传递是传递参数值,即单向传递

  • 引用传递可以实现双向传递

  • 常引用作参数可以保障实参数据的安全

 

引用类型

引用的概念

  • 引用(&)是标识符的别名;

  • 定义一个引用时,必须同时对它进行初始化,使它指向一个已存在的对象。

  • 例如:

    int i, j;
    
    int &ri = i; //定义int引用ri,并初始化为变量i的引用
    
    j = 10;
    
    ri = j;  //相当于 i = j;
  • 一旦一个引用被初始化后,就不能改为指向其它对象。

  • 引用可以作为形参

输入两个整数并交换(值传递)

#include<iostream>
using namespace std;
void swap(int a, int b) {
    int t = a;
    a = b;
    b = t;
}

int main() {
    int x = 5, y = 10;
    cout<<"x = "<<x<<"  y = "<<y<<endl;
    swap(x, y);
    cout<<"x = "<<x<<"  y = "<<y<<endl;
    return 0;
}

运行结果:
x = 5      y = 10
x = 5      y = 10

C++函数和类

输入两个整数并交换(引用传递)

#include<iostream>
using namespace std;

void swap(int& a, int& b) {
    int t = a;
    a = b;
    b = t;
}

int main() {
    int x = 5, y = 10;
    cout<<"x = "<<x<<"  y = "<<y<<endl;
    swap(x, y);
    cout<<"x = "<<x<<"  y = "<<y<< endl;
    return 0;
}

C++函数和类

 

引用与取地址符

引用是给已定义的变量起别名,在声明的时候一定要初始化

int a = 88;
int &c = a;  //声明变量a的一个引用c,c是变量a的一个别名,如果引用,声明的时候一定要初始化
int &d = a;  //引用声明的时候一定要初始化,一个变量可以有多个引用

&(引用)用来传值,出现在变量声明语句中位于变量左边时,表示声明的是引用.
&(取地址运算符)用来获取首地址,在给变量赋初值时出现在等号右边或在执行语句中作为一元运算符出现时表示取对象的地址.

 

总而言之,和类型在一起的是引用,和变量在一起的是取址

实例如下:1)引用在赋值=的左边,而取地址在赋值的右边,比如

int a=3int &b=a;        //引用
int *p=&a;        //取地址

 

  2)和类型在一起的是引用,和变量在一起的是取址。 举例同样如上,还有下例:

int function(int &i)
{
}  //引用

 

3)对于vector,上面2条同样适合

vector<int> vec1(10,1);  //initialize vec1: 10 elements, every element's value is 1
vector<int> &vec2 = vec1; // vec2 is  reference to vec1
vector<int> *vec3 = &vec2; //vec3 is addresss of vec1 and vec2

 

含有可变参数的函数

  • C++标准中提供了两种主要的方法

    • 如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;

    • 如果实参的类型不同,我们可以编写可变参数的模板

  • initializer_list

    • initializer_list是一种标准库类型,用于表示某种特定类型的值的数组,该类型定义在同名的头文件中

initializer_list提供的操作

 C++函数和类

initializer_list的使用方法

  • initializer_list是一个类模板

  • 使用模板时,我们需要在模板名字后面跟一对尖括号,括号内给出类型参数。例如:

    • initializer_list<string>  ls;  // initializer_list的元素类型是string

    • initializer_list<int> li;      // initializer_list的元素类型是int

  • initializer_list比较特殊的一点是,其对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。

  • 含有initializer_list形参的函数也可以同时拥有其他形参

initializer_list使用举例

  • 在编写代码输出程序产生的错误信息时,最好统一用一个函数实现该功能,使得对所有错误的处理能够整齐划一。然而错误信息的种类不同,调用错误信息输出函数时传递的参数也会各不相同。

  • 使用initializer_list编写一个错误信息输出函数,使其可以作用于可变数量的形参。

 

内联函数

声明时使用关键字 inline。

编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销。

注意:

  • 内联函数体内不能有循环语句和switch语句;
  • 内联函数的定义必须出现在内联函数第一次被调用之前;
  • 对内联函数不能进行异常接口声明。

内联函数应用举例

#include <iostream>
using namespace std;
const double PI = 3.14159265358979;
inline double calArea(double radius) {
    return PI * radius * radius;
}
int main() {
    double r = 3.0;
    double area = calArea(r);
    cout << area << endl;
    return 0;
}

 

constexpr函数

constexpr函数语法规定

l  constexpr修饰的函数在其所有参数都是constexpr时,一定返回constexpr;

l  函数体中必须有且仅有一条return语句。

constexpr函数举例

l  constexpr int get_size() { return 20; }

l  constexpr int foo = get_size();  //正确:foo是一个常量表达式

 

带默认参数值的函数

默认参数值

可以预先设置默认的参数值,调用时如给出实参,则采用实参值,否则采用预先设置的默认参数值。

l  例:

int add(int x = 5,int y = 6) {

     return x + y;

}

int main() {

     add(10,20);  //10+20

     add(10);     //10+6

     add();       //5+6

}

 

默认参数值的说明次序

l  有默认参数的形参必须列在形参列表的最右,即默认参数值的右面不能有无默认值的参数;

l  调用时实参与形参的结合次序是从左向右。

l  例:

int add(int x, int y = 5, int z = 6);//正确

int add(int x = 1, int y = 5, int z);//错误

int add(int x = 1, int y, int z = 6);//错误

 

默认参数值与函数的调用位置

l  如果一个函数有原型声明,且原型声明在定义之前,则默认参数值应在函数原型声明中给出;如果只有函数的定义,或函数定义在前,则默认参数值可以函数定义中给出。

l  例:

C++函数和类计算长方体的体积

函数getVolume计算体积
有三个形参:length(长)、width(宽)、height(高),其中width和height带有默认值2和3。
主函数中以不同形式调用getVolume函数。
源代码:

#include <iostream>
#include <iomanip>
using namespace std;

int getVolume(int length, int width = 2, int height = 3);

int main() {
    const int X = 10, Y = 12, Z = 15;
    cout << "Some box data is " ;
    cout << getVolume(X, Y, Z) << endl;
    cout << "Some box data is " ;
    cout << getVolume(X, Y) << endl;
    cout << "Some box data is " ;
    cout << getVolume(X) << endl;
    return 0;
}

int getVolume(int length, int width, int height) {
    cout << setw(5) << length << setw(5) << width << setw(5)
         << height << 't';
    return length * width * height;
}

 

C++系统函数

系统函数
C++的系统库中提供了几百个函数可供程序员使用,例如:

  • 求平方根函数(sqrt)
  • 求绝对值函数(abs)

使用系统函数时要包含相应的头文件,例如:

  • cmath

系统函数应用举例
题目:

从键盘输入一个角度值,求出该角度的正弦值、余弦值和正切值。

分析:

系统函数中提供了求正弦值、余弦值和正切值的函数:sin()、cos()、tan(),函数的说明在头文件cmath中。

源代码

#include <iostream>

#include <cmath>

using namespace std;

const double PI = 3.14159265358979;



int main() {

      double angle;

      cout << "Please enter an angle: ";

      cin >> angle;  //输入角度值

      double radian = angle * PI / 180; //转为弧度

      cout << "sin(" << angle << ") = " << sin(radian) <<endl;

      cout << "cos(" << angle << ") = " << cos(radian) <<endl;

      cout << "tan(" << angle << ") = " << tan(radian) <<endl;

      return 0;

}

l  运行结果

30

sin(30)=0.5

cos(30)=0.866025

tan(30)=0.57735

 

函数重载

函数重载的概念

C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。方便使用,便于记忆。
例:

 C++函数和类

注意事项

  • 重载函数的形参必须不同:个数不同或类型不同。
  • 编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。

C++函数和类

  • 不要将不同功能的函数声明为重载函数,以免出现调用结果的误解、混淆。这样不好:

 C++函数和类

 

 

重载函数应用举例
编写两个名为sumOfSquare的重载函数,分别求两整数的平方和及两实数的平方和。
源代码:

#include <iostream>
using namespace std;
int sumOfSquare(int a, int b) {
    return a * a + b * b;
}
double sumOfSquare(double a, double b) {
    return a * a + b * b;
}
int main() {
    int m, n;
    cout << "Enter two integer: ";
    cin >> m >> n;
    cout<<"Their sum of square: "<<sumOfSquare(m, n)<<endl;
    double x, y;
    cout << "Enter two real number: ";
    cin >> x >> y;
    cout<<"Their sum of square: "<<sumOfSquare(x, y)<<endl;
    return 0;
}

运行结果:
Enter two integer: 3 5
Their sum of square: 34
Enter two real number: 2.3 5.8
Their sum of square: 38.93

 

二、类与对象

面向对象程序设计的基本特点 

抽象

对同一类对象的共同属性和行为进行概括,形成类。

先注意问题的本质及描述,其次是实现过程或细节。

数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。

代码抽象:描述某类对象的共有的行为特征或具有的功能。

抽象的实现:类。

抽象实例——钟表

数据抽象:

int hour,int minute,int second

代码抽象:

setTime(),showTime()

 

class  Clock {

  public:

   void setTime(int newH, int newM, int newS);
   void showTime();

  private:

   int hour, minute, second;

};

 

封装

将抽象出的数据、代码封装在一起,形成类。

目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。

实现封装:类声明中的{}

例:

class  Clock {

  public: void setTime(int newH, int newM, int newS);
            void showTime();

  private: int hour, minute, second;

};

 

继承

在已有类的基础上,进行扩展形成新的类。

 

多态

多态:同一名称,不同的功能实现方式。

目的:达到行为标识统一,减少程序中标识符的个数。

实现:重载函数和虚函数

 

类和对象的定义

对象是现实中的对象在程序中的模拟。

类是同一类对象的抽象,对象时类的某一特定实体。

定义类的对象,才可以通过对象使用类中定义的功能。

设计类就是设计类型

此类型的“合法值”是什么?

此类型应该有什么样的函数和操作符?

新类型的对象该如何被创建和销毁?

如何进行对象的初始化和赋值?

对象作为函数的参数如何以值传递?

谁将使用此类型的对象成员?

类定义的语法形式

class 类名称

{

   public:

                     公有成员(外部接口)

   private:

                      私有成员

   protected:

                      保护型成员

};

类内初始值

可以为数据成员提供一个类内初始值

在创建对象时,类内初始值用于初始化数据成员

没有初始值的成员将被默认初始化。

类内初始值举例

class Clock {

public:

   void setTime(int newH, int newM, int newS);

   void showTime();

private:

   int hour = 0, minute = 0, second = 0;

};

类成员的访问控制

公有类型成员

    在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。

私有类型成员

    在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。

    如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。

保护类型成员

    与private类似,其差别表现在继承与派生时对派生类的影响不同。

对象定义的语法

类名  对象名;

例:Clock  myClock;

类成员的访问权限

类中成员互相访问

直接使用成员名访问

类外访问

使用“对象名.成员名”方式访问 public 属性的成员

类的成员函数

在类中说明函数原型

可以在类外给出函数体实现,并在函数名前使用类名加以限定;

可以直接在类中给出函数体,形成内联成员函数;

允许声明重载函数和带默认参数值的函数。

内联成员函数

为了提高运行时的效率,对于较简单的函数可以声明为内联形式。

内联函数体中不要有复杂结构(如循环语句和switch语句)。

在类中声明内联成员函数的方式:

  • 将函数体放在类的声明中。
  • 使用inline关键字。

 

类的定义

#include<iostream>

using namespace std;

class Clock{

public:         

     void setTime(int newH = 0, int newM = 0, int newS = 0);

     void showTime();

private:  

     int hour, minute, second;

};



成员函数的实现

void Clock::setTime(int newH, int newM, int newS) {

   hour = newH;

   minute = newM;

   second = newS;

}

void Clock::showTime() {

   cout << hour << ":" << minute << ":" << second;

}

对象的使用

int main() {

     Clock myClock;

     myClock.setTime(8, 30, 30);

     myClock.showTime();

     return 0;

}

 

 

 

构造函数

构造函数的作用
在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。

例如:

希望在构造一个Clock类对象时,将初试时间设为0:0:0,就可以通过构造函数来设置。

构造函数的形式

  • 函数名与类名相同;
  • 不能定义返回值类型,也不能有return语句;
  • 可以有形式参数,也可以没有形式参数;
  • 可以是内联函数;
  • 可以重载;
  • 可以带默认参数值。

构造函数的调用时机
在对象创建时被自动调用

例如:

Clock myClock(0,0,0);

默认构造函数
调用时可以不需要实参的构造函数

参数表为空的构造函数

全部参数都有默认值的构造函数

下面两个都是默认构造函数,如在类中同时出现,将产生编译错误:

Clock();

Clock(int newH=0,int newM=0,int newS=0);

隐含生成的构造函数
如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数

参数列表为空,不为数据成员设置初始值;

如果类内定义了成员的初始值,则使用内类定义的初始值;

如果没有定义类内的初始值,则以默认方式初始化;

基本类型的数据默认初始化的值是不确定的。

“=default”
如果程序中已定义构造函数,默认情况下编译器就不再隐含生成默认构造函数。如果此时依然希望编译器隐含生成默认构造函数,可以使用“=default”。

例如

class Clock {

public:

    Clock() =default; //指示编译器提供默认构造函数

    Clock(int newH, int newM, int newS); //构造函数

private:

    int hour, minute, second;

};

 

//类定义

class Clock {

public:

     Clock(int newH,int newM,int newS);//构造函数

     void setTime(int newH, int newM, int newS);

     void showTime();

private:

     int hour, minute, second;

};



//构造函数的实现:

Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM),  second(newS) {

     }

//其它函数实现同例4_1



int main() {

  Clock c(0,0,0); //自动调用构造函数

  c.showTime();

     return 0;

}

 

class Clock {

public:

       Clock(int newH, int newM, int newS); //构造函数

       Clock(); //默认构造函数

       void setTime(int newH, int newM, int newS);

       void showTime();

private:

       int hour, minute, second;

};

Clock::Clock(): hour(0),minute(0),second(0) { }//默认构造函数

//其它函数实现同前



int main() {

    Clock c1(0, 0, 0);       //调用有参数的构造函数

    Clock c2;         //调用无参数的构造函数

    ……

}

 

委托构造函数

类中往往有多个构造函数,只是参数表和初始化列表不同,其初始化算法都是相同的,这时,为了避免代码重复,可以使用委托构造函数。

       

回顾

Clock类的两个构造函数:

Clock(int newH, int newM, int newS) : hour(newH),minute(newM),  second(newS)  {         //构造函数

}

Clock::Clock(): hour(0),minute(0),second(0) { }//默认构造函数

       

委托构造函数

委托构造函数使用类的其他构造函数执行初始化过程

例如:

Clock(int newH, int newM, int newS):  hour(newH),minute(newM),  second(newS){

}

Clock(): Clock(0, 0, 0) { }

 

 

 

复制构造函数

复制构造函数定义

复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已存在的对象去初始化同类型的新对象。

class 类名 {

public :

    类名(形参);//构造函数

    类名(const  类名 &对象名);//复制构造函数

    //       ...

};

类名::类( const  类名 &对象名)//复制构造函数的实现

{    函数体    }

隐含的复制构造函数

如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的复制构造函数。

这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。

“=delete”

如果不希望对象被复制构造

  • C++98做法:将复制构造函数声明为private,并且不提供函数的实现。
  • C++11做法:用“=delete”指示编译器不生成默认复制构造函数。

例:

class Point {   //Point 类的定义

public:

    Point(int xx=0, int yy=0) { x = xx; y = yy; }    //构造函数,内联

    Point(const Point& p) =delete;  //指示编译器不生成默认复制构造函数

private:

    int x, y; //私有数据

};

 

复制构造函数被调用的三种情况

定义一个对象时,以本类另一个对象作为初始值,发生复制构造;

如果函数的形参是类的对象,调用函数时,将使用实参对象初始化形参对象,发生复制构造;

如果函数的返回值是类的对象,函数执行完成返回主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,此时发生复制构造。

这种情况也可以通过移动构造避免不必要的复制

 

class Point {   //Point 类的定义
public:
    Point(int xx=0, int yy=0) { x = xx; y = yy; }    //构造函数,内联
    Point(const Point& p); //复制构造函数
    void setX(int xx) {x=xx;}
    void setY(int yy) {y=yy;}
    int getX() const { return x; } //常函数(第5章)
    int getY() const { return y; } //常函数(第5章)
private:
    int x, y; //私有数据
};


//复制构造函数的实现
Point::Point (const Point& p) {
    x = p.x;
    y = p.y;
    cout << "Calling the copy constructor " << endl;
}

//形参为Point类对象void fun1(Point p) {
    cout << p.getX() << endl;
}


//返回值为Point类对象Point fun2() {
    Point a(1, 2);
    return a;
}
int main() {
    Point a(4, 5);
    Point b(a);   //用a初始化b。
    cout << b.getX() << endl;
    fun1(b);      //对象b作为fun1的实参
    b = fun2();  //函数的返回值是类对象
    cout << b.getX() << endl;
    return 0;
}

析构函数

  • 完成对象被删除前的一些清理工作。
  • 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。
  • 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数,其函数体为空。
  • 构造函数和析构函数举例
#include 
using namespace std;
class Point {     
public:
  Point(int xx,int yy);
  ~Point();
  //...其他函数原型
private:
  int x, y;
};

 

类的组合

组合的概念

  • 类中的成员是另一个类的对象。
  • 可以在已有抽象的基础上实现更复杂的抽象。

类组合的构造函数设计

  • 原则:不仅要负责对本类中的基本类型成员数据初始化,也要对对象成员初始化。
  • 声明形式:
类名::类名(对象成员所需的形参,本类成员形参)

        :对象1(参数),对象2(参数),......

{

//函数体其他语句

}

构造组合类对象时的初始化次序

  • 首先对构造函数初始化列表中列出的成员(包括基本类型成员和对象成员)进行初始化,初始化次序是成员在类体中定义的次序。
    • 成员对象构造函数调用顺序:按对象成员的声明顺序,先声明者先构造。
    • 初始化列表中未出现的成员对象,调用用默认构造函数(即无形参的)初始化
  • 处理完初始化列表之后,再执行构造函数的函数体。
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point类定义
public:
    Point(int xx = 0, int yy = 0) {
      x = xx;
      y = yy;
    }
    Point(Point &p);
    int getX() { return x; }
    int getY() { return y; }
private:
    int x, y;
};

Point::Point(Point &p) {  //复制构造函数的实现
    x = p.x;
    y = p.y;
    cout << "Calling the copy constructor of Point" << endl;
}

//类的组合
class Line {   //Line类的定义
public:   //外部接口
    Line(Point xp1, Point xp2);
    Line(Line &l);
    double getLen() { return len; }
private: //私有数据成员
    Point p1, p2;   //Point类的对象p1,p2
    double len;
};

//组合类的构造函数
Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) {
    cout << "Calling constructor of Line" << endl;
    double x = static_cast<double>(p1.getX() - p2.getX());
    double y = static_cast<double>(p1.getY() - p2.getY());
    len = sqrt(x * x + y * y);
}
Line::Line (Line &l): p1(l.p1), p2(l.p2) {  //组合类的复制构造函数
    cout << "Calling the copy constructor of Line" << endl;
    len = l.len;
}

//主函数
int main() {
    Point myp1(1, 1), myp2(4, 5);   //建立Point类的对象
    Line line(myp1, myp2);   //建立Line类的对象
    Line line2(line);   //利用复制构造函数建立一个新对象
    cout << "The length of the line is: ";
    cout << line.getLen() << endl;
    cout << "The length of the line2 is: ";
    cout << line2.getLen() << endl;
    return 0;
}

前向引用声明

类应该先声明,后使用

如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。

前向引用声明只为程序引入一个标识符,但具体声明在其他地方。

例:

class B;  //前向引用声明

class A {

public:

  void f(B b);

};

class B {

public:

  void g(A a);

};

 

前向引用声明注意事项

使用前向引用声明虽然可以解决一些问题,但它并不是万能的。

在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。

当使用前向引用声明时,只能使用被声明的符号,而不能涉及类的任何细节。

class Fred; //前向引用声明

class Barney {

   Fred x; //错误:类Fred的声明尚不完善

};

class Fred {

   Barney y;

};

 

结构体

  • 结构体是一种特殊形态的类
    • 与类的唯一区别:类的缺省访问权限是private,结构体的缺省访问权限是public
    • 结构体存在的主要原因:与C语言保持兼容
  • 什么时候用结构体而不用类
    • 定义主要用来保存数据、而没有什么操作的类型
    • 人们习惯将结构体的数据成员设为公有,因此这时用结构体更方便

 

结构体的定义

struct 结构体名称 {
     公有成员
protected:
    保护型成员
private:
     私有成员
};

 

结构体的初始化

  • 如果一个结构体的全部数据成员都是公共成员,并且没有用户定义的构造函数,没有基类和虚函数(基类和虚函数将在后面的章节中介绍),这个结构体的变量可以用下面的语法形式赋初值
类型名 变量名 = { 成员数据1初值, 成员数据2初值, …… };

用结构体表示学生的基本信息

#include 
#include 
#include 
using namespace std;

struct Student {    //学生信息结构体
    int num;        //学号
    string name;    //姓名,字符串对象,将在第6章详细介绍
    char sex;        //性别
    int age;        //年龄
};

int main() {
    Student stu = { 97001, "Lin Lin", 'F', 19 };
    cout << "Num:  " << stu.num << endl;
    cout << "Name: " << stu.name << endl;
    cout << "Sex:  " << stu.sex << endl;
    cout << "Age:  " << stu.age << endl;
    return 0;
}

运行结果:
Num:  97001
Name: Lin Lin
Sex:  F
Age:  19

 

联合体

声明形式

union 联合体名称 {
    公有成员
protected:
    保护型成员
private:
    私有成员
};

特点

  • 成员共用同一组内存单元
  • 任何两个成员不会同时有效

联合体的内存分配

  • 举例说明:
union Mark {    //表示成绩的联合体
    char grade;    //等级制的成绩
    bool pass;    //只记是否通过课程的成绩
    int percent;    //百分制的成绩
};

 

C++函数和类

无名联合

  • 例:
union {
  int i;
  float f;
}
在程序中可以这样使用:
i = 10;
f = 2.2;

使用联合体保存成绩信息,并且输出。

#include 
#include 
using namespace std;
class ExamInfo {
private:
    string name;    //课程名称
    enum { GRADE, PASS, PERCENTAGE } mode;//计分方式
    union {
        char grade;    //等级制的成绩
        bool pass;    //只记是否通过课程的成绩
        int percent;    //百分制的成绩
    };
public:
    //三种构造函数,分别用等级、是否通过和百分初始化
    ExamInfo(string name, char grade)
        : name(name), mode(GRADE), grade(grade) { }
    ExamInfo(string name, bool pass)
        : name(name), mode(PASS), pass(pass) { }
    ExamInfo(string name, int percent)
        : name(name), mode(PERCENTAGE), percent(percent) { }
    void show();
}

void ExamInfo::show() {
    cout << name << ": ";
    switch (mode) {
      case GRADE: cout << grade;  break;
      case PASS: cout << (pass ? "PASS" : "FAIL"); break;
      case PERCENTAGE: cout << percent; break;
    }
    cout << endl;
}

int main() {
    ExamInfo course1("English", 'B');
    ExamInfo course2("Calculus", true);
    ExamInfo course3("C++ Programming", 85);
    course1.show();
    course2.show();
    course3.show();
    return 0;
}
运行结果:
English: B
Calculus: PASS
C++ Programming: 85

 

枚举类

枚举类定义

语法形式

enum class 枚举类型名: 底层类型 {枚举值列表};

 

例:

enum class Type { General, Light, Medium, Heavy};

enum class Type: char { General, Light, Medium, Heavy};

enum class Category { General=1, Pistol, MachineGun, Cannon};

 

枚举类的优势

强作用域,其作用域限制在枚举类中。

例:使用Type的枚举值General:

Type::General

 

转换限制,枚举类对象不可以与整型隐式地互相转换。

可以指定底层类型

例:

enum class Type: char { General, Light, Medium, Heavy};

 

枚举类举例

#include<iostream>

using namespace std;

enum class Side{ Right, Left };

enum class Thing{ Wrong, Right };  //不冲突

int main()

{

    Side s = Side::Right;

    Thing w = Thing::Wrong;

    cout << (s == w) << endl;  //编译错误,无法直接比较不同枚举类

    return 0;

}

 

 

 

 

原文链接: https://www.cnblogs.com/aidata/p/12979813.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    C++函数和类

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/352123

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月2日 上午7:21
下一篇 2023年3月2日 上午7:21

相关推荐