从C到C++(1)
参考资料: 侯捷C++课程.
本文是从C入手C++学习过程中的一些记录. 文中代码均忽略了与对应主题无关的部分, 用 “…” 代替.
输出的变化
首先是基本输入输出的变化. C语言中的输出:
#include <stdio.h>
...
printf("i = %d", i);
...
在C++中输出变为
#include <iostream>
...
cout << "i = " << i << endl;
...
调用方式
调用自己写的头文件和基本库格式上有不同.
#include <iostream> // 调用基本库
#include "complex.h" // 调用自己写的库
自己写库的规范
要自己写一个Header文件(库)供其他项目调用, 需要注意header文件的行文规范.
#ifndef __COMPLEX__
#define __COMPLEX__
#include <cmath>
class ostream;
class complex;
/// forward declarations, 前置声明
complex&
__doapl (complex* ths, const complex& r);
/// class declarations, 类声明
class complex // class head
// class body
{
public:
complex(double r = 0, double i = 0)
: re(r), im(i) // 构造函数, 把值给对应的变量. 并且带默认值
{ }
complex& operator += (const complex&); // 声明一个函数, 但未定义
double real () const { return re;} // 传出实部
double imag () const { return im;} // 传出虚部
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
}
/// class definition, 类定义
complex::function ...
#endif
首先是防卫式声明. 第一句, 判断如果不曾定义过complex, 就把它定义出来, 按照后面文件主体的代码定义. 这样, 第二次进入这个文件, 就发现这个complex已经被定义过, 就不会再进入文件主体.
在主体部分写类的具体内容, 包含前置声明, 类声明, 类的定义.
类的模板 class template
在上面的类具体内容中限定了复数的实部虚部都为double类型, 但实际上我们可能用到float类型, int类型的复数, 难道要为了类型的改变重写一遍吗? 那太浪费时间了. C++提供了类模板工具, 我们可以不把类型定义死.
template <typename T> // 告诉编译器, 用T指代未定的类型名称
class complex // class head
// class body
{
public:
complex(T r = 0, T i = 0)
: re(r), im(i)
{ }
complex& operator += (const complex&);
T real () const { return re;}
T imag () const { return im;}
private:
T re, im;
friend complex& __doapl (complex*, const complex&);
}
{
complex<double> c1(2.5, 1.5); // 告诉编译器未定类型名称T绑定为double
complex<int> c2(2, 6);
}
类的声明
访问级别
- 一般来说, 数据是作为private的, 只有类自己能看到, 不让外界轻易访问和修改.
- 要给外界使用的函数, 就放在public级别.
- 并不一定要把所有的public和private集中到两段内, 可以随用随定义.
- 尽量, 我们要让数据private, 所以数据的读写要另外写配套的函数放在public里面.
inline函数(内联函数)
- 内联函数会快会好, 但太复杂的函数, 编译器做不到inline.
- 一个函数是否变为inline函数, 我们是不知道的.
- 可以把构造函数, 数据读写函数作为内联函数.
- 在函数定义前面加上inline表示希望编译器把它作为内联函数(最后能不能成还得看编译器)
inline double imag(const complex& x)
{
return x.imag();
}
构造函数
- 构造函数是用来创建对象的. 它的名字和类的名字是相同的.
- 构造函数没有返回值, 不需要有.
- 可以用赋值的方式: re = r 写构造函数, 但是我们最好使用初值列的语法来写: re(r)
complex (double r = 0, double i = 0)
: re (r), im(i)
...
complex c1(2.5, 1.5);
complex c2();
complex
- 一个变量的数值设定, 有两个阶段, 一是初始化, 二是赋值. 初始化就是构造函数的初值列所做的事情, 如果用赋值的方式来写, 效率会低一些.
- 不可能调用构造函数, 只需要在初始化一个变量的时候直接用即可.
- 不带指针的类一般不需要析构函数(什么是析构函数?).
- 是可以有多个同名函数的(但实际在编译器之后是不同名的). 这是重载(overloading). 只要参数不同. 所以常常会写多个同名的构造函数, 应对不同的情况.
- 如果已经有一个带默认参数的构造函数了, 这个时候再去写一个不需要参数的构造函数, 是会带来混乱的, 这种情况是不合法的.