【20170710】C++ 初学踩过的一些坑总结

关于double与float

字面值默认是double, 加f才是float
如124412.0是double。124412.0f才是float
没特殊需要只用double(现代CPU可直接运算,性能与存储速度不比float慢),特殊情况考虑float和long double等

类型转换

1 / 2 = 0 // both int
1.0 /2 = 0.5 // double and int
1 / 2.0 = 0.5 // int and double

io流操作符setw()

setw()只对紧随其后的输出起作用,其他的操作如setfill()对随后的所有输出起作用。

cout << setfill('0')<< setw(5) << 233 << 666;

将输出

00233666

关于类的数据成员初始化代码位置问题

有且只有static成员可以且必须在类外初始化,非staitc只能在类内初始化。

非const不可以直接在声明后写初值
const数据成员不可以在构造函数体中赋值。
函数实现不管是否staitc/const可以在任何地方写函数体,但在类内会默认当做inline函数

乱七八糟的……画了个流程图结合下面示例看一下吧

流程图包含4种位置(类外初始化、声明处初始化、构造函数初始化列表处初始化、构造函数体赋值),没有出现的即为不可以。

其中构造函数体中代码,严格来讲不是初始化而是赋值,但是对于一些内置类型来说也相差无几。

关于类的数据成员初始化代码位置问题

类外初始化示例:

class c
{
private:
     static int x_;
public: 
     c(){}; 
};
static int c::x_ = 666; // correct

类内声明处初始化示例:

class c
{
private:
     const int x_ = 666; // correct
public: 
     c(){}; 
};

初始化列表示例:

class c
{
private:
     const int x_; 
public: 
     c():x_(666){}; // correct
};

函数体示例:

class c
{
private:
     int x_; 
public: 
     c():{
        this->x_ = 666; // correct
     }; 
};

堆上动态分配的类数据成员

应该定义拷贝构造函数与赋值运算符函数,否则按编译器默认行为可能造成出错(默认浅拷贝行为)

myClass(const myClass& anotherObject)
{
     // run when constructing object (this is still uninitialized)……
}

myClass& operator=(const myClass& anotherObject)
{
    // run when be assigned (this is already initialized)……
}

const对象与非const对象可以相互转换

 const int a =0;
 int b;
 b = a; //correct
 const int c = b; //correct

对于类来说,非const对象可以直接转换为const对象,而const对象转换为非const对象则需要对应的接收const对象的复制构造函数(默认会有一个,但是如果已有复制构造函数但不接收const对象则会导致无法转换)

友元函数

声明友元函数时,即使并在头文件中不使用具体类型,也要include具体文件以引入具体类型声明。

类继承体系

非虚函数不要在子类中重定义
原因:如果需要重定义,那么就不应该继承,子类与基类不构成is-a关系
非虚函数静态联编,使基类引用总调用基类函数,即使这个基类引用实际引用了一个派生类对象

从基类继承的带默认参数的函数,不要重定义其参数

重载函数注意事项

重载类构造函数

派生类永远在初始化列表中显式调用基类构造函数
复制构造函数还要注意参数要接收const对象以提供const对象到非const对象的转换

重载类型转换函数

没有返回值,且必须是非静态成员函数

//correct
myClass::operator double()
{
    return this.toDouble();
}

//error
static myClass::operator double()
{
    //……
}

//error
operator double(const myClass &object)
{
    return object.toDouble();
}

重载赋值运算符

永远应该返回*this以便使用于连续赋值
永远应该检查到自身的赋值以防错误
派生类永远显示调用基类赋值运算符,若基类没有重载赋值运算符,则应 static_cast<base_class>(*this)=rhs;
参数要接收const对象以提供const对象到非const对象的转换

const myClass& operator=(const myClass& rhs)
{
    base_class::operator=(rhs); // or static_cast<base_class>(*this)=rhs;
    if(this != &rhs)
    {
         // do something
    }
    return *this;
}
myClass c1,c2,c3;
c1 = c2 = c3;

运算符==与!=重载

总应该同时重载两者,且执行结果相反。

重载数组下标运算符

返回值类型需为非const引用类型,以便进行c[i]=xxx;的赋值操作

重载&& || ,

不要试图重载&& || ,
重载后他们便成为了普通函数,无法保证从左到右求值,&&和||使用的短路求值也无法保证。

异常处理

避免在构造函数中抛出异常
因为不会调用析构函数,而可能导致已经分配的资源无法释放,造成内存泄漏。
如果一定要抛出也要保证会安全释放,譬如使用智能指针。

catch类型最好是引用类型
对于类对象来说,引用不仅节约了拷贝对象的时间,而且防止可能异常对象会复制失败。
同时可以利用多态,而防止对象截断。

dynamic_cast操作的对象是类指针
bad_cast只有在无法相互转换的类进行dynamic_cast时才会抛出,如同级的互相转换。
如指向派生类对象的基类指针试图转换到另一派生类的指针,即试图向下类型转换(从父类转换为子类)失败,则会返回空指针而不会抛出bad_cast。

STL中异常基类及一系列派生类在<exception>中定义,而runtime_error与logic_erro还需要<stdexcept>

函数模板

在试图进行类型匹配时,不会进行类型转换。
即(T a, T b)只匹配(int, int)(double, double)等相同类型,而不会进行类型转换以匹配(int, double)
模板参数只接受编译期可确定的常量值

template <typename T>
void func(T a, T b)
{}
func(1, 2); //legal
func(1.0, 2.0); //legal
func(1.0, 2); //illegal

若为类成员函数,则需将定义放在头文件.h中以编译期实例化。

类模板

默认类型参数

可以为类模板指定默认类型参数

template <typename T = int>

但不能在函数模板中使用默认类型参数。

继承限制

类模板可以继承普通类。
类模板可以继承类模板。
普通类可以继承类模板的特化。
普通类不可以继承类模板。
总结:若子类为已实例化的类,则基类必须已实例化。

静态成员

每个模板实例类都有一份静态数据成员

嵌套类型

一个模板中的依赖于模板参数的名字被称为依赖名字
当一个 依赖名字 嵌套在一个类的内部时,我称它为 嵌套依赖名字
嵌套依赖名字必须显式使用typename关键字声明其为类型,否则将不被认为是类型而可能导致编译错误。

例如:

template<typename T> void f() {
    T::c* x;
   ...
}

这里没有使用typename声明,我们想表达的是:定义一个指针变量x,指向的数据类型是类T中的c类型。
然而编译器将认为是T中的静态成员变量c与已存在的变量x进行*运算相乘,此时编译出错。

如果要声明x为C内的nested dependent name(嵌套依赖名字),必须显式使用typename:

template<typename T> void f() {
    typename T::c* x;
   ...
}
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

请我喝杯咖啡吧~

支付宝
微信