【C++】C++中的Sequence Point,构造函数等问题

本文将简要的讨论以下几个问题

  • 什么是Sequence Point
  • C++的构造函数为什么没有返回值
  • 如何显示调用构造函数和析构函数
  • 拷贝构造函数为什么必须是引用传递
  • 类成员函数中static和const 不能连用的原因

1. 什么是Sequence Point

在现今的笔试中 ,我们经常会遇到如下的问题,当下列语句执行完后,a的值是多少?

int a = 1; // no side effect
a = a ++;  // side effect

答案无非是提供了,1, 2, undefined…
其实上述例子,在C++中已经有相应的名词Sequence Point

A sequence point is a point in the program’s exexution sequence where all previous side-effects shall have take place and where all subsequent side-effects shall not have take place.

在C++中,表达式计算存在两种类型,一是无副作用的,而是有副作用(side effect)的。 如上述代码所示。
而一个 sequence point 按照上述定义为,该点前的表达式的所有副作用,在程序执行到达改点之前发生完毕;该点后的表达式的所有副作用,在程序执行到达该点时尚未发生。

  • 法则1 在表达式求值时,在前一个和下一个顺序点之中,一个对象所存储的值至多只能被修改一次。如下图的结果是undefined
    rule 1
  • 法则2 在表达式求值的过程中会更改某个对象的值,要求更改前的值被读取的唯一目的是用来确定存入新值。
    rule 2

在C中,规定的Sequence Point很少,因为这有益于编译器的优化最大化。


2. C++的构造函数为什么没有返回值

上一个简单的例子,帮助理解与记忆。

class Base {
// ...
};

void foo(int a) {
    // do something... 
}

void foo(const Base& base) {
    // do something...
}

int main()
{
    // 如果构造函数返回值,下面的结果将调用哪个函数?
    foo(Base()); 
    return 0;
}

很显然,构造函数不设定返回值,是因为构造函数的特殊性质决定的。如果有返回值,那么将会存在很多问题。


3. 如何显示调用构造函数和析构函数

事实上,我们采用new操作符时,一般会发生以下三件事情:

  • 调用::operator new 分配所需内存
  • 调用对象的构造函数
  • 返回新分配的并构造的对象的指针

我们用以下代码来模拟这个过程。结果说明,我们总是可是显示的调用构造函数和析构函数。

class Base
{
public:
    Base() { cout << "constructors" << endl; }
    ~Base() { cout << "Destructors" << endl; }
};

int main()
{
    Base *pb = (Base *)malloc(sizeof(Base));
    new(pb) Base();
    pb->~Base();
    delete pb;

    return 0;
}

4. 拷贝构造函数为什么必须是引用传递

在深入探讨之前,首先列出拷贝初始化的几种情况:

  • 在使用=定义变量时, 如 Foo newObj = oldObj
  • 将 一个对象作为实参传递给一个非引用类型的实参;
  • 从一个返回类型为非引用类型的函数返回一个对象;
  • 用花括号初始化一个数组中的元素或一个聚合类中的成员;
  • 某些类类型会对他们所分配的对象使用拷贝初始化。例如,当我们初始化标准库容器或者调用insert或push成员,容器会对其进行拷贝初始化。而用emplace成员创建的元素则都进行直接初始化。
class Base
{
public:
    Base() { cout << "constructors" << endl; }
    Base(const Base bs) {
        a = bs.num;
    }
    ~Base() { cout << "Destructors" << endl; }

private:
    int num;
};

正如上述所说的,在函数调用过程中,具有非引用类型的参数进行拷贝初始化。类似的,当一个具有非引用类型的返回类型时,返回值会被用来初始化调用方的结果。
拷贝构造函数被用来初始化非引用类类型参数,如果参数不是引用类型,则将进入死循环–>因为为了调用拷贝构造函数,必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环


5. 类成员函数中static和const 不能连用的原因

C++的设计准则之一:nonstatic member function必须至少和nonmember function有相同的效率。
例如:

float magnitude3d(const Point3d *_this) { //... }
float Point3d::magnitude3d() const { //... }

对于后者的nonstatic member function而言,其最终会扩展为:

Point3d Point3d::magnitude(const Point3d *const this) {
    //...
}

由上可见,const修饰的是指针this,而this属于object的范畴。而对于static而言,类中的static成员函数或成员变量,不隶属于某个具体的object,其范畴是整个class。因此,修饰的范畴不同,故static和const在类中对同一个成员函数或者成员变量不能一起使用


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值