[C++] 右值引用

根据我个人的理解,引用应当是是C++为了实现与C中的指针相同功能的一种封装。右值引用,就是为了完整实现原有C中的“指针功能”。
传统的引用往往就是左值引用,比如:

int gen = 1;
int& ref_gen = gen;
const int& conref_gen = gen;

左值引用(int& ),一般用于需要在函数内成员对象,比如std::swap。
常量左值引用(const int &),一般用于传入一个较大的数据的时候,比如一个巨大的结构体,比如在传入的时候的拷贝工作。

但是,右值引用解决的一种具体的情形是这样的,如果一个临时的变量(传完即销毁)传入其中,那么就不需要再拷贝一次内存进行赋值,只需要把指针指向临时变量的地址即可。这种,用指针非常容易实现,但是如果使用引用的方式就非常难,因此右值引用就可以避免一次拷贝的过程,最直接的影响就是可以加快程序的运行速度。

class Info
{
private:
    char* _data;
public:
    Info(const char* str = 0)
    {
        _data = new char[strlen(str) + 1];
        strcpy(_data, str);
    }
    Info(const Info& cur)
    {
        _data = new char[strlen(cur._data) + 1];
        strcpy(_data, cur._data);
    }
    Info(Info&& cur) noexcept
    {
        _data = cur._data;
        cur._data = nullptr;
    }
};
int main()
{
    Timer CurTimer;
    CurTimer.StartTimer();
    for (int i=0;i<100000;i++)
    {
        Info cur("10000000086");
        Info l(cur);
    }
    CurTimer.StopTimer();
    std::cout << "left: " << CurTimer.GetTimerMilliSec() << std::endl;
    CurTimer.StartTimer();
    for (int i = 0; i < 100000; i++)
    {
        Info cur("10000000086");
        Info r(std::move(cur));
    }
    CurTimer.StopTimer();
    std::cout << "right: " << CurTimer.GetTimerMilliSec() << std::endl;
    return 0;
}

