其他运算符

位逻辑运算符

我们能够通过位逻辑运算符从字中抽取位域。下面归纳两种常见的用法:
如果某个函数达到了输入的末尾,可以用下面形式报告状态:

<!--more-->

state=goodbit;
//.....
if(state&(badbit|failbit))  //流的状态不好

如果某个函数到达了输入的末尾,则它可以报告如下内容:

state|=eofbit;

|=能够在现有的状态上添加内容。

constexpr unsigned short middle(int s)
{
  static_assert(sizeof(int)==4, "unexpected int size");
  static_assert(sizeof(short)==2, "unexpected short size")
  return (a>>8)&0xFFFF;
}
int x=0xFF00FF00;  //假定sizeof(int)==4
short y=middle(x);

//x>>8,注意0xFFFF,一个字符占4位,x>>8=0xFF00FF
//(X>>8)&0xFFFF,只取(x>>8)的最低16位,即y=0x00FF

还有一种比较常见的使用方法:

//就是将一个32位的数的高16位加上低16位
sum = (sum >> 16) + (sum & 0xffff)
//再将结果的高16位加上自身
sum += (sum >> 16)

再看c++11中的一些特性:

自由存储

在C++11中引入了这种新的特性,sp是一个序列,s是一个用于访问sp中基本元素的变量,每次迭代都会用sp中的下一个元素来初始化s

void f(const string &s)
{
  vector<char> v;
  for(auto c:s)
    v.push_back(c);
  //push_back负责执行new和delete的操作
}

注意使用智能指针

void f(int n)
{
  int *p1=new int[n];  //不建议,使用裸new
  unique_ptr<int []> p2{new int [n]};

  if(n%2) throw runtime_error("odd");
}

new常见的用法和错误
允许使用new的情况:

void f(int n)
{
  vector<int>* p=new vector<int>(n);  //单个对象
  int* q=new int[n];
  //...
  delete p;
  delete[] q;
}

不要用new创建局部对象

void f1()
{
  X* p=new X;
  delete p;
}

而应该使用局部变量来解决这一问题

void f2()
{
  X x;
}

重载new

核心思想:将对象置于别处:

void* operator new(size_t,void* p){return p;}

void* buf=reinterpret_cast<void*>(0xF00F) //一个明确的地址
X* p2=new(buf) X;

在<new>的头文件中,operator new()是最简单的一个:

void* operator new(size_t sz, void* p) noexcept;
//将大小为sz的对象放置到p处
void* operator new[](size_t sz, void* p) noexcept;

//删除:
void operator delete(void* p, void*) noexcept;
//如果p!=null,令*p无效
void operator delete[](void* p, void*) noexcept;

放置式new还能够用于从某个特定区域里分配内存:

class Arena
{
  public:
    virtual void* alloc(size_t)=0;
    //在类的成员函数中,能够执行分配、释放内存
    virtual void free(void*)=0;
    //....
};

void* operator new(size_t sz, Arena* a)
{
  return a->alloc(sz);
}

//能在不同的Arena中分配任意类型的对象了
extern Arena* Persistent;
extern Arena* Shared;

void g(int i)
{
  X* p=new(Persistent) X(i);
  X* q=new(Shared) X(i);
  //.....
}

//销毁此类对象的时候要特别小心:
void destroy(X* p, Arena* a)
{
  p->~X();  //调用析构函数
  a->free(p); //释放内存

  //这里,X是一个类对象,p指向该对象,调用该对象的析构函数
  //p是在Arena上存放的内存空间,通过Arena的成员free释放内存
}

c++11特性之initializer_list

std::vector v = { 1, 2, 3, 4 };

vector容器中的构造过程如下:

const double temp[]={double{1},double{2},3.14};
//使用列表初始化一个临时数组
const initializer_list<double> tmp(temp,sizeof(temp)/sizeof(double));
vector<double> v(tmp);
void f()
{
  initializer_list<int> lst{1,2,3};
  cout<<*lst.begin()<<'\n';
  //lst不可修改,*lst.begin()=2会发生错误!
}

限定列表

T x {v};

struct S {int a,b;};

void f()
{
  S v {7,8};  //直接初始化一个变量
  v=S{7,8};  //用限定列表进行赋值
  S* p=new S{7,8};  //这里使用new,是在自由存储上构建对象
}

标准库类型initializer_list<T>用于处理可变长度的{}列表,常把它用于自定义容器的初始化列表。

int high_value(initializer_list<int> val)
{
  int high=numeric_traits<int>lowest();
  /*当函数,类或者一些封装的通用算法中的某些部分会因为数据类型不同而导致处理或逻辑不同(而我们又不希望因为数据类型的差异而修改算法本身的封装时),traits会是一种很好的解决方案。*/
  if(val.size()==0)
    return high;

  for(auto x:val)
    if(x>high)
      high=x;

  return high;
}

int v1=high_value({1,2,3,4,5,6,7});
int v2=high_value({-1,2,v1});

lambda表达式

理解lambda表达式的语义,有几个简单的例子:

void print_modulo(const vector <int>& v,ostream& os, int m)
{
  for_each(begin(v),end(v),[&os,m](int x){if(x%m==0) os<<x<<'\n';})
}

