C++代码简化之道 (2):消除非必要的指针

简介: 作为C++程序员,肯定免不了和指针打交道了。一般我们使用指针都是为了避免不必要的拷贝,但有时候其实可以简化掉它。

作为C++程序员,肯定免不了和指针打交道了。一般我们使用指针都是为了避免不必要的拷贝,但有时候其实可以简化掉它。


活用三目运算符


先看一段例子,假设我们有一段老代码:


...
  const string s = "1234567";
  ...
  foo(s); // foo(const string&)
  bar(s); // bar(const string&)
  ...


在某个函数中我们有一个常量s,参与后续计算逻辑。某一天逻辑调整,我们通过某种计算(比如解析配置)获得了一个 vector类型的变量 v,如果v里面有元素就取第一个元素赋值给s,否则还沿用默认值"1234567"。那么初学者可能这样改:


string s = "1234567";
  if (v.size() > 0) {
      s = v[0];
  }
  ...
  foo(s);
  bar(s);
  ...


或者:


string s;
  if (v.size() > 0) {
      s = v[0];
  } else {
      s = "1234567";
  }
  ...
  foo(s);
  bar(s);
  ...


但是无论哪种都存在string的operato=() 的调用。一开始的初始化可能会变得无意义,因为紧接着就会被覆盖。稍有经验的程序员会这样写:


const string def_s = "123467";
  const string* sp = &def_s;
  if (v.size() > 0) {
      sp = &v[0];
  }
  const auto& s = *sp;
  ...
  foo(s);
  bar(s);
  ...


使用指针就能避免这次operato=()的开销。但其实利用三目运算符会更简洁:


const auto& s = v.size() > 0 ? v[0]: "1234567";
  ...
  foo(s);
  bar(s);
  ...


如果有更多的条件和判断,那么三目运算符嵌套就会影响可读性了,所以不建议盲目使用三目运算符,不要生搬硬套。


通过leetcode题目来看swap


leetcode 102. 二叉树的层序遍历:


给你一个二叉树,请你返回其按 层序遍历 得到的节点值。(即逐层地,从左到右访问所有节点)。


class Solution {
public:
   vector<vector<int>> levelOrder(TreeNode* root) {
   }
};


题目不难,就是二叉树的BFS遍历,一般实现BFS我们通常都借助队列 queue来实现。比如:


class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (!root) {
            return {};
        }
        vector<vector<int>> ans;
        queue<TreeNode*>* q = new queue<TreeNode*>;
        q->push(root);
        while (!q->empty()) {
            queue<TreeNode*>* tmp_q = new queue<TreeNode*>;
            vector<int> v;
            while (!q->empty()) {
                TreeNode* node = q->front();
                if (node) {
                    v.push_back(node->val);
                    tmp_q->push(node->left);
                    tmp_q->push(node->right);
                }
                q->pop();
            }
            // 调整q的指向
            q = tmp_q;
            if (!v.empty()) {
                ans.push_back(move(v));
            }
        }
        return ans;
    }
};


上面代码中,之所以qtmp_q都使用指针,是因为最外层的while循环判断条件是一直判断 q是否为空,然而每一层遍历的q 其实是不同的。使用指针目的是在单层遍历完成后,直接修改q指针的指向,使其指向下一层的队列tmp_q。


另外请忽略代码中只有new,而没有delete这件事,因为leetcode刷题一般都不考虑内存泄漏


如果不想让q 变成指针呢?那么很多人会这样写:


class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (!root) {
            return {};
        }
        vector<vector<int>> ans;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            vector<TreeNode*> tmp_v;
            vector<int> v;
            while (!q.empty()) {
                TreeNode* node = q.front();
                if (node) {
                    v.push_back(node->val);
                    tmp_v.push_back(node->left);
                    tmp_v.push_back(node->right);
                }
                q.pop();
            }
            // 遍历,入队
            for (TreeNode* node: tmp_v) {
                q.push(node);
            }
            if (!v.empty()) {
                ans.push_back(move(v));
            }
        }
        return ans;
    }
};


借助一个临时的vector来处理,在每一层遍历完成后,再从vector里把下一层的节点push到已经变成空的队列q中。很明显这里的问题就是会多一些遍历和push的操作。

能不能即不把q弄成指针,又不需要做额外的push呢?答案是借助swap:


