当前位置: 首页 > news >正文

突破编程_C++_C++11新特性(nullptr、constexpr与基于范围的 for 循环)

1 nullptr

C++11 引入了一个名为 nullptr 的新关键字,用于替换 C++98/03 中的 NULL 宏。nullptr 是空指针常量的一种类型安全的表示方法,它解决了使用 NULL 时可能遇到的一些问题和歧义。

1.1 NULL 的问题

在 C++98/03 中,NULL 通常被定义为 (void*)0 或 0,这取决于具体的实现。使用 NULL 有两个潜在的问题:

(1)类型不安全: NULL 可以隐式转换为任何指针类型,这可能导致一些类型混淆的错误。

(2)重载函数的歧义: 如果函数重载了接受 int 和指针参数的版本,使用 NULL 可能会导致调用错误版本的函数。

1.2 nullptr 的优势及示例

nullptr 的优势如下:

(1)类型安全: nullptr 是一个指针字面量,它只能转换为指针类型,不能转换为整数类型。

(2)消除重载函数的歧义: 使用 nullptr 可以确保调用正确版本的函数,因为它只能表示指针类型的空值。

nullptr 的示例如下:

示例 1:替换 NULL

#include <iostream>  int* getPointer() {  // ...  return nullptr; // 返回空指针  
}  int main() 
{  int* ptr = getPointer();  if (nullptr == ptr) { // 检查指针是否为空  std::cout << "Pointer is null." << std::endl;  } else {  std::cout << "Pointer is not null." << std::endl;  }  return 0;  
}

示例 2:消除重载函数的歧义

#include <iostream>  void foo(int x) {  std::cout << "Called foo(int)" << std::endl;  
}  void foo(int* ptr) {  if (nullptr == ptr) {  std::cout << "Called foo(int*) with null pointer." << std::endl;  } else {  std::cout << "Called foo(int*) with non-null pointer." << std::endl;  }  
}  int main() 
{  foo(0);         // 调用 foo(int)  foo(nullptr);   // 调用 foo(int*)  return 0;  
}

在这个例子中,如果使用 NULL 替换 nullptr,那么 foo(NULL) 的调用将变得不明确,因为编译器无法区分应该调用哪个版本的 foo 函数。但是使用 nullptr 可以消除这种歧义。

1.3 注意事项

使用 nullptr 的注意事项如下:

  • 确保在编写新的 C++11 代码时使用 nullptr 替代 NULL。
  • 当升级旧代码时,逐步替换 NULL 为 nullptr,确保代码的正确性和兼容性。
  • 了解编译器和库是否支持 C++11,以确保 nullptr 可以被正确识别和使用。
  • 通过使用 nullptr,可以提高代码的类型安全性,并消除由于使用 NULL 而可能引入的错误和歧义。

2 constexpr

C++11 引入了一个非常强大的关键字 constexpr,它允许在编译时计算表达式的值,并且这些值可以用于初始化常量表达式。这不仅可以提高代码的可读性和性能,还可以用于创建模板元编程中的常量值。

2.1 constexpr 的基本用法

constexpr可以用于变量、函数或类的构造函数。当用于变量时,它表示该变量的值是一个常量表达式,必须在编译时确定。当用于函数或构造函数时,它表示该函数或构造函数的返回值是一个常量表达式。

示例 1:constexpr变量

constexpr int a = 5; // a是一个编译时常量  
constexpr int b = a * 2; // b的值在编译时确定为10

示例 2:constexpr函数

constexpr int square(int x) {  return x * x;  
}  int main() 
{  constexpr int c = square(3); // c的值在编译时确定为9  return 0;  
}

2.2 constexpr 的限制和扩展

在 C++11 中,constexpr 函数有一些限制:

  • 它们只能包含单个返回语句。
  • 它们只能调用其他 constexpr 函数。
  • 它们不能包含非 constexpr 的变量或函数调用。

注意:从 C++14 开始,这些限制得到了放宽,允许 constexpr 函数有更多的灵活性,包括循环、条件语句等。

示例 1:C++14 中的 constexpr 函数

constexpr int factorial(int n) {  int result = 1;  for (int i = 1; i <= n; ++i) {  result *= i;  }  return result;  
}  int main() 
{  constexpr int d = factorial(5); // d 的值在编译时确定为 120  return 0;  
}

constexpr 也可以用于类的构造函数,允许在编译时创建类的常量对象。

示例 2:constexpr 构造函数

#include <iostream>  
class Point {
public:constexpr Point(int x = 0, int y = 0) : m_x(x), m_y(y) {}constexpr int getX() const { return m_x; }constexpr int getY() const { return m_y; }private:int m_x, m_y;
};int main()
{constexpr Point p(1, 2); // p 是一个编译时常量对象  constexpr int e = p.getX(); // e 的值在编译时确定为 1  return 0;
}

2.3 使用 constexpr 的优势

使用 constexpr 的优势如下:

  • 性能提升:由于 constexpr 的值在编译时确定,所以不需要在运行时进行计算,这可以提高代码的执行效率。
  • 类型安全:使用 constexpr 可以确保常量表达式在编译时就是正确的,从而避免运行时错误。
  • 模板元编程:constexpr 在模板元编程中特别有用,因为元编程需要在编译时计算值。

2.4 注意事项

使用 constexpr 的注意事项如下:

  • 并不是所有的编译器都完全支持 C++11 和 C++14 中的 constexpr 特性,特别是在处理复杂表达式和函数时。因此,在使用 constexpr 时,最好检查编译器文档以确保兼容性。
  • 过度使用 constexpr 可能会使代码难以阅读和维护。通常,只在确实需要编译时常量表达式的地方使用它。
  • 通过掌握 constexpr 的用法,可以写出更加高效、安全的 C++ 代码,并利用编译时计算的优势来提升性能。