//等价于
class Modulo_print
{
  ostream& os;
  int m;

public:
  Modulo_print(ostream& s,int mm):os(s),m(mm){}  //捕获
  void operator()(int x) const{if(x%m==0) os<<x<<'\n';}
};

lambda表达式能够简化局部类,与此同时,可以泛化print_modulo(),令其可以处理更多的容器类型。

template<class C>
void print_modulo(const C& v, ostream &os, int m)
{
  for(auto x:v)
    if(x%m==0)
      os<<x<<'\n';
}

//进行bfs
template<class C>
void print_modulo(const C& v, ostream& os, int m)
{
  breadth_first(begin(v),end(v),[&os,m](int x){if(x%m==0) os<<x<<'\n';});
}

捕获,使用[]定义,无须访问局部环境

void algo(vector<int>& v)
{
  bool sensitive = true;
  //以下代码错误,无权访问sensitive
  /*sort(v.begin(),v.end(),[](int x,int y){return sensitive? x<y:abs(x)<abs(y);}); */

  sort(v.begin(),v.end(),[sensitive](int x,int y){return sensitive? x<y:abs(x)<abs(y);});
}

lambda与生命周期

注:lambda传递给另一个线程的时候,很有可能生命周期比调用者更长:

void setup(Menu& m)
{
  //...
  Point p1,p2,p3;
  //...计算p1,p2,p3的位置
  //m.add("draw triangle",[&]{ m.draw(p1,p2,p3); });
  //按引用传递的时候,并不会存下它的副本,很有可能setup()完成之后,用户才点击
  //draw triangle按钮,此时lambda会访问一个不存在的局部变量
  //解决方法:使用[=]将对象拷贝到闭包中,通过return机制返回
  m.add("draw triangle",[=]{ m.draw(p1,p2,p3); });
}

lambda用在成员函数中

class Request
{
  function<map<string,string>(const map<string,string>&)> oper;   //操作,function<map<string,string>(//map容器的参数)> oper
  map<string,string> values;
  map<string,string> results;

public:
  Request(const string& s);

  void execute()
  {
    [this](){ results=oper(values); }   //通过this捕获成员values
    //results
  }
}

标准库<map>是关联数组,主要的用法如下:

map<string,int> phone_book
{
  {"David Hume",123456},
  {"Karl Popper",234567},
  {"Bertrand Arthur William Russell",345678}
};

lambda的调用与返回

有多个return的时候,必须显式提供一个返回类型

void g(double y)
{
  auto z4 = [=,y]()->int( if(y) return 1; else return 2;)
  //显式指定返回类型
}

lambda的类型

任意两个lambda的类型都不相同。一旦两个lambda具有相同的类型,模板实例化机制就无法辨识它们了。lambda是一种局部类类型,它含有一个构造函数以及一个const成员函数operator()()

//错误的代码:
auto rev=[&rev](char* a, char* b){ if (1<e-b) {swap(*b, *--e); rev(++b,e);} };

//递归使用的两个lambda表达式都是auto类型,模板实例化机制无法辨识它们了。
//[&rev]这里捕获了rev,但是,我们无法在推断出一个auto变量的类型之前
//使用并且捕获rev

正确的做法是:引入一个新的名字,然后使用它。

void f(string& s1, string& s2)
{
  function<void(char* b,char* e)> rev=[&](char* a,char* b){ if(1<e-b) { swap(*b,*--e); rev(++b,e);}};  //表示++b,e重新作为参数传递,这里是进行递归使用
  rev(&s1[0],&s1[0]+s1.size());
  rev(&s2[0],&s2[0]+s2.size());
}

如果只想给lambda起一个名字而不是递归使用,可以用auto

void g(vector<string>& vs1, vector<string>& vs2)
{
  auto rev=[&](char* b, char* e){while(1<e-b) swap(*b++,*--e);};

  rev(&s1[0],&s1[0]+s1.size());
  rev(&s2[0],&s2[0]+s2.size());
}

lambda什么也不捕获,可以让它指向一个正确类型的函数的指针

double (*p1)(double)=[](double){return sqrt(a);};
//错误代码:double (*p3)(int)=[](int a){return sqrt(a); };
//返回类型不匹配

实现显式类型转换

使用一个自制的显式转换函数narrow_cast,比较安全

template<class Target, class Source>
Target narrow_cast(Source v)
{
  auto r=static_cast<Target>(v);
  if(static_cast<Source>(r)!=v)
    throw runtime_error("narrow_cast<>() failed");

  return r;
}

构造

auto d1 = double{2};
double d2 {double{2}/4}
//T{e}执行行为良好的类型转换

static_assert(sizeof(int)==sizeof(double),"unexpected sizes");

int x=numeric_limits<int>::max();  //可能的最大整数值
double d=x;
int y=x;  //该程序并不会得到x==y的结果

fogsail
31 声望6 粉丝

一直认为数学、音乐、诗歌都是世间最美好的语言,程序是用数学这种语言写成的逻辑。于2014年12月被人拉到兰州大学开源社区中去(mirror.lzu.edu.cn),从此与自由软件和编程解下不解之缘,毅然抛弃生物专业从事编...


« 上一篇
约瑟夫环问题
下一篇 »
动态规划概论

引用和评论

0 条评论