C++基础学习教程(六)----类编写的前情回顾以及项目实战(1)

在开始类的编写之前我们依然需要回顾整理一下前面所说的内容,(前面虽然是一个自定义数据类型的实现过程,但是内容有点繁杂).

先看一段代码:

/** @file calssStruct.cpp */
/** Member Functions for Class point */
#include <cmath> // for sqrt and atan

using namespace  std;

struct point
{
  point()
  : x_(0.0), y_(0.0)
  {}
  point(double x, double y)
  : x_(x), y_(y)
  {}
  point(point const& pt)
  : x_(pt.x_), y_(pt.y_)
  {}

  /// Distance to the origin.
  double distance()
  {
    return std::sqrt(x*x + y*y);
  }
  /// Angle relative to x-axis.
  double angle()
  {
    return std::atan2(y, x);
  }

  /// Add an offset to x and y.
  void offset(double off)
  {
    offset(off, off);
  }
  /// Add an offset to x and an offset to y
  void offset(double  xoff, double yoff)
  {
    x = x + xoff;
    y = y + yoff;
  }

  /// Scale x and y.
  void scale(double mult)
  {
    this->scale(mult, mult);
  }
  /// Scale x and y.
  void scale(double xmult, double ymult)
  {
    this->x = this->x * xmult;
    this->y = this->y * ymult;
  }
  double x_;
  double y_;
};

上面代码中有构造函数,有成员函数,有数据成员.我们现在需要分析一个在上面和之前的成员函数中出现的一个this引用.

在C++中,每个成员函数都有一个隐含的形参this,当成员函数被调用时,其对象本身就被编译器作为实参隐式传入.在成员函数中可以使用*this表达式访问该对象.在C++语法中点操作符比星操作符优先级高,因此需要使用圆括号(如(*this).x).另一种等价的调用时使用”箭头”,即是this->x.

但是其实编译器可以自动识别那些是成员名字,所以this->是可选的,即是可以省略的.但是有时候为了代码清晰总是加上它.而一些程序员则为了表明是数据成员,有时候往往在数据成员名称前面加上前缀,或者在后面加上后缀.我倾向于后缀,如上面的代码.

关于构造函数,初始化时类和内置类型的一个重要区别,如果定义一个内置类型而不提供初始化序列,那么它的值就是无意义的;而定义类的对象时,该对象一定会被构造器初始化,因此总有机会初始化数据成员.

尽管构造函数的冒号后面的初始化列表是可选的,但是建议总是加上.

根据数据成员的类型是类或者内置类型,编译器会做出不同的处理.每个成员的初始化表有下面的三种情况:

下面再来一个构造函数的验证代码:

/** @file constructFun.cpp */
/** Visual Constructors */
#include <iostream>
#include <ostream>

using namespace std;

struct demo
{
  demo()      : x_(0) { std::cout << "default constructor\n"; }
  demo(int x) : x_(x) { std::cout << "constructor(" << x << ")\n"; }
  demo(demo const& that)
  : x_(that.x_)
  {
    std::cout << "copy constructor(" << x_ << ")\n";
  }
  int x_;
};

demo addone(demo d)
{
  ++d.x_;
  return d;
}

int main()
{
  demo d1;
  demo d2(d1);
  demo d3(42);
  demo d4(addone(d3));
}

结果如下:

但是是否是学习到这里我们就把构造函数什么的搞明白了呢,下面我们来测试一下,先看代码:

/** @file Test_Construct.cpp */
/** Mystery Program */
#include <iostream>
#include <ostream>

using namespace std;
struct point
{
  point()
  : x_(0.0), y_(0.0)
  {
    cout << "default constructor\n";
  }
  point(double x, double y)
  : x_(x), y_(y)
  {
    cout << "constructor(" << x << ", " << y << ")\n";
  }

  double x_;
  double y_;
};

int main()
{
  point pt();
}

你认为这个编译后会输出什么呢?是default constructor么?

但是你错了……这就是输出:

即是根本没有定义变量,编译器认为你编写了一个无参数的返回值为point的名字是pt的函数声明!

如果你不理解那么把point改成int,现在是intpt();这样是不是就更像一个函数声明了呢.但是如果要定义一个变量应该怎么样的呢?

是 point pt;即是不带括号这个时候调用了默认的无参数构造函数初始化.

所以在定义变量的时候注意哪些括号是必须的,不然很可能就会误导编译器将你的所谓的”变量声明”当成函数声明.

练习项目一

身体质量指数BMI小程序

学习了那么多可以做个小项目练习一下了.要计算BMI需要知道一个人的身高体重,BMI的计算公式是体重/(身高^2),结果是一个无单位的值.现在的任务是编写一个程序,使其能够读取记录,打印记录并计算一些统计值.该程序以请求一个BMI上极限开始,仅打印BMI值大雨或等于此极限值的记录,每条记录包含姓名(可以有空格),体重,身高(单位cm),以及性别(M或F,不限大小写).读取完每个人的记录后要立即打印该记录的BMI值,手机所有的记录后,基于数据打印两个表------男性一个,女性一个.在BMI值后面用*标记超过界限的BMI记录.并打印BMI的均值和中值.

