2088 字
10 分钟
C++学习0629
2023-06-29

NULL和nullptr#

void*类型隐式转换,0解决()问题

1

1

1

nullptr可以被转换成任意其它的指针类型;而NULL在CPP中被宏定义为0,在遇到重载时可能会出现问题并且在cpp中不允许转换。

void fun(int a)
{
cout << "int" << endl;
}
void fun(char *a)
{
cout << "char *" << endl;
}
int main()
{
fun(0);
}
// 会调用参数为Int的函数,但是0也表示NULL空指针。

在CPP中NULL的本质就是宏定义0。只有在cpp中才会定义宏__cplusplus。也就是说如果源码是 C++ 程序 NULL 就是 0,如果是 C 程序 NULL 表示 (void*)0。

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

由于 C++ 中,void * 类型无法隐式转换为其他类型的指针,此时使用 0 代替 ((void *)0),用于解决空指针的问题。这个 0(0x0000 0000)表示的就是虚拟地址空间中的 0 地址,这块地址是只读的。

auto#

一个必须,有指针引用const时推导规则,3个限制,使用范围

1

1

1

  • c++11中auto并不代表一种实际的数据类型,它只是一个类型声明的占位符,auto也并不是在所有场景下都能推导出变量的实际类型,使用auto必须要进行初始化,让编译器推导出它的实际类型,在编译阶段将auto占位符替换为真正的类型。
auto temp = 2;
  • auto还可以和指针,引用以及const,在不同的场景下有对应的推导规则。
    • 当变量不是指针或者引用时,推导的结果中不会保留const 关键字
    • 当变量是指针或者引用时,推导结果中会保留const 关键字
int temp = 110;
auto *a = &temp; // int
auto b = &temp; // int*
auto &c = temp; // int
auto d = temp; // int
int tmp = 250;
const auto a1 = tmp; // int
auto a2 = a1; // int 因为没有声明是指针或者引用所以是int
const auto &a3 = tmp; // int
auto &a4 = a3; // const int

auto的限制#

  • auto不能作为函数参数,因为函数参数只有在函数调用的时候才会将实参传入,auto要求必须给修饰的变量赋值。
  • 不能定义数组
  • 不能用于类的非常量静态成员变量的初始化
class A {
public:
auto c = 2; // 报错
static auto a; // 错误的因为静态成员变量需要在类外赋值
const static auto b = 2;
};
  • auto经常应用在STL容器的遍历,其他地方尽量少用不利于代码阅读

增强for循环#

4点

1

1

1

  • 在增强for循环中不需要传递容器需要遍历的范围,循环会自动以容器为范围展开,并且循环屏蔽掉了迭代器的遍历细节,直接抽取容器中的元素进行运算。
  • 如果想要修改遍历的容器,则需要使用引用的方式遍历
  • 如果只读数据,不修改元素的值for(const auto & it : vec) 比非引用的效率高一些
  • 注意遍历哈希表时获取的是对象不是迭代器所以要用 it.first 或者 it.second

lambda#

什么样,2个优点

捕获:6个捕获

返回值:不指定返回值时,一个注意

用途:与()相比lambda表达式的优势,

1

1

1

lambda是C++11非常重要也是最常用的特性之一,他有以下优点:

  • 可以就地匿名定义目标函数或函数对象,不需要额外写一个函数
  • lambda是一个匿名的内联函数

lambda表达式定义了一个匿名函数,语法如下:

[ capture ]{ params } -> ret {body;};

其中capture是捕获列表,params是参数列表,ret是返回值类型,body是函数体。

捕获列表[]:捕获一定范围内的变量

参数列表():和普通函数的参数列表一样,如果没有参数参数列表可以省略不写

auto fun = [](){return 0;};
auto fun = []{return 0;};