class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (!root) {
            return {};
        }
        vector<vector<int>> ans;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            queue<TreeNode*> tmp_q;
            vector<int> v;
            while (!q.empty()) {
                TreeNode* node = q.front();
                if (node) {
                    v.push_back(node->val);
                    tmp_q.push(node->left);
                    tmp_q.push(node->right);
                }
                q.pop();
            }
           // 交换q和tmp_q的内部存储
            q.swap(tmp_q);
            if (!v.empty()) {
                ans.push_back(move(v));
            }
        }
        return ans;
    }
};


不止queue,像vector、map等STL容器都有swap成员函数。请放心swap的开销是常数时间。


STL容器会占用栈存储和堆存储,比如vector,即使你使用的局部变量的vector,它内部也会把具体的数据用堆来存储,在类内部使用一个指针指向这块内存。


这个指针本身是栈存储,它指向的位置是堆存储区。所谓的swap,只不过是交换两个容器内部指向数据的指针。

相关文章
|
30天前
|
C++
C++ 语言异常处理实战:在编程潮流中坚守稳定,开启代码可靠之旅
【8月更文挑战第22天】C++的异常处理机制是确保程序稳定的关键特性。它允许程序在遇到错误时优雅地响应而非直接崩溃。通过`throw`抛出异常,并用`catch`捕获处理,可使程序控制流跳转至错误处理代码。例如,在进行除法运算或文件读取时,若发生除数为零或文件无法打开等错误,则可通过抛出异常并在调用处捕获来妥善处理这些情况。恰当使用异常处理能显著提升程序的健壮性和维护性。
45 2
|
22天前
|
算法框架/工具 C++ Python
根据相机旋转矩阵求解三个轴的旋转角/欧拉角/姿态角 或 旋转矩阵与欧拉角(Euler Angles)之间的相互转换,以及python和C++代码实现
根据相机旋转矩阵求解三个轴的旋转角/欧拉角/姿态角 或 旋转矩阵与欧拉角(Euler Angles)之间的相互转换,以及python和C++代码实现
90 0
|
17天前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。
|
17天前
|
C++
C++(九)this指针
`this`指针是系统在创建对象时默认生成的,用于指向当前对象,便于使用。其特性包括:指向当前对象,适用于所有成员函数但不适用于初始化列表;作为隐含参数传递,不影响对象大小;类型为`ClassName* const`,指向不可变。`this`的作用在于避免参数与成员变量重名,并支持多重串联调用。例如,在`Stu`类中,通过`this-&gt;name`和`this-&gt;age`明确区分局部变量与成员变量,同时支持链式调用如`s.growUp().growUp()`。
|
29天前
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
23 1
|
30天前
|
程序员 C++ 开发者
C++命名空间揭秘:一招解决全局冲突,让你的代码模块化战斗值飙升!
【8月更文挑战第22天】在C++中,命名空间是解决命名冲突的关键机制,它帮助开发者组织代码并提升可维护性。本文通过一个图形库开发案例,展示了如何利用命名空间避免圆形和矩形类间的命名冲突。通过定义和实现这些类,并在主函数中使用命名空间创建对象及调用方法,我们不仅解决了冲突问题,还提高了代码的模块化程度和组织结构。这为实际项目开发提供了宝贵的参考经验。
43 2
|
30天前
|
C++
拥抱C++面向对象编程,解锁软件开发新境界!从混乱到有序,你的代码也能成为高效能战士!
【8月更文挑战第22天】C++凭借其强大的面向对象编程(OOP)能力,在构建复杂软件系统时不可或缺。OOP通过封装数据和操作这些数据的方法于对象中,提升了代码的模块化、重用性和可扩展性。非OOP方式(过程化编程)下,数据与处理逻辑分离,导致维护困难。而OOP将学生信息及其操作整合到`Student`类中,增强代码的可读性和可维护性。通过示例对比,可以看出OOP使C++代码结构更清晰,特别是在大型项目中,能有效提高开发效率和软件质量。
21 1
|
1月前
|
安全 NoSQL Redis
C++新特性-智能指针
C++新特性-智能指针
|
1月前
|
编译器 C++
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
|
23天前
|
C++
C++代码来计算一个点围绕另一个点旋转45度后的坐标
C++代码来计算一个点围绕另一个点旋转45度后的坐标
42 0