其中的一个示例代码如下.

/** @file BMI_Again.cpp */
/** New BMI Program */
#include <algorithm>
#include <cstdlib>
#include <iomanip>
#include <ios>
#include <iostream>
#include <istream>
#include <limits>
#include <locale>
#include <ostream>
#include <string>
#include <vector>

using namespace std;

/// Compute body-mass index from height in centimeters and weight in kilograms.
int compute_bmi(int height, int weight)
{
   return static_cast<int>(weight * 10000 / (height * height) + 0.5);
}

/// Skip the rest of the input line.
void skip_line(istream& in)
{
  in.ignore(numeric_limits<int>::max(), '\n');
}

/// Represent one person’s record, storing the person’s name, height, weight,
/// sex, and body-mass index (BMI), which is computed from the height and weight.
struct record
{
  record() : height_(0), weight_(0), bmi_(0), sex_('?'), name_()
  {}

  /// Get this record, overwriting the data members.
  /// Error-checking omitted for brevity.
  /// @return true for success or false for eof or input failure
  bool read(istream& in, int num)
  {
    cout << "Name " << num << ": ";
    string name;
    if (not getline(in, name))
      return false;

    cout << "Height (cm): ";
    int height;
    if (not (in >> height))
      return false;
    skip_line(in);

    cout << "Weight (kg): ";
    int weight;
    if (not (in >> weight))
      return false;
    skip_line(in);

    cout << "Sex (M or F): ";
    char sex;
    if (not (in >> sex))
      return false;
    skip_line(in);
    sex = toupper(sex, locale());

    // Store information into data members only after reading
    // everything successfully.
    name_ = name;
    height_ = height;
    weight_ = weight;
    sex_ = sex;
    bmi_ = compute_bmi(height_, weight_);
    return true;
  }

  /// Print this record to @p out.
  void print(ostream& out, int threshold)
  {
    out << setw(6) << height_
        << setw(7) << weight_
        << setw(3) << sex_
        << setw(6) << bmi_;
    if (bmi_ >= threshold)
      out << '*';
    else
      out << ' ';
    out << ' ' << name_ << '\n';
  }

  int height_;       ///< height in centimeters
  int weight_;       ///< weight in kilograms
  int bmi_;          ///< Body-mass index
  char sex_;         ///< 'M' for male or 'F' for female
  string name_; ///< Person’s name
};

/** Print a table.
 * Print a table of height, weight, sex, BMI, and name.
 * Print only records for which sex matches @p sex.
 * At the end of each table, print the mean and median BMI.
 */
void print_table(char sex, vector<record>& records, int threshold)
{
  cout << "Ht(cm) Wt(kg) Sex  BMI  Name\n";

  float bmi_sum(0);
  long int bmi_count(0);
  vector<int> tmpbmis; // store only the BMIs that are printed
                            // in order to compute the median
  for (vector<record>::iterator iter(records.begin());
       iter != records.end();
       ++iter)
  {
    if (iter->sex_ == sex)
    {
      bmi_sum = bmi_sum + iter->bmi_;
      ++bmi_count;
      tmpbmis.push_back(iter->bmi_);
      iter->print(cout, threshold);
    }
  }

  // If the vectors are not empty, print basic statistics.
  if (bmi_count != 0)
  {
    cout << "Mean BMI = "
              << setprecision(1) << fixed << bmi_sum / bmi_count
              << '\n';

    // Median BMI is trickier. The easy way is to sort the
    // vector and pick out the middle item or items.
    sort(tmpbmis.begin(), tmpbmis.end());
    cout << "Median BMI = ";
    // Index of median item.
    int i(tmpbmis.size() / 2);
    if (tmpbmis.size() % 2 == 0)
      cout << (tmpbmis.at(i) + tmpbmis.at(i-1)) / 2.0 << '\n';
    else
      cout << tmpbmis.at(i) << '\n';
  }
}

/** Main program to compute BMI. */
int main()
{
  locale::global(locale(""));
  cout.imbue(locale());
  cin.imbue(locale());

  vector<record> records;
  int threshold;

  cout << "Enter threshold BMI: ";
  if (not (cin >> threshold))
    return EXIT_FAILURE;
  skip_line(cin);

  cout << "Enter name, height (in cm),"
               " and weight (in kg) for each person:\n";
  record rec;
  while (rec.read(cin, records.size()+1))
  {
    records.push_back(rec);
    cout << "BMI = " << rec.bmi_ << '\n';
  }

  // Print the data.
  cout << "\n\nMale data\n";
  print_table('M', records, threshold);
  cout << "\nFemale data\n";
  print_table('F', records, threshold);
}