捕获列表#

  • []不捕获任何变量
  • [&]捕获外部作用域中的所有变量,并且按照引用捕获
  • [=]捕获外部作用域的所有变量,按照值捕获,拷贝过来的副本在函数体内是只读的
  • [= ,&a]按值捕获外部作用域中的所有变量,并且按照引用捕获外部变量a
  • [bar]按值捕获bar变量,不捕获其他变量
  • [this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限
int main() {
int a = 10, b = 20;
auto f1 = [] {return a; };//错误,没有捕获外部变量,因此无法访问变量a
auto f2 = [&] {return a++; };//正确,使用引用的方式捕获外部变量,可读写
auto f3 = [=] {return a; };//正确,使用值拷贝的方式捕获外部变量,可读
auto f4 = [=] {return a++; };//错误,使用值拷贝的方式捕获外部变量,可读不能写
auto f5 = [a] {return a + b; };//错误,使用拷贝的方式捕获外部变量a,没有捕获外部变量b,因此无法访问变量b
auto f6 = [a, &b] {return a + (b++); };//正确,使用拷贝的方式捕获外部变量a,只读,使用引用的方式捕获外部变量b,可读写
auto f7 = [=, &b] {return a + (b++); };//正确,使用值拷贝的方式捕获所有外部变量以及b的引用,b可读写,其他只读
return 0;
}
class Test {
public:
void output(int x, int y) {
auto x1 = [] {return m_number; };//错误,没有捕获外部变量,不能使用类成员m_number
auto x2 = [=] {return m_number + x + y; };//正确,以值拷贝的方式捕获所有外部变量
auto x3 = [&] {return m_number + x + y; };//正确,以引用的方式捕获所有外部变量
auto x4 = [this] {return m_number; };//正确,捕获this指针,可访问对象内部成员
auto x5 = [this] {return m_number + x + y; };//错误,捕获this指针,可访问类内部成员,没有捕获到变量x和y,因此不能访问
auto x6 = [this, x, y] {return m_number + x + y; };//正确,捕获this指针,x,y
auto x7 = [this] {return m_number++; };//正确,捕获this指针,并且可以修改对象内部变量的值
}
int m_number = 100;
}

返回值#

一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值类型,但是需要注意的是lambda表达式不能通过列表初始化自动推导出返回值类型。

// 可以自动推导出返回值类型
auto f = [](int i) {
return i;
}
// 不能推导出返回值类型
auto f1 = []() {
return { 1, 2 }; // 基于列表初始化推导返回值,错误
}
// 正确显示声明了函数的返回值类型
auto f1 = []()->vector<int> {
return { 1, 2 }; // 基于列表初始化推导返回值,错误
};

用法#

与STL搭配使用:

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>
int main() {
vector<int> vec = {1,2,3,4,5,6};
sort(vec.begin(), vec.end(), [](int a, int b) {
return a > b;
});
for (auto it : vec) {
cout << it << " ";
}
}
#include <vector>
#include <algorithm>
using namespace std;
#include <iostream>
vector<int> nums;
vector<int> largeNums;
const int ubound = 10;
inline void LargeNumsFunc(int i) {
if (i > ubound)
largeNums.push_back(i);
}
void Above() {
// 传统的for循环
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
if (*itr >= ubound)
largeNums.push_back(*itr);
}
// 使用函数指针
for_each(nums.begin(), nums.end(), LargeNumsFunc);
// 使用lambda和算法for_each
for_each(nums.begin(), nums.end(), [=](int i) {
if (i > ubound)
largeNums.push_back(i);
});
}

那么我们再比较一下函数指针方式以及lambda方式。函数指针的方式看似简洁,不过却有很大的缺陷。

第一点是函数定义在别的地方,比如很多行以前(后)或者别的文件中,这样的代码阅读起来并不方便。

第二点则是出于效率考虑,使用函数指针很可能导致编译器不对其进行inline优化( inline对编译器而言并非强制),在循环次数较多的时候,内联的 lambda和没有能够内联的函数指针可能存在着巨大的性能差别。因此,相比于函数指针,lambda拥有无可替代的优势。

C++学习0629
https://fuwari.cbba.top/posts/c学习0629/
作者
Chen_Feng
发布于
2023-06-29
许可协议
CC BY-NC-SA 4.0