首页 > 代码库 > C++基础学习教程(六)----类编写的前情回顾以及项目实战(1)
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对象的时候就可以调用成员函数.