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,只不过是交换两个容器内部指向数据的指针。

相关文章
|
1天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
8 0
|
1月前
|
算法 安全 C++
提高C/C++代码的可读性
提高C/C++代码的可读性
48 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
81 4
|
2月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
2月前
|
存储 搜索推荐 C语言
深入C语言指针,使代码更加灵活(二)
深入C语言指针,使代码更加灵活(二)
|
2月前
|
存储 程序员 编译器
深入C语言指针,使代码更加灵活(一)
深入C语言指针,使代码更加灵活(一)
|
2月前
|
C语言
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
|
2月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
56 1
|
2月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
40 2
|
2月前
|
Linux C语言 C++
vsCode远程执行c和c++代码并操控linux服务器完整教程
这篇文章提供了一个完整的教程,介绍如何在Visual Studio Code中配置和使用插件来远程执行C和C++代码,并操控Linux服务器,包括安装VSCode、安装插件、配置插件、配置编译工具、升级glibc和编写代码进行调试的步骤。
338 0
vsCode远程执行c和c++代码并操控linux服务器完整教程