Algorithm-Pattern
  • Introduction
  • C & C++
    • C语言
      • C/C++编译器
      • 宏的使用
      • 编译过程
      • 指针 & 数组
      • 柔性数组
      • 函数指针 & 回调函数
      • C标准库之stdio
      • C标准库之string
      • C标准库之errno
      • C标准库之stdarg
      • C标准库之regex
    • C++基础语法
      • 自增(++) & 自减(--)
      • c语言到c++
      • 可变模板参数
      • 强制类型转换
      • C/C++类型转换的本质
      • 指针 & 引用
      • const的用法
      • static的用法
      • 重要的关键字(一)
      • 重要的关键字(二)
      • 内存申请和释放
      • 内联函数
      • 函数 & 运算符重载
      • 面向对象之封装
      • 构造函数 & 析构函数
      • 面向对象之继承
      • 面向对象之多态
      • 泛型编程
      • 异常
      • 再谈函数指针
    • C++并发编程
      • C++的锁
      • 并发与多线程
    • C++高级特性
      • 函数对象
      • 移动语义 & 完美转发
      • lambda表达式
      • RTTI技术
      • RAII技术
      • 智能指针
      • 模板的特化
      • C++静态库和动态库
      • 内存溢出和内存泄漏
    • STL基础
      • String
      • array/vector/list
      • deque/priority_queue
      • set/map
      • unordered_set/unordered_map
      • algorithm_1
      • functional
      • allocator
    • C++标准库
      • IO
      • Tuple
      • regex
      • bitset
      • numeric
    • STL深入源码
      • vector内部实现
      • deque内部实现
      • sort函数实现
    • 第三方库
      • JsonCpp
      • ProtoBuf
  • 数据结构
    • 线性表
    • 字符串
    • 栈和队列
    • 二叉树
    • 平衡二叉树
    • 平衡多路搜索树
    • 树结构的延申
    • 图
    • 二进制
    • 散列表
  • 算法基础
    • 排序算法
    • 查找算法
    • 数学问题
    • 并查集
    • 递归算法
    • 附加——主定理
    • Catalan数
  • 算法设计思想
    • 滑动窗口思想
    • BFS/DFS
    • 二分法
    • 回溯法
    • 贪心算法
    • 分治法
    • 动态规划
    • 分支限界算法
    • 有限状态机(FSM)
  • LeetCode系列
    • 死磕二叉树
    • 股票买卖问题
    • 打家劫舍问题
    • 跳跃游戏问题
    • 括号匹配问题
    • 石子游戏问题
    • 子序列问题
    • 数组 & 矩阵
    • 排列 & 组合
  • 经典算法问题
    • 几何问题
    • 区间问题
    • 背包问题
    • 石子堆问题
    • 表达式求值
  • 面试题
    • 数据结构和算法基础
    • 程序设计题
      • 实现双线程交替打印
      • C++实现读写锁
      • 实现阻塞队列
      • 实现环形队列
      • 实现线程池
      • 实现智能指针
      • 实现string类
      • 实现高性能local cache
      • 实现内存池
      • 生产者-消费者模型
      • 设计定时器
    • 经典的算法题
    • C++面试题总结
    • 面试算法题总结
由 GitBook 提供支持
在本页
  • 1、内置类型
  • 2、自定义类型
  • 2.1、a++与++a区别
  • 2.2、a+=b与a=a+b的区别
  • 3、求值过程中的副作用与顺序点
  • 幕后的“顺序点”

这有帮助吗?

  1. C & C++
  2. C++基础语法

自增(++) & 自减(--)

上一页C++基础语法下一页c语言到c++

最后更新于4年前

这有帮助吗?

1、内置类型

++a    a++   a=a+1    a+=1

对于内置类型,对于现代编译器而言,这四个的效率都是没有区别的,在VS2019下查看汇编代码如下:

注意:不要在一条语句中使用多个++,因为在不同系统中对这样的情况处理可能不一样,比如y = (4 + x++) + (6 + x++)。这条语句只能保证程序执行到下一条语句之前,x被递增两次,并不能保证4 + x++后立即自增。

  1. a++是先赋值再自增,++a是先自增再赋值。

  2. a++是先用临时对象保存原来的对象,然后对原对象自增,再返回临时对象,不能作为左值;++a是直接对于原对象进行自增,然后返回原对象的引用,可以作为左值。

  3. 由于要生成临时对象,a++需要调用两次拷贝构造函数与析构函数(将原对象赋给临时对象一次,临时对象以值传递方式返回一次);++a由于不用生成临时变量,且以引用方式返回,故没有构造与析构的开销,效率更高。

左值一般是可以放在赋值符号左边的值,其在内存中有实体;右值一般只能放在赋值符号右边,不具有内存实体,无法通过取地址获得相应对象。如:

class Point {
    int x_;
    int y_;
public:
    Point(int x = 0, int y = 0);
    Point(const Point&);
    ~Point();
    Point& operator++();         //前置
    const Point operator++(int); //后置,int仅仅为了重载
    Point operator+(const Point&);
    Point& operator+=(const Point&);
    void DisplayPoint();
};

Point::Point(int x, int y)
{
    x_ = x;
    y_ = y;
    cout << "this is constructor" << endl;
}

Point::Point(const Point& b)
{
    this->x_ = b.x_;
    this->y_ = b.y_;
    cout << "this is copy constructor" << endl;
}

Point::~Point()
{
    cout << "this is destructor" << endl;
}