通用引用
当右值引用和模板类型和auto搭配的时候,T&&不一定代表右值引用,也可能是一个左值引用。具体的类型跟初始化的内容有关,初始化是左值引用,那么之后的类型就是左值引用。(以下的程序说明,左值引用和原值的地址,而右值引用的内容是暂时存储到了一个新的地址里面。

template<typename T>
void func(T&& foo) 
{
    std::cout << &foo << std::endl;
}

int main()
{
    int value = 10;
    int& ref = value;
    int&& lef = value * 1;
    func(ref);
    func(lef);
    std::cout << &value << std::endl;
}
/*
output:
000000E3425EF664
000000E3425EF6C4
000000E3425EF664
*/

完美转发
如果将一个参数交给另外一个函数处理,那么那么另一个函数会不知道这个是左值还是右值。int && a并不是一个右值,而是一个绑定了右值对象的左值。因此,需要有一个函数使得传递的函数常数的类型。(以下的程序可以很好的说明参数在传递过程的类型变化。

#include <iostream>

template<typename T>
void typeprint(T& _) 
{
    std::cout << "left value" << std::endl;
}

template<typename T>
void typeprint(T&& _) 
{
    std::cout << "right value" << std::endl;
}

template<typename T>
void testForward(T&& v) 
{
    typeprint(v);
    typeprint(std::forward<T>(v));
    typeprint(std::move(v));
}

int main()
{
    testForward(1);
    std::cout << std::endl;
    int x = 1;
    testForward(x);
}

/*
output:
left value
right value
right value

left value
left value
right value
*/

STL中也使用右值引用作为参数来加速,比如emplace_back,根据cppreference可以看到,当然构造函数也是需要经过处理的,就跟给出的例子一样。

std::move 是将原本的左值临时显式的变为右值,之后原本的左值应当应当被销毁。
std::forward 用于完美转发的函数。

[C] 关于goto的想法

goto似乎一直都不推荐使用,但是我在处理动态内存申请时候,感觉不用goto挺难维护代码。比如在一个函数有多个出口的时候,函数内有动态内存申请,那么退出的时候必然要去free,但如果每个return之前都要写free,这样在修改的时候很复杂。

void func()
{
  malloc();
  for	(..)
  {
    if (..)
    {
      free(...);
      return;
    }
    if (..)
    {
      free(...);
      return;
    }
  }
  free(...)
  return;
}

如果使用Goto的话,所有的动态内存释放都放在一个地方管理,而且代码似乎更加简洁了。不过C++就不需要考虑这个问题了。

void func()
{
  malloc();
  for	(..)
  {
    if (..)
    {
      goto CLEAR;
    }
    if (..)
    {
      goto CLEAR;
    }
  }
  
CLEAR:
  free(...);
  return;
}

网上还有一些关于Goto的应用比如:
Ragel State Machine Compiler 有限状态机编译器,可以直接转混成C和C++源码

C里面还有setjmp和longjmp来完成goto的操作。

#include <stdio.h>
#include <setjmp.h>
int main()
{
  jmp_buf env;
  int ret = 0;
  ret = setjmp(env);
  printf("ret=%d\n", ret);
  if (ret == 0)
    longjmp(env, 1);
  if (ret == 1)
    longjmp(env, 2);
  return 0;
}
//	output:
//	ret = 0
//	ret = 1
//	ret = 2

[C++]内存对齐

编译器会对内存自动进行对齐,以加快内存读取。

内存对齐主要遵循下面三个原则:

1.结构体变量的起始地址能够被其最宽的成员大小整除
2.结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
3.结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节

比如struct的字节大小并不是单纯按照所有元素的字节和相加。
可以用系统预编译来进行调节,#pragma pack(1)默认就是紧凑存放的。
linux下默认#pragma pack(4),#pragma pack()恢复缺省对齐

[C++] 关于不同分布的随机数的生成

源于SJ加分题的发红包,想到如何生成制定分布的随机数。

当时就想到红包的值应该是以红包的平均值为峰值的正态的分布。(后来想想应该是偏态分布)这个分布的平均值是红包的平均值。实际上,那个函数的具体实现方法应该是也不是这么简单,我还没找到合适的方法。

前几天看机器学习的时候看到一个Gamma分布(看这个图形的样子的好像是偏态分布。就去学习了一番。

平均分布

过于简单,不再赘述

正态分布

最广为流传的方式应该就是Box-Muller的变换了。U_1, U_2是随机分布的[0~1]内的随机数

    \[U_1=Rand()\quad U_2=Rand()\]

    \[Z=R*\cos(\theta)\]

    \[R=\sqrt{-2*\ln(U_2)}\]

    \[\theta=2*\pi*U_1\]

这样的Z就是符合正态分布的随机数了。

Gamma分布

发现网上也没什么资料,不过C++11提供了很多现成的库来生成不同分布的随机数。这是C++PLUSPLUS的URL

有Gamma分布,Binomial分布,还有指数分布,大概有十多个。

// gamma_distribution
#include <iostream>
#include <random>

int main()
{
  const int nrolls=10000;  // number of experiments
  const int nstars=100;    // maximum number of stars to distribute

  std::default_random_engine generator;
  std::gamma_distribution<double> distribution(2.0,2.0);

  int p[10]={};

  for (int i=0; i<nrolls; ++i) {
    double number = distribution(generator);
    if (number<10) ++p[int(number)];
  }

  std::cout << "gamma_distribution (2.0,2.0):" << std::endl;

  for (int i=0; i<10; ++i) {
    std::cout << i << "-" << (i+1) << ": ";
    std::cout << std::string(p[i]*nstars/nrolls,'*') << std::endl;
  }

  return 0;
}

Possible output:

gamma_distribution (2.0,2.0):
0-1: *********
1-2: *****************
2-3: ******************
3-4: **************
4-5: ************
5-6: *********
6-7: *****
7-8: ****
8-9: ***
9-10: **

UPDATE(2019/7/31):
在知乎上看到的TESTU01,可以用来检验随机数生成的随机性(Random Number Generator, RNG),有官方文档用来证明验证的方式和验证函数的使用。

[C++]杂碎知识

关于Sizeof

sizeof(Gen()),Gen()是一个函数,Gen不会运行。

#include <iostream>
#include <cstring>
#include <iomanip>
using namespace std;

int Gen()
{
  cout << "!!!!" << endl;
  return 0;
}
int main()
{
  cout << sizeof(Gen()) << endl;
  return 0;
}

输出的是Gen()的返回值int的sizeof
据说是sizeof的值在编译的时候就已经处理完毕了

Putchar的返回值

putchar的返回值即是它要输出的值

Perror,Strerror 错误捕捉后的输出字符串

!!Num=1 将数字转换成0,1

atof,atoi 字符串转浮点或整形

memcpy在内存重合的时候可能会出错,用memmove会好一点

 

#include <typeinfo>
typeid(t).name() 得到t的类型名称

Chocolatey Windows包管理系统..但是听说只有一个源,速度很慢。

C中使用Restrict 来解决 Pointer aliasing 的问题