运行结果如下:

但是看到上面的这个函数:

你是否觉得函数参数传递应该是const类型更合适呢,我们修改了一下,测试,发现,,,不行,出现了错误.但是显然应该是const类型的引用传递.那怎么实现呢.

应该记得在每个成员函数的内部都有一个this隐藏参数,上面的代码段中,print_table调用print成员函数,但是经过修改,this引用了一个const对象.尽管你知道print函数不会修改任何的数据成员,但是编译器不知道,因此你需要让编译器知道,怎么做呢,只要在函数头和函数体之间加上一个const修饰符即可.如下:

而一个通用的规则是,为所有的不修改成员变量的函数添加const修饰符,它确保程序在有const对象的时候就可以调用成员函数.

C++基础学习教程(六)----类编写的前情回顾以及项目实战(1)

时间: 2024-10-14 17:07:52

C++基础学习教程(六)----类编写的前情回顾以及项目实战(1)的相关文章

C++基础学习教程(七)----类编写及类的两个特性解析---&gt;多态&amp;继承

类引入 到目前为止我们所写的自定义类型都是关键字struct,从现在起我们将采用class方式定义类,这种方式对于学习过其他高级语言包括脚本(Such as Python)的人来说再熟悉不过了. 但是在写之前我们还是需要比较一下用struct和class之间有什么区别. 首先对于struct,在C兼容性方面很重要,尽管C++是有别于C的另一门语言,但许多程序还是必须与C交互,C++有两个重要功能,可以方便的与C交互.其中之一的就是POD,即是Plain Old Data(简单旧式数据)的缩写.

C++基础学习教程(一)

开始自己的C++复习进阶之路. 声明: 这次写的博文纯当是一个回顾复习的教程,一些非常基础的知识将不再出现,或者一掠而过,这次的主要风格就是示例代码很多~~~ 所有代码在Ubuntu 14.04 LTS 版,GCC4.8.1(g++)编译通过.其他的平台没试过,估计有些代码在VC6.0下面通过不了,因为有些语言特性是C++11标准的. 下面就是正文的开始吧. 一.C++必须说和必须略过的一些东西 1.工具 工具的话,简答的编程貌似现在已经习惯了在GCC(g++)下了.Linux平台下面,一般不需

C++基础学习教程(二)

接上一节内容 2.5条件和逻辑 自增和自减操作符 这个主要区别就是在前和后,大多数学习过其他语言的应该都知道.所以,一个程序带过. 示例如下: /************************************************************************* > File Name: list1001_++.cpp > Author: suool_hu > Mail: [email protected] > Created Time: 2014年0

C++基础学习教程(三)

承接上一讲. 2.7文件I/O 关于读写文件,C++中有一个专门的头文件<fstream>. 首先是读文件示例,如下: </pre><pre> /************************************************************************* > File Name: list1301_file.cpp > Author: suool > Mail: [email protected] > Cre

C++基础学习教程(八)

转载请注明出处:http://blog.csdn.net/suool/article/details/38300117 引入 在进行下一步的学习之前,我们需要厘清几个概念. RAII 首先介绍一个编程习语,"RAII"(ResourceAcquisition Is Initialization,资源获取即为初始化),他描述了利用构造函数\析构函数,并在函数返回时自动析构的机制.简言之,RAII意为构造函数获取一种资源;打开一个文件,一个网络连接,或仅仅是从某I/O流中复制一些标志.这种

C++基础学习教程(四)

2.9字符专题 2.9.1类型同义词 也就是typedef声明,这个东西就是相当于起绰号,为了方便记忆和简化而生.相信在学习其他语言的时候一定有所了解,在此不再赘述. 再次示例一个之前写过的用typedef改写的程序: /************************************************************************* > File Name: char_count.cpp > Author: suool > Mail: [email pr

android 基础学习图片六progross

加载进度条应用 android 基础学习图片六progross,布布扣,bubuko.com

javascript基础学习(六)

javascript之对象 学习要点: 对象的属性和方法 对象的原型 一.对象 对象其实就是一种引用类型,而对象的值就是引用对象的实例. 二.创建对象 在javascript中有两种对象,一种是系统内置对象,另一种是用户自己创建的对象. 1.使用构造函数创建内置对象 在javascript中有很多内置的对象,每个内置的对象都有一个构造函数,直接使用构造函数就可以创建并初始化一个对象. 在javascript中可以使用new运算符来调用构造函数创建对象.如:var myObject=new Obj

下载快速上手数据挖掘之solr搜索引擎高级教程(Solr集群、KI分词、项目实战)

Solr是一个高性能,采用Java开发,基于Lucene的全文搜索服务器.同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置.可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎. 快速上手数据挖掘之solr搜索引擎高级教程(Solr集群.KI分词.项目实战),刚刚入手,转一注册文件,视频的确不错,可以先下载看看:http://pan.baidu.com/s/1jIdgtWM 密码:s1t3