Point& Point::operator+=(const Point& _right)
{
    this->x_ += _right.x_;
    this->y_ += _right.y_;
    return *this;
}

Point Point::operator+(const Point& _right)
{
    Point temp;
    temp.x_ = this->x_ + _right.x_;
    temp.y_ = this->y_ + _right.y_;
    return temp;
}


Point& Point::operator++()
{
    ++x_;
    ++y_;
    return *this;
}

const Point Point::operator++(int)
{
    Point temp(*this);
    this->x_++;
    this->y_++;
    return temp;
}

void Point::DisplayPoint()
{
    cout << "x: " << this->x_ << endl;
    cout << "y: " << this->y_ << endl;
}
int main() {
    Point a(1, 1);
    cout << endl << "this is a++: " << endl;
    a++;
    cout << endl << "this is ++a: " << endl;
    ++a;
	return 0;
}

// 输出
this is constructor

this is a++:
this is copy constructor
this is copy constructor
this is destructor
this is destructor

this is ++a:
this is destructor

a++将会有两次的拷贝构造与析构的调用,效率非常低。

class Point {
    int x_;
    int y_;
public:
    Point(int x = 0, int y = 0);
    Point(const Point&);
    ~Point();
    Point& operator++();     //前置
    Point operator++(int);   //后置,去掉了const限定
    Point operator+(const Point&);
    Point& operator+=(const Point&);
    void DisplayPoint();
};

Point::Point(int x, int y)
{
    x_ = x;
    y_ = y;
    cout << "this is constructor" << endl;
}

Point::Point(const Point& b)
{
    this->x_ = b.x_;
    this->y_ = b.y_;
    cout << "this is copy constructor" << endl;
}

Point::~Point()
{
    cout << "this is destructor" << endl;
}

Point& Point::operator+=(const Point& _right)
{
    this->x_ += _right.x_;
    this->y_ += _right.y_;
    return *this;
}

Point Point::operator+(const Point& _right)
{
    Point temp;
    temp.x_ = this->x_ + _right.x_;
    temp.y_ = this->y_ + _right.y_;
    return temp;
}


Point& Point::operator++()
{
    ++x_;
    ++y_;
    return *this;
}

Point Point::operator++(int) // int仅仅为了重载
{
    Point temp(*this);
    this->x_++;
    this->y_++;
    return temp;
}

void Point::DisplayPoint()
{
    cout << "x: " << this->x_ << endl;
    cout << "y: " << this->y_ << endl;
}

int main() {
    Point b(2, 2);
    cout << endl << "this is &b: " << &b << endl;

    Point* c;
    c = &(++b);
    cout << endl << "this is c = &(++b): " << c << endl;
    c->DisplayPoint();

    c = &(b++);
    cout << endl << "this is c = &(b++): " << c << endl;
    c->DisplayPoint();

    ++b = *c;
    b.DisplayPoint();
    b++ = *c;
    b.DisplayPoint();
	return 0;
}

// 输出
this is constructor

this is &b: 008FFD38

this is c = &(++b): 008FFD38
x: 3
y: 3
this is copy constructor
this is copy constructor
this is destructor
this is destructor

this is c = &(b++): 008FFC5C
x: 3
y: 3
x: 3
y: 3
this is copy constructor
this is copy constructor
this is destructor
this is destructor
x: 4
y: 4
this is destructor

可以看到++b返回的对象指针跟b原来的地址是一样的,而b++返回的对象地址跟原来的b地址不一样(应该是临时对象的地址),虽然可以取到地址,但当成左值将有可能导致错误。比如b++ = c就不会将c给b,达不到原来的目的。为此,我们应该将后置的++函数返回值定义为const类型:const Point Point::operator++(int),就可以避免这种当成左值情况出现。另外发现返回temp的引用可以减少一次拷贝和析构,但是不建议返回局部变量的引用!因为函数退出,局部变量将析构,引用就会指向不确定内存。

a+=b返回的是a的引用,中间不涉及构造与析构,效率与++a一样。而a=a+b则会生成临时变量,而且以值传递方式返回,会有两次的构造与析构,与a++一样。

int i = 1;
i = (++i) + (++i); // i = ?

对于该代码,gcc和clang给出了不同的结果。clang给出了5,这很好理解;gcc却给出了6。为什么是6?

如果把i换成不同的变量,我们能写出这样的代码:k = (++i) + (++j)。尽管i和j自增先后不确定,但对结果是没有影响的。编译器可以这样拆分它:

++i;
++j;
k = i + j;

那么,我们把三个i代入,就得到了:

++i; // i = 2
++i; // i = 3
i = i + i; // i = 3 + 3

因此结果是6。

幕后的“顺序点”

之所以(++i) + (++i)未定义,这涉及到“顺序点”的概念。

“副作用”,即代码中对数据的改变,++i和i = ...都会产生副作用。C/C++为了让编译器能更好地优化代码,定义了一些位置,编译器能保证这些位置之后的副作用开始前,之前的副作用完成。这些位置,也就是顺序点。

顺序点包括:

  • 两个语句之间,for循环的括号中包含了三个语句;

  • 函数调用的参数与函数返回之间(但是参数自身没有顺序保证);

  • 短路求值操作符前后;

  • 问号表达式的条件与结果之间;

  • 变量初始化之间、之后。

2、自定义类型

2.1、a++与++a区别

2.1.1、效率检测

2.1.2、左右值检测

2.2、a+=b与a=a+b的区别

3、求值过程中的副作用与顺序点

✏️
🖋️
💎
💎
🖋️
✏️
✏️