3 范围的 for 循环

基于范围的for循环(Range-based for loop)是 C++11 引入的一个新特性,它使得遍历容器或数组变得更加简单和直观。这种循环方式可以自动处理迭代器,使得代码更加简洁易读。

3.1 基本语法

基于范围的for循环的基本语法如下:

for (declaration : expression) {  // 循环体  
}

其中:

  • declaration:定义循环变量的类型。
  • expression:返回一个序列,通常是一个容器或数组。

3.2 示例

下面是一些使用基于范围的for循环的示例:

示例 1:遍历数组

#include <iostream>  int main() 
{  int arr[] = {1, 2, 3, 4, 5};  for (int num : arr) {  std::cout << num << " ";  }  std::cout << std::endl;  return 0;  
}

输出:

1 2 3 4 5

示例 2:遍历 STL 容器

#include <iostream>  
#include <vector>  
#include <string> int main() 
{  std::vector<std::string> vec = {"apple", "banana", "cherry"};  for (const std::string& fruit : vec) {  std::cout << fruit << " ";  }  std::cout << std::endl;  return 0;  
}

输出:

apple banana cherry

示例 3:遍历自定义容器

只要自定义容器支持 begin() 和 end() 成员函数,并且这些函数返回的迭代器支持解引用操作,那么就可以使用基于范围的 for 循环来遍历它:

#include <iostream>  class IntegerSequence {
public:IntegerSequence(int start, int end) : m_start(start), m_end(end) {}class iterator {public:iterator(int value) : m_current(value) {}bool operator==(const iterator& other) const {return m_current == other.m_current;}bool operator!=(const iterator& other) const {return !(*this == other);}int operator*() const {return m_current;}iterator& operator++() {++m_current;return *this;}private:int m_current;};iterator begin() const {return iterator(m_start);}iterator end() const {return iterator(m_end + 1);}private:int m_start;int m_end;
};int main() 
{IntegerSequence seq(1, 5);// 使用基于范围的for循环遍历自定义类型  for (int num : seq) {std::cout << num << " ";}std::cout << std::endl;return 0;
}

输出:

1 2 3 4 5

在上面的代码中,IntegerSequence 类包含了一个迭代器 iterator,该迭代器可以递增并返回当前值。IntegerSequence 的 begin 和 end 成员函数返回迭代器,分别指向序列的开始和结束位置。

在 main 函数中,创建了一个 IntegerSequence 对象,并使用基于范围的 for 循环遍历它。循环体会自动使用 begin 和 end 返回的迭代器,并在每次迭代时递增迭代器,直到它等于 end 返回的迭代器为止。

3.3 注意事项

如下是基于范围的 for 循环的一些注意事项:

  • 基于范围的 for 循环只能用于支持迭代器的序列。
  • 在循环体内,无法直接修改序列的大小(例如,在遍历 std::vector 时,不能添加或删除元素)。如果需要修改序列,则需要使用传统的迭代器循环。
  • 对于引用类型的循环变量(如示例 2 中的 const std::string& fruit),实际上是在引用序列中的元素,而不是复制它们。这意味着对循环变量的修改会影响序列中的实际元素(如果没有使用 const 的话)。

相关文章:

  • 数字孪生与智慧城市:实现城市治理现代化的新路径
  • ES6(二):解构赋值、Symbol、Map和Set、数组的扩展方法
  • 【漏洞复现】大华智慧园区综合管理平台deleteftp命令执行漏洞
  • 从零开始的LeetCode刷题日记:替换数字
  • 小白必看的Python基础之函数篇
  • 如果网络不好 如何下载huggingface上的模型
  • 华为三层交换机:ACL的基本实验
  • WPF制作带图标和文字的按钮模板(通过附加属性实现)
  • 3、设计模式之工厂模式2(Factory)
  • Ubuntu 20.04 系统如何优雅地安装NCL?
  • web 课程
  • Linux-新手小白速秒Hadoop集群全生态搭建(图文混编超详细)
  • 用户数据的FLASH存储与应用(FPGA架构)
  • rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
  • HarmonyOS NEXT应用开发—状态栏显隐变化
  • create-react-app项目添加less配置
  • CSS 提示工具(Tooltip)
  • JS 面试题总结
  • MySQL几个简单SQL的优化
  • opencv python Meanshift 和 Camshift
  • python3 使用 asyncio 代替线程
  • vue-cli在webpack的配置文件探究
  • 关于for循环的简单归纳
  • 函数式编程与面向对象编程[4]:Scala的类型关联Type Alias
  • 开发基于以太坊智能合约的DApp
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 世界上最简单的无等待算法(getAndIncrement)
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 微信小程序实战练习(仿五洲到家微信版)
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 再次简单明了总结flex布局,一看就懂...
  • 阿里云API、SDK和CLI应用实践方案
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​520就是要宠粉,你的心头书我买单
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #pragma pack(1)
  • $$$$GB2312-80区位编码表$$$$
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (AngularJS)Angular 控制器之间通信初探
  • (LeetCode) T14. Longest Common Prefix
  • (待修改)PyG安装步骤
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (更新)A股上市公司华证ESG评级得分稳健性校验ESG得分年均值中位数(2009-2023年.12)
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (转载)OpenStack Hacker养成指南
  • .chm格式文件如何阅读
  • .FileZilla的使用和主动模式被动模式介绍
  • .gitignore文件---让git自动忽略指定文件
  • .NET BackgroundWorker
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost