frankfan的胡思乱想

学海无涯,回头是岸

参数与返回值以及新特性等

引用当函数的参数与返回值 智能指针再论 C11的一些有用新特性

关于引用当函数的参数与返回值

引用本质就是指针,只不过是将指针削弱了而已。比如不能进行引用的加法运算以及定义时必须初始化等。

因此引用作为函数参数与指针作为函数参数并没有什么不同,只是C++只在定义引用时使用了特殊的&符号,右值与普通赋值一致,优化了函数调用时的写法。

#include <iostream>
using namespace std;
void swap(int& a,int& b){
  int tmp = a;
  a = b;
  b = tmp;
}
int main(){
  int a = 11,b = 22;
  swap(a,b);//并不需要对a、b取地址
  return 0;
}

而当函数返回引用时,则需要注意。

当返回堆对象的引用或地址时,是合法且安全的,而当返回对象自身的引用时(*this)也是合法且安全的。但是当返回局部变量(对象)的引用时,则是语法可行但不安全的。这也通常是在编码中所不允许的。

因为返回局部变量的引用与返回局部变量的地址本质上并没有区别,但是局部变量的栈并不稳定,随时有被覆写的可能。

(返回对象自身的引用多发生在运算符重载的场景中,返回对象自身的引用好处在于既能避免一次拷贝发生又能传递对象自身)

智能指针

智能指针本质是使用了组合的设计模式,用引用计数的机制来标记堆对象被持有的次数,当0次持有时,堆对象被delete;
那么,什么时候检测堆对象被持有的次数(引用计数是多少)呢,时机就在每次宿主对象被析构时。若此时检测到引用计数为0,则delete被组合的对象;
那么,宿主对象什么时候被析构,为什么宿主对象会被析构呢?
因为宿主对象不能被定义在堆上,言外之意宿主对象不能使用new创建对象,这样宿主对象就只能被定义在栈中,每个栈中的对象在出作用域后,都会被编译器释放,这就是宿主对象能起作用的精髓所在,也是智能指针的本质所在。

除此外,宿主类还需要能正确处理寄宿对象是对象以及对象数组的情况,还需要重载相关的预算符。


C11的一些新关键词和特性

以下是一些C11新的有用或常用的关键字或者特性

final

1、用于类名,阻止本类被继承

2、用于函数,阻止此函数被覆盖

class B {
 virtual void f() const final;	// do not override
 virtual void g();
};

struct D : public B {
 void f() const; // error: D::f attempts to override final B::f
 void g();	// OK
};

int main()
{
    D d; //报错,这里不能声明,因为不允许f重写
    return 0;
}

=default =delete

=default: 告诉编译器我已经有默认(无参构造,拷贝构造,析构,=运算符),不需要为我生成。 =delete: 告诉编译器,我有些函数已经废弃不用了,不允许任何人(类内和类外)使用。

class CTest {
public:
 // ...
 CTest& operator=(const CTest&) = delete;	// Disallow copying
 CTest(const CTest&) = default;

 CTest() {

 }
};

int main()
{
 CTest t;
 CTest t2;
 t = t2;

    return 0;
}

Raw String Literals

原生字符串,即字符串中无转义,所有的字符都是其本身的意思

printf("(hello\\\n)\n");
printf(R"(hello\\\n)");

initializer List

可以认为是字面量语法

vector<int> vi = { 1,2,3,4,5,6,7,8,9 };
map<int, string> m = {
        {1, "hello"}, {2, "world"}, {3, "!"}
    };
m.insert({4, "haha"});

decltype

仅得到类型,而不产生变量关系.

decltype(expr)所推导出来的类型,完全与expr类型一致。在编译期间完成,并不会真正计算表达式的值。 可以与typedef使用

 char* sz = "hello";
 decltype(sz) sz2 = "hello 2";

functor 仿函数

重载了 operator()的类的对象,在使用中,语法类型于函数。故称其为仿函数。相对于函数,仿函数,可以拥用初始状态,一般通过 class 定义私有成员,并在声明对象的时候,进行初始化。私有成员的状态,就成了仿函数的初始状态

#include <iostream>
using namespace std;
class Tax
{
public:
    Tax(float r, float b):_rate(r),_base(b){}
    float operator()(float money)
    {
       return (money-_base)*_rate;
    }
    private:
    float _rate;
    float _base;
};

int main(int argc, char *argv[])
{
    Tax high(0.40,30000);
    Tax middle(0.25,20000);
    Tax low(0.12,10000);
    cout<<"大于 3w 的税:"<<high(37500)<<endl;
    cout<<"大于 2w 的税:"<<middle(27500)<<endl;
    return 0;
}

Lambda

这个是个特殊的表达式,这种表达式是一种代码上下文环境,这个环境允许被当做函数参数与函数返回值进行传递。

[capture](paras)mutable->return type{statement}

auto a = [](int a){
  cout<<a<<endl;
};
//这是一个表达式,而非函数,因此需要分号结尾。
a(11);//调用这个表达式

单纯的将表达式赋值给另一个变量并非其常规用法(这样用意义也不大)

而是,它可以当做一个变量在函数中传递或者当返回值传出。

比函数指针更加方便。

此外还能捕获外界的变量供内部使用

[] 闭包 lambda函数能够捕获lambda函数外的具有自动存储时期的变量。函数与这些变量的集合合起来叫闭包。闭包的概念在lambda中通过[]来体现出来。 [] 不截取任何变量。 [bar] 仅对外部变量 bar 值传递在函数体中使用。 [&bar] 仅对外部变量 bar 引用传递在函数体中使用。 [x, &y] 仅 x 按值传递,y 按引用传递在函数体中使用。。 [&] 截取外部作用域中所有变量,并作为引用传递在函数体中使用。 [=] 截取外部作用域中所有变量,并按值传递在函数体中使用。 [=, &foo] 截取外部作用域中所有变量,并值传递在函数体中使用,但是foo变量使用引用传递。 [&, =foo] 截取外部作用域中所有变量,在函数体中作引用传递使用,但是对foo 变量作值传递。

闭包既可以作为函数的参数又能作为函数的返回值

#include <iostream>
using namespace std;
auto clos(){
  int a = 11;
  auto c = [=](){
    cout<<a<<endl;
  }
  return a;
}

int main(){
  
  auto r = clos();
  cout<<"begin"<<endl;
  r();//11 闭包内部捕获了局部变量a的值,即使函数调用后栈被回收,被函数返回的闭包内部依然捕获了局部变量的值没有释放
  //具体捕获变量的值还是变量的引用,或者捕获那个外部变量的值,通过[]中的符号决定
  return 0;
}

posted on 2021-12-28 00:13  shadow_fan  阅读(25)  评论(0编辑  收藏  举报

导航