吴尼玛

吴尼玛 查看完整档案

武汉编辑武汉理工大学  |  电子与通信工程 编辑优品财富  |  Cplusplus开发 编辑填写个人主网站
编辑

学技术简单记,吴尼玛带你记笔记。

个人动态

吴尼玛 发布了文章 · 1月15日

c++关键字typeid

  • typeid是c++的一个关键字,typeid操作符的返回结果是标准库类型type_info对象的引用。
  • 但是,C++标准并没有明确定义type_info,其具体实现依赖于各个编译器。标准只规定了typeid操作符必需实现如下四种操作:
操作说明
t1 == t2如果两个对象t1和t2类型相同,则返回true;否则返回false
t1 != t2如果两个对象t1和t2类型不同,则返回true;否则返回false
t.name()返回类型的C-style字符串。由编译器决定,不一定就是真实的类型名
t1.before(t2)判断t1是否位于t2的前面。类型排列顺序与编译器相关,基类不一定位于派生类的前面。
  • type_info的成员函数name返回类型的C-style字符串,但这个返回的类型名与程序中使用的相应类型名不一定一致,其返回值的实现由编译器决定,标准只要求每个类型返回的字符串是唯一的。
  • 和sizeof操作符类似,typeid的操作对象既可以是数据类型,也可以是表达式。
  • 使用typeid判断各种类型示例代码如下:
#include<iostream>  
#include <typeinfo>  
using namespace std;  

class Base{};
class Derived:public Base{};
void func1();
int func2(int n);

int main()  
{  
    int a = 10;
    int* b = &a;
    float c;
    double d;

    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    cout << typeid(Base).name() << endl;
    cout << typeid(Derived).name() << endl;
    cout << typeid(func1).name() << endl;
    cout << typeid(func2).name() << endl;
}  
  • Mac下使用clang++编译运行结果如下:
i
Pi
f
d
4Base
7Derived
FvvE
FiiE
  • 不像Java、C#等动态语言,C++运行时能获取到的类型信息非常有限,标准也定义的很模糊,如同“鸡肋”一般。在实际工作中,我们一般只使用type_info的“==”运算符来判断两个类型是否相同。
  • 再来看看下面的示例代码:
#include<iostream>  
#include <typeinfo>  
using namespace std; 

class Base{};
class Drived: public Base{};

int main()
{
    Base* pb;
    Drived d;
    pb = &d;

    if(strcmp(typeid(*pb).name(), typeid(Base).name()) == 0)
    {
        cout << "this is Base" << endl;
    }
    else if(strcmp(typeid(*pb).name(), typeid(Drived).name()) == 0)
    {
        cout << "this is Drived" << endl;
    }
    
    if(strcmp(typeid(d).name(), typeid(Base).name()) == 0)
    {
        cout << "this is Base" << endl;
    }
    else if(strcmp(typeid(d).name(), typeid(Drived).name()) == 0)
    {
        cout << "this is Drived" << endl;
    }
}
  • Mac下使用clang++编译运行结果如下:
this is Base
this is Drived
  • 从运行结果中可以看出,即使用基类指针指向派生类,但使用typeid判断的基类指针类型依然是基类指针。因此我们不能用typeid来判断基类指针实际指向的是否是某个派生类。
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 1月10日

一文读懂C++内存对齐

操作系统64位和32位有什么区别?

  • 64位操作系统意味着其cpu拥有更大的寻址能力。理论上来说,其性能相比于32位操作系统会提升1倍。但是这也需要在64位操作系统上运行的软件也是64位的。
  • 软件中数据类型的的字节数大小其实和操作系统是多少位的没有关系,而是由编译器决定的。也就是说数据结构占多少位取决于在软件编译时我们选择的是64位还是32位的编译器。其具体占位数在编译器已经决定了。

数据类型对应字节数

  • 下面是不同位数编译器下基本数据类型对应的字节数。

32位编译器:

char :1个字节
char*(即指针变量): 4个字节
short int : 2个字节
int:  4个字节
unsigned int : 4个字节
float:  4个字节
double:   8个字节
long:   4个字节
long long:  8个字节
unsigned long:  4个字节

64位编译器:

char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int:  4个字节
unsigned int : 4个字节
float:  4个字节
double:   8个字节
long:   8个字节
long long:  8个字节
unsigned long:  8个字节
  • 总结:32位和64位编译器的基本数据类型字节数主要差别在64位的指针和long为8字节。

C++内存对齐

  • 众所周知,为了保证每个对象拥有彼此独立的内存地址,C++空类的内存大小为1字节。而非空类的大小与类中非静态成员变量和虚函数表的多少有关。其中,类中非静态成员变量的大小则与编译器的位数以及内存对齐的设置有关。
  • 类中的成员变量在内存中并不一定是连续的。它是按照编译器的设置,按照内存块来存储的,这个内存块大小的取值,就是内存对齐。
  • 内存对齐有2个规则:

    • 第一个成员变量放在类中内存offset为0的地方,之后的成员变量的对齐按照#pragma pack(n)指定的数值和这个成员变量类型所占字节数中,比较小的那个进行(成员变量间补齐)。
    • 在成员变量完成各自内存对齐之后,类(结构或联合)本身也要进行内存对齐,对齐按照#pragma pack(n)指定的数值和类中最大成员变量类型所占字节数中,比较小的那个进行(类中最后一个成员变量结尾后补齐),类大小需要是对齐值得整数倍。
  • \#pragma pack(n)作为一个预编译指令用来设置内存对齐的字节数。需要注意的是,n的缺省数值是编译器设置的,一般为8,合法的数值分别是1、2、4、8、16。

延伸知识:C++空类大小

  • C++标准指出,不允许一个对象(当然包括类对象)的大小为0,不同的对象不能具有相同的地址。这是由于:

    • new需要分配不同的内存地址,不能分配内存大小为0的空间
    • 避免除以sizeof(T)时得到除以0错误故使用一个字节来区分空类。
  • 需要注意的是,这并不代表一个空基类也需要加一个字节到子类中去。这种情况下,空基类并不是独立的,它附属于子类。子类继承空基类后,子类如果有自己的数据成员,则空基类的那一个字节并不会加到子类中去。
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-12-25

《More Effective C++》总结笔记(一)

基础议题

条款1:仔细区别pointers和references

  • 首先你必须认知一点,没有所谓的 null reference。
  • 由于reference一定得代表某个对象,C++因此要求references必须有初值。
  • Pointers和references之间的另一个重要差异就是,pointers可以被重新赋值,指向另一个对象,reference却总是指向(代表)它最初获得的那个对象。
  • 结论:当你知道你需要指向某个东西,而且绝不会改变指向其他东西,或是当你实现一个操作符而其语法需求无法由pointers达成,你就应该选择references。任何其他时候,请采用pointers。

条款2:最好使用C++转型操作符

  • C++提供的4种新的转型操作符,相比于C风格的转型,尽管其语法看起来又臭又长。但它提供了严谨的意义以及更强的辨识度。
  • 如果你在程序中使用新式转型法,比较容易被解析(不论是对人类还是对工具而言),编译器也因此得以诊断转型错误(那是旧式转型法侦测不到的)。
  • 让转型动作既丑陋又不易键入(typing),或许未尝不是件好事。

条款3:绝对不要以多态方式处理数组

  • 因为基类和派生类大小不一样,函数传参时如果是形参是基类数组,实参是派生类的数组,这时在函数内循环遍历基类数组的结果就是不可预期的。
  • 简单地说,多态和指针算术不能混用。数组对象几乎总是会设计指针的算术运算,所以数组和多态不要混用。

条款4:非必要不提供default constructor

class EquipmentPiece {
public:
  EquipmentPiece(int IDNumber = UNSPECIFIED);
  ...
private:
  static const int UNSPECIFIED;  //一个某数数字,意味没有被指定ID值。
};
  • 这就是一个添加了default constructor的class,这样我们就可以直接通过

EquipmentPiece e;
这样的语句来产生一个EquipmentPiece的object。虽然方便了使用,但是这几乎总是会造成class内的其他member functions变得复杂。

  • 添加无意义的default constructor,会影响classes的效率。如果member functions必须测试字段(指的是default constructor被赋了默认值的必备字段)是否真的被初始化了,其调用者便必须为测试行为付出时间代价,并为测试代码付出空间代价,因为可执行文件和程序库都变大了。万一测试结果为否定,对应的处理又需要一些空间代价。如果class constructors可以确保对象的所有字段都被正确地初始化,上述所有成本便都可以免除。如果default constructor无法提供这种保证,那么最好避免让default constructor出现。虽然这可能会对classes的使用方式带来某种限制,但同时也带来一些保证:当你真的使用了这样的classes,你可以预期它们所产生的对象会被完全地初始化,实际上亦富有效率。

操作符

条款5:对定制的“类型转换函数”保持警觉

  • 单自变量constructors和隐式类型转换操作符可以让自定义类型实参隐式转换。
  • 重载类型转换操作符可能会导致错误(非预期)的函数被调用。解决办法就是以功能对等的另一个函数取代重载的类型转换操作符(参考std::string的c_str)。
  • 将constructors声明为explicit,使得编译器不能因隐式转换的需要而调用它们,只能显示的调用。通过这种方式可以消除单自变量constructors引起的隐式转换。

条款6:区别increment/decrement操作符的前置和后置形式

  • 重载increment前置式和后置式操作符的范式(decrement同理):
// 前置式:累加然后取出(increment and fetch)
UPInt& UPInt::operator++()
{
*this +=1;
return *this;
}
// 后置式:取出然后累加(fetch and increment)
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this;
++(*this);
return oldValue;
}
  • 为了使类似i++++这样的调用不合法,所以重载后置式increment操作符的返回值必须是const的。
  • 后置式increment和decrement操作符的实现应以其前置式兄弟为基础。如此一来你就只需维护前置式版本。
  • 因后置式increment(decrement)函数会返回一个临时对象,所以它天生效率没有其前置式兄弟高(主要对自定义类型)。

条款7:千万不要重载&&,||和,操作符

  • C++对于“真假值表达式”采用所谓的“骤死式”评估方式。意思是一旦该表达式的真假值确定,即使表达式中还有部分尚未检验,整个评估工作仍告结束。
  • 而如果重载了&&或||操作符则他们变成了函数调用,函数调用则没办法实现类似的“骤死式”语法,因为函数调用必须所有参数都评估完成。而且也无法保证表达式的评估顺序,因为C++语言规范并未明确定义函数调用动作中各参数的评估顺序。
  • 逗号操作符也有类似问题。表达式如果内含逗号,那么逗号左侧会先被评估,然后逗号的右侧再被评估;最后,整个逗号表达式的结果以逗号右侧的值为代表。而如果重载它,无法模仿这些行为。

条款8:了解各种不同意义的new和delete

  • 如果你希望将对象产生于heap,请使用new operator。它不但分配内存而且为该对象调用一个constructor。如果你只是打算分配内存,请调用operator new,那就没有任何constructor会被调用。如果你打算在heap objects产生时自己觉得内存分配方式,请写一个自己的operator new,并使用new operator,它将会自动调用你所写的operator new。如果你打算在已分配(并拥有指针)的内存中构造对象,请使用placement new。
  • 总结关系如下:
new <->delete
operator new <-> operator delete //分配、释放内存
placement new <-> placement delete //在指定内存上构造、析构对象
new[] <-> delete[]
  • operator new调用示范:
void *rawMemroy = operator new(sizeof(string));
  • plcement new示范:
class Widget {
public:
  Widget(int widgetSize);
  ...
};
Widget* constructWidgetInBuffer(void* buffer, int widgetSize)
{
  return new (buffer) Widget(widgetSize);
}
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-12-23

C++11拾穗

C++11新关键字

alignas:指定对齐大小

alignof:获取对齐大小

decltype

auto(重新定义):可作为返回值类型后置时的占位符

static_assert:静态断言

using(重新定义):类型别名或者模板别名

noexcept:声明函数不可以抛出任何异常

export(弃用,不过未来可能留作他用)

nullptr

constexpr:可在在编译期确认的常量表达式

thread_local:等价于TLS

快速初始化成员变量

C++11中支持使用等号 = 或者花括号 {} 进行就地的(也就是在声明的时候)非静态成员变量初始化。例如:

struct init{
    int a = 1;
    double b {1.2};
}

在C++11标准支持了就地初始化非静态成员的同时,初始化列表这个手段也被保留下来了。只不过初始化列表总是看起来“后作用于”非静态成员。

final/override控制

C++11提供关键字final,作用是使派生类不可覆盖它所修饰的虚函数。final关键字也可 用于基类中,但是这样定义的虚函数就没有意义了。final通常就是在继承关系的“中途”终止派生类的重载。

在C++中对于基类声明为virtual的函数,之后的重载版本都不需要再声明该重载函数为virtual。即使在派生类中声明了virtual,该关键字也是编译器可以忽略的。另外,有的虚函数会“跨层”,没有在父类中声明的接口有可能是祖先的虚函数接口。所以C\++11引入了虚函数描述符override,来帮助程序员写继承结构复杂的类型。如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。

继承构造函数

C++11提供了继承构造函数,在派生类中使用using声明,就可以把基类中的构造函数继承到派生类中。 但其实质是编译器自动生成代码,通过调用父类构造函数来实现,不是真正意义上的“继承”,仅仅是为了减少代码书写量。

class A {
    A(int i) {}
    A(double d, int i) {}
    A(char *c , double d, int i) {}
    //... 更多构造函数
};

class B : A {
    using A::A; //继承构造函数
    //...
}

委派构造函数

C++11提供委派构造函数,可以简化多构造函数的类的编写。如果我们能将一个构造函数设定为“基准版本”,则其他构造函数可以通过委派“基准版本”来进行初始化。我们将这个“基准版本”称为目标构造函数。

class Info {
public:
    Info() { InitRest(); }//目标构造函数
    Info(int i) : Info() { type = i; }//委派构造函数
    Info(char c) : Info() { name = c; }//委派构造函数
    
private:
    void InitRest(); { /* 其他初始化 */}
    int type {1};
    char name {'a'};
    //...
}

注意:委派构造函数不能有初始化列表。如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。

在使用委派构造函数时,建议程序员抽象出最为“通用”的行为作为目标构造函数。

移动语义

拷贝构造与移动构造
在C++11中,这样的“偷走”临时变量中资源的构造函数,被称为移动构造函数。而这样的“偷”的行为,则称之为“移动语义”。

class HasPtrMem(){
public:
    HasPtrMem() : d(new int(3)) {}
    HasPtrMem(const HasPtrMem & h) : d(new int(*h.d)) {}// 拷贝构造函数
    HasPtrMem(HasPtrMem && h) : d(h.d) {h.d = nullptr;}// 移动构造函数,需将临时值的指针成员置空
    ~HasPtrMem() {delete d;}
private:
    int *d;
}

左值,右值,右值引用

关于左值,右值很难去做一个非常明确的定义,但是在C++中有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。

无论是声明一个左值引用还是右值引用,都必须立即进行初始化。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。

标准库在<type_traits>头文件中提供了3个模板类:is_rvalue_reference、is_lvalue_reference、is_reference,可供我们进行判断引用类型是左值引用还是右值引用。

std::move:强制转化为右值

C++11中提供了std::move这个函数使我们能将一个左值强制转化为右值引用,继而我们可以通过右值引用使用该值,以用于移动语义。

// 使用上面例子中的HasPtrMem
HasPtrMem a;
HasPtrMem b(std::move(a));// 调用移动构造函数

事实上,为了保证移动语义的传递,程序员在编写移动构造函数的时候,应该总是记得使用std::move转换拥有形如堆内存、文件句柄等资源的成员为右值,这样依赖,如果成语支持移动构造的话,就可以实现其移动语义。而即使成员没有移动构造函数,那么接受常量左值的构造函数版本也会轻松地实现拷贝构造,因此也不会引起大的问题。

显示转换操作符

在C++中,有一个非常好也非常坏的特性,就是隐式类型转换。隐式类型转换的“自动性”可以让程序员免于层层构造类型。但也是由于它的自动性,会在一些程序员意想不到的地方出现严重的但不易被发现的错误。

关键字explicit主要用于修饰的构造函数,其作用主要就是阻止构造函数的隐式转换。

C\++11中将explicit的使用范围扩展到了自定义的类型转换操作符上,以支持所谓的“显示类型转换”。

列表初始化

在C\++11中可以使用花括号“{}”来进行初始化,这种初始化方式被称为列表初始化,已经称为C++语言的一个基本功能。我们甚至可以使用列表初始化的方式对vector、map等非内置的复杂的数据类型进行初始化。

而且,C++11中,标准总是倾向于使用更为通用的方式来支持新的特性。标准模板库中容器对初始化列表的支持源自<initilizer_list>这个头文件中的initilizer_list类模板的支持。我们可以通过声明已initilizer_list<T>模板类为参数的构造函数,来使得自定义的类使用列表初始化。

enum Gender {boy, girl};
class People{
public:
    People(initilizer_list<pair<string, Gender>> l) {//initilizer_list的构造函数
        auto i = l.begin();
        for (; i != l.end(); ++i)
            data.pushback(*i);
    }
private:
    vector<pair<string, Gender>> data;
};

People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};

另外,使用列表初始化可以有效的防止类型收窄。类型收窄一般是指一些可以使得数据变化或者精度丢失的隐式类型转换。

auto类型推导

auto声明的变量必须被初始化,以使编译器能够从其初始化表达式中推导出其类型。

auto使用时需注意:

(1)、可以使用const、volatile、pointer(*)、reference(&)、rvalue reference(&&)等说明符和声明符来修饰auto关键字;

(2)、用auto声明的变量必须初始化;

(3)、auto不能与其它任何类型说明符一起使用;

(4)、方法、参数或模板参数不能被声明为auto;

(5)、定义在堆上的变量,使用了auto的表达式必须被初始化;

(6)、auto是一个占位符,不是类型,不能用于类型转换或其它一些操作,如sizeof、typeid;

(7)、auto关键字内声明的声明符列表的所有符号必须解析为同一类型;

(8)、auto不能自动推导成CV-qualifiers(constant& volatile qualifiers),除非被声明为引用类型;

(9)、auto会退化成指向数组的指针,除非被声明为引用;

(10)、auto不能作为函数的返回型,在C++14中是可以的。

decltype

decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型,decltype总是以一个普通的表达式为参数,返回该表达式的类型。而与auto相同的是,作为一个类型指示符,decltype可以将获得的类型来定义另外一个变量。与auto相同,decltype类型推导也是在编译时进行的。

decltype只能接受表达式做参数,像函数名做参数的表达式decltype(hash)这种是无法通过编译的。

追踪返回类型

利用auto和decltype以及返回类型后置的语法就能实现追踪返回类型。比如,我们想写一个泛型的加法函数时可能直观的写下如下代码。

template <typename T1, typename T2>
decltype(t1 + t2) sum(const T1& t1, const T2& t2) {
   return t1 + t2;
}

但是,这样写是编译不过的,因为编译器只会从左往右地读入符号,这里的t1和t2在编译器看来都是未声明的。正确的写法是这样的。

template <typename T1, typename T2>
auto sum(const T1& t1, const T2 & t2) -> decltype(t1 + t2) {
    return t1 + t2;
}

基于范围的for循环

语法很简单,不赘述。主要使用时需要注意两点。一是使用基于范围的for循环需要for循环迭代的范围是可确定的。二是,基于范围的for循环要求迭代对象实现++和==等操作符,这点标准库中的容器不会有问题,但用户自定义的类需要自己实现。

强类型枚举

原来的枚举类型有非强类型作用域,允许隐式转换为整型,占用存储控件及符号性不确定的缺点。C++11引入了强类型枚举来解决问题。

声明强类型枚举只需要在enum后加上关键字class。比如:

enum class Type { General, Light, Medium, Heavy};

强类型枚举具有一下几点优势:

  • 强作用域,强类型枚举成员的名称不会输出到其父作用域空间。
  • 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换。
  • 可以指定底层类型。强类型枚举默认的底层类型为int,但也可以显式地指定底层类型,具体方法为在枚举名称后面加上":type",其中type可以是除了wchar_t以外的任何整型。比如:

enum class Type: char { General, Light, Medium, Heavy};

由于enum class是强类型作用域的,故匿名的enum class很可能什么都做不了。

常量表达式函数

常量表达式函数需要满足几个条件,否则不能用constexpr关键字进行修饰:

  • 函数只能包含return语句。
  • 函数必须有返回值。
  • 在使用前必须已经定义。
  • return返回语句中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式。

constexpr int GetConst() { return 1; }

常量表达式值

const int i = 0;

constexpr int j = 0;

constexpr表示的就是编译期常量,const表示的是运行期常量。

大部分情况下这两个定义是没有区别的。不过i只要在全局范围内声明,编译器一定会为它产生数据;而对于j,如果没有地方调用它,编译器可以选择不为它生成数据。

并且,默认只有内置类型才能修饰为常量表达式值,自定义类型如果要成为常量表达式值,必须定义一个constexpr修饰的构造函数。

tuple元组

当我们希望将一些数据组合成单一对象,但又不想麻烦地定义一个新的数据结构来表示这些数据时,tuple是非常有用的。一般,tuple可以用于函数返回多个返回值。

tuple容器, 可以使用直接初始化, 和"make_tuple()"初始化, 访问元素使用"get<>()"方法, 注意get里面的位置信息, 必须是常量表达式(const expression)。

可以通过"std::tuple_size<decltype(t)>::value"获取元素数量; "std::tuple_element<0, decltype(t)>::type"获取元素类型。

如果tuple类型进行比较, 则需要保持元素数量相同, 类型可以比较。

#include <iostream>  
#include <vector>  
#include <string>  
#include <tuple>  
  
using namespace std;  
  
std::tuple<std::string, int>  
giveName(void)  
{  
    std::string cw("Caroline");  
    int a(2013);  
    std::tuple<std::string, int> t = std::make_tuple(cw, a);  
    return t;  
}  
  
int main()  
{  
    std::tuple<int, double, std::string> t(64, 128.0, "Caroline");  
    std::tuple<std::string, std::string, int> t2 =  
            std::make_tuple("Caroline", "Wendy", 1992);  
  
    //返回元素个数  
    size_t num = std::tuple_size<decltype(t)>::value;  
    std::cout << "num = " << num << std::endl;  
  
    //获取第1个值的元素类型  
    std::tuple_element<1, decltype(t)>::type cnt = std::get<1>(t);  
    std::cout << "cnt = " << cnt << std::endl;  
  
    //比较  
    std::tuple<int, int> ti(24, 48);  
    std::tuple<double, double> td(28.0, 56.0);  
    bool b = (ti < td);  
    std::cout << "b = " << b << std::endl;  
  
    //tuple作为返回值  
    auto a = giveName();  
    std::cout << "name: " << get<0>(a)  
            << " years: " << get<1>(a) << std::endl;  
  
    return 0;  
}  

nullptr

在C++11中,nullptr是一个所谓“指针空值类型”的编译期常量。指针空值类型被命名为nullptr_t。

需要注意的是,nullptr是C++11中的关键字,它是有类型的,且仅可以被隐式转化为指针类型,其类型定义是:

typedef decltype(nullptr) nullptr_t;

lambda函数

lambda表达式的语法定义如下:

\[capture] (parameters) mutable ->return-type {statement};

  • \[capture]:捕捉列表。捕捉列表总是出现在lambda函数的开始处。实质上,[]是lambda引出符(即独特的标志符),编译器根据该引出符判断接下来的代码是否是lambda函数。捕捉列表能够捕捉上下文中的变量以供lambda函数使用。捕捉列表由一个或多个捕捉项组成,并以逗号分隔,捕捉列表一般有以下几种形式:

    • [var] 表示值传递方式捕捉变量var。
    • [=] 表示值传递方式捕捉所有父作用域的变量(包括this指针)。
    • [&var] 表示引用传递捕捉变量var。
    • [&] 表示引用传递捕捉所有父作用域的变量(包括this指针)。
    • [this]表示值传递方式捕捉当前的this指针。
    • [=,&a,&b]表示以引用传递的方式捕捉变量 a 和 b,而以值传递方式捕捉其他所有的变量。
    • [&,a,this]表示以值传递的方式捕捉 a 和 this,而以引用传递方式捕捉其他所有变量。
备注:父作用域是指包含lambda函数的语句块。
  • (parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号()一起省略。
  • mutable :mutable修饰符。默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->return-type :返回类型。用追踪返回类型形式声明函数的返回类型。出于方便,不需要返回值的时候也可以连同符号->一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
  • {statement} :函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。
// 简单示例
int a = 20, b = 10;

auto totalAB = [] (int x, int y)->int{ return x + y; };
int aAddb = totalAB(a, b);
cout << "aAddb :" << aAddb << endl;

// lambda与STL
vector<int> v{ 1, 2, 3, 4, 5 }; 
  
for_each( v.begin(), v.end(), [] (int val)  
{  
    cout << val;  
} );  

在现阶段,通常编译器都会把lambda函数转化为一个仿函数对象。这是编译器实现lambda函数的一种方式。因此,C++11中,lambda函数可以视为仿函数的一种等价形式。

原生字符串字面量

原生字符串字面量的意思就是所见即所得,在代码中的字符串常量是怎么样的,我们得到的就是怎么样的,不需要转义字符来控制特定的字符。

在C++11中程序员只需要在字符串前面加入前缀字母R,并在引号中使用括号左右标识即可将该字符串声明为原生字符串了。

// 输出带引号的字符串"你好"
int main()
{
    std::string str = "\"你好\"";
    std::cout << str << std::endl;

    const char* str1 = R"("你好")";
    std::cout << str1 << std::endl;

    return 0;
}
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-11-15

c++11强化知识点

初始化列表(std::initializer_list)

  • c++11提供了std::initializer_list,将使得类对象的初始化也可以和普通数组或者POD数据一样使用初始化列表的方式。只要为类对象提供初始化列表构造函数即可。
  • std::initializer_list也可以作为函数的参数使用。
  • 初始化列表std::initializer_list相关示例代码如下。
#include <iostream>
#include <initializer_list>
#include <vector>

using namespace std;

class InitClass {
public:
    InitClass(initializer_list<int> list) {
        for (int l : list)
            initializer_list_.emplace_back(l);
    }

    void PrintInit() {
        for (int l : initializer_list_)
            cout << l << endl;
    }

    void Print(initializer_list<int> list) {
        for (int l : list)
            cout << l << endl;
    }

private:
    vector<int> initializer_list_;
};

struct A {
    double a;
    int b;
};
struct B {
    B(int a, double b): a_(a), b_(b) {}
private:
    int a_;
    double b_;
};


int main()
{
    // 使用初始化列表初始化类对象
    InitClass i = {1, 2, 3, 4, 5};
    i.PrintInit();
    cout << endl;
    // 初始化列表做函数参数
    i.Print({1, 2, 3});
    // 使用初始化列表初始化POD数据
    vector<int> v = {1, 2, 3, 4};
    A a {1.1, 1};
    B b {2, 2.2};
    
    return 0;
}

变长参数模板(typename... Args)

  • c++11提供了变长参数模板,堪比黑魔法。可以实现任意类型、任意个数的变长参数模板类和函数。
  • 可以使用经典的递归模板函数的方式去取出变长参数模板函数中的参数,示例代码如下。
#include <iostream>
template<typename T>
void printf(T value) {
    std::cout << value << std::endl;
}
template<typename T, typename... Args>
void printf(T value, Args... args) {
    std::cout << value << std::endl;
    printf(args...);
}
int main() {
    printf(1, 2, "123", 1.1);
    return 0;
}
  • c++14提供了更简便的方法,可以使用初始化列表展开变长参数,示例代码如下。
// 编译这个代码需要开启 -std=c++14
#include <iostream>
template<typename T, typename... Args>
auto print(T value, Args... args) {
    std::cout << value << std::endl;
    return std::initializer_list<T>{([&] {
        std::cout << args << std::endl;
    }(), value)...};
}
int main() {
    print(1, 2.1, "123");
    return 0;
}

强类型枚举(enum class)

  • c++11提供了类型安全的枚举类enum class。枚举类中定义的枚举值不能够被隐式转换为整数,也不能与整数直接比较,更不能与不同的枚举类型的枚举值比较。
  • 枚举类定义的枚举值可以定义相同的值。
  • 枚举类中可以自己定义枚举值的类型,默认是int。
  • 可以通过重载<<运算符来实现直接打印枚举类的值。
  • 可以定义模板转换函数来方便将枚举类的枚举值与基本数据类型(如int)直接进行比较。
  • 所有示例代码如下。
#include <iostream>
enum class new_enum : unsigned int {
    value1,
    value2,
    value3 = 888,
    value4 = 888
};

template<typename T>
std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream, const T& e) {
    return stream << static_cast<typename std::underlying_type<T>::type>(e);
}

template<typename T>
auto to_underlying_type(const T& e) {
    return static_cast<typename std::underlying_type<T>::type>(e);
}

int main() {
    if (new_enum::value3 == new_enum::value4 && 888 == to_underlying_type(new_enum::value3))
    {
         std::cout << new_enum::value3 << std::endl;
    }
    return 0;
}

函数对象包装器(std::function)

  • c++11提供了可以定义任意可调用类型的函数对象包装器(std::function),这是对函数对象的一种类型安全的包装。
  • 利用std::function和c++11提供的更强大的using语法以及Lambda函数,我们可以更方便的实现类对象中的简单的回调函数。示例代码如下。
#include <iostream>
#include <memory>

class A {
public:
    using CallBack = std::function<void()>;

    void SetCallBack(CallBack cb) { call_back_ = cb; }
    void CallCallBack() {
        if (call_back_)
        {
            call_back_();
        }
        
    }

private:
    CallBack call_back_ = nullptr;
};

class B {
public:
    B() {
        a = std::make_shared<A>();
        a->SetCallBack([](){
            std::cout << "call A cb in B" << std::endl;
        });
        a->CallCallBack();
    }

private:
    std::shared_ptr<A> a = nullptr;
};

int main() {
    B b;
}
  • 如果回调函数很复杂,不适合用Lambda函数实现,也可以通过std::bind来将回调函数绑定到类的成员函数上。

智能指针初始化(make_unique)

  • 为了简化c++的指针管理,c++11扩展了标准库,推出了智能指针——std::shared_ptr/std::unique_ptr/std::weak_ptr。智能指针使用引用计数的方式来实现自动释放资源,使得c++语言更具现代性。
  • 标准库提供了std::make_shared来初始化一个std::shared_ptr,避免了我们使用new来初始化std::shared_ptr。但是由于“被标准委员会忘记了”,标准库却没有提供make_unique的方法来给我们初始化一个std::unique_ptr(好在c++14提供了)。为此我们可以自己实现一个make_unique,代码如下。
#include <memory>

template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

int main() {
    std::unique_ptr<int> p = make_unique<int>(1);
    return 0;
}
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-11-06

查找代码中所有中文字符的正则表达式

  • 所有中文

^((?!(\*|//)).)+[\u4e00-\u9fa5]

  • 排除注释

(".[\u4E00-\u9FA5]+)|([\u4E00-\u9FA5]+.")

查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-10-16

解决github无法访问的问题,亲测有效。

  • 最近发现github突然打不开了,查了下原因好像也没有被墙。最后,终于找到一个改host的方法使得可以正常打开github,而且发现打开速度比之前还快。大家也可以试试。
  • C:WindowsSystem32driversetc中打开host文件(可以用UE等编辑工具打开),在该文件中添加如下内容。
# GitHub Start 
140.82.114.4 github.com
140.82.114.4 gist.github.com
185.199.108.153 assets-cdn.github.com
151.101.64.133 raw.githubusercontent.com
151.101.108.133 gist.githubusercontent.com
151.101.108.133 cloud.githubusercontent.com
151.101.108.133 camo.githubusercontent.com
151.101.108.133 avatars0.githubusercontent.com
151.101.108.133 avatars1.githubusercontent.com
151.101.108.133 avatars2.githubusercontent.com
151.101.108.133 avatars3.githubusercontent.com
151.101.108.133 avatars4.githubusercontent.com
151.101.108.133 avatars5.githubusercontent.com
151.101.108.133 avatars6.githubusercontent.com
151.101.108.133 avatars7.githubusercontent.com
151.101.108.133 avatars8.githubusercontent.com 
# GitHub End
  • 保存文件,重新刷新网页即可打开github了。
  • github打开慢的同学,也可以试试这个方法。
  • 感谢大佬,原文在此
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-10-06

VSCode C++开发环境配置文件(Windows&Mac)

Windows(使用MSVC编译)

  • c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "windowsSdkVersion": "8.1",
            "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "msvc-x64"
        }
    ],
    "version": 4
}
  • launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "cl.exe - 生成和调试活动文件",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "preLaunchTask": "C/C++: cl.exe build active file"
        }
    ]
}
  • tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "msvc build",
            "type": "shell",
            "command": "cl.exe",
            "args": [
                "/EHsc",
                "/Zi",
                "/Fe:",
                "main.exe",
                "main.cpp"
            ],
            "group": "build",
            "presentation": {
                "reveal": "always"
            },
            "problemMatcher": "$msCompile"
        },
        {
            "type": "shell",
            "label": "C/C++: cl.exe build active file",
            "command": "cl.exe",
            "args": [
                "/Zi",
                "/EHsc",
                "/Fe:",
                "${fileDirname}\\${fileBasenameNoExtension}.exe",
                "${file}"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$msCompile"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}
  • Windows中使用MSVC来编译源文件一定要通过VS提供的Developer Command Prompt(开发人员命令提示)工具来打开。
  • 打开方式是:

    • cd到源文件的工作区。
    • 输入“code .”,之后就会自动打开VSCode。
  • 想调试源文件就将launch.json中的stopAtEntry的值设置为true。之后打断点,F5就可以调试了。

Mac(使用clang编译)

  • c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "macFrameworkPath": [
                "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
            ],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}
  • launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "clang++ build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "targetArchitecture": "x86_64",
            "program": "${fileDirname}/${fileBasenameNoExtension}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "MIMode": "lldb",  
            "preLaunchTask": "clang++ build active file"
        }
    ]
}
  • tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "shell",
            "label": "clang++ build active file",
            "command": "/usr/bin/clang++",
            "args": [
                "-g",
                "-std=c++11",
                "-lpthread",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "/usr/bin"
            }
        },
        {
            "type": "shell",
            "label": "g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-g",
                "-std=c++11",
                "-lpthread",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "/usr/bin"
            }
        }
    ]
}
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-04-30

iOS备忘录

年前加过年这段时间,公司的项目时间紧非常忙,加上自己在做一个iOS的小项目,下班和周末时间就基本没有休息了,这样博客也就懈怠了。

这里就稍稍总结了一些我写这个小项目时候用上的一些代码片段。一来是给自己做个备忘,二来分享出来希望对大家也有帮助。

//通过图片Data数据第一个字节 来获取图片扩展名
- (NSString *)contentTypeForImageData:(NSData *)data {
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return @"jpeg";
        case 0x89:
            return @"png";     
        case 0x47:
            return @"gif";        
        case 0x49:   
        case 0x4D:
            return @"tiff";        
        case 0x52:  
            if ([data length] < 12) {
                return nil;
            }
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return @"webp";
            }
            return nil;
    }
    return nil;
}
/** 设置圆形图片(放到分类中使用) */
- (UIImage *)cutCircleImage {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    // 获取上下文
    CGContextRef ctr = UIGraphicsGetCurrentContext();
    // 设置圆形
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextAddEllipseInRect(ctr, rect);
    // 裁剪
    CGContextClip(ctr);
    // 将图片画上去
    [self drawInRect:rect];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}
#define SWWeakSelf(type)  __weak typeof(type) weak##type = type;
#define SWToast(str) [NSString stringWithFormat:@"%@",str]
// AFNetworking 3.0 设置http头
AFHTTPSessionManager *manger = [AFHTTPSessionManager manager];
AFHTTPRequestSerializer *requestSerializer =  [AFJSONRequestSerializer serializer];

NSDictionary *headerFieldValueDictionary = @{@"version":@"1.0"};
if (headerFieldValueDictionary != nil) {
    for (NSString *httpHeaderField in headerFieldValueDictionary.allKeys) {
        NSString *value = headerFieldValueDictionary[httpHeaderField];
        [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField];
    }
}
manger.requestSerializer = requestSerializer;
[manger GET:@"url" parameters:nil  progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {

   } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

}];
// 单例
+ (SWString *)sharedManager{
    static SWString *shared = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[self alloc]init];
    });
    return shared;
}
// 检测网络状态
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
    
    [manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        //这里是监测到网络改变的block
        //在里面可以随便写事件
        switch (status) {
            case AFNetworkReachabilityStatusUnknown:
                SWLog(@"未知网络状态");
                break;
            case AFNetworkReachabilityStatusNotReachable:
                SWLog(@"无网络");
                break;
                
            case AFNetworkReachabilityStatusReachableViaWWAN:
                SWLog(@"蜂窝数据网");
                break;
                
            case AFNetworkReachabilityStatusReachableViaWiFi:
                SWLog(@"WiFi网络");
                break;
            default:
                break;
        }
    }] ;
// NSDate转NSString
+ (NSString *)nowDataString{
    NSDate *date = [NSDate date];
    NSDateFormatter *objDateformat = [[NSDateFormatter alloc] init];
    [objDateformat setDateFormat:@"yyyyMMddHHmmss"];
    return [objDateformat stringFromDate: date];
}
// ios md5
#import <CommonCrypto/CommonDigest.h>

- (NSString *)MD5:(NSString *)mdStr
{
    const char *original_str = [mdStr UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(original_str, strlen(original_str), result);
    NSMutableString *hash = [NSMutableString string];
    for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
        [hash appendFormat:@"%02X", result[i]];
    return [hash lowercaseString];
}
// ios des加解密
 #import <CommonCrypto/CommonCryptor.h>
+ (NSString *) encryptUseDES:(NSString *)plainText key:(NSString *)key
{
    NSString *ciphertext = nil;
    const char *textBytes = [plainText UTF8String];
    NSUInteger dataLength = [plainText length];
    unsigned char buffer[1024];
    memset(buffer, 0, sizeof(char));
    Byte iv[] = {1,2,3,4,5,6,7,8};
    size_t numBytesEncrypted = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, kCCAlgorithmDES,
                                          kCCOptionPKCS7Padding,
                                          [key UTF8String], kCCKeySizeDES,
                                          iv,
                                          textBytes, dataLength,
                                          buffer, 1024,
                                          &numBytesEncrypted);
    if (cryptStatus == kCCSuccess) {
        NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesEncrypted];
        
        ciphertext = [[[NSString alloc] initWithData:[GTMBase64 encodeData:data] encoding:NSUTF8StringEncoding] autorelease];
    }
    return ciphertext;
}
 
//解密
+ (NSString *) decryptUseDES:(NSString*)cipherText key:(NSString*)key 
{
    NSData* cipherData = [GTMBase64 decodeString:cipherText];
    unsigned char buffer[1024];
    memset(buffer, 0, sizeof(char));
    size_t numBytesDecrypted = 0;
    Byte iv[] = {1,2,3,4,5,6,7,8};
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, 
                                          kCCAlgorithmDES, 
                                          kCCOptionPKCS7Padding, 
                                          [key UTF8String], 
                                          kCCKeySizeDES, 
                                          iv, 
                                          [cipherData bytes], 
                                          [cipherData length], 
                                          buffer, 
                                          1024, 
                                          &numBytesDecrypted);
    NSString* plainText = nil;
    if (cryptStatus == kCCSuccess) {
        NSData* data = [NSData dataWithBytes:buffer length:(NSUInteger)numBytesDecrypted];
        plainText = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
    }
    return plainText;
}
查看原文

赞 0 收藏 0 评论 0

吴尼玛 发布了文章 · 2020-04-19

解决xib自定义tableFooterView一个神奇的bug

最近看视频学习,做一个demo的时候碰到一个神奇的bug,后来经过各种搜索、调试和修改代码虽然把这个bug解决了,但不知道为什么。这里打算把这个过程分享出来,给大家做参考顺便也求大神来指导下。

事情是这样的,我打算用xib自定义一个UIView,然后将他设置为tableView的tableFooterView以实现下面这样的效果。

点击加载更多.gif

其中我xib中的控件是这样的。在xib上首先放一个点击加载更多的UIButton。然后放一个正在加载的UIView,这个View里面有一个UIActivityIndicatorView和正在加载更多数据的UILabel,并且一开始隐藏这个View,点击Button后显示它。最后设置好各个约束。

xib.png

设置tableFooterView的代码是这样的。

    WKLoadMoreFooter *footerView = [WKLoadMoreFooter loadMoreFooter];
    footerView.delegate = self;
    _dealsTable.tableFooterView = footerView;

为了能看清,我给xib设置了个背景色,效果是这样的。Button的文字呢,当时我是懵逼的。

懵逼的效果.png

于是我设置了半天的Button、Label等等控件的文字颜色和背景色,发现都没有用,点击后虽然能加载,但是一直就是这个粉红色的。然后,我突然想到是不是这个UIView的高度问题。于是就把他设置成tableHeaderView,果然效果就出来了,它被拉高了,覆盖了下面的两个cell。

将自定义的控件设置成tableHeaderView的效果.png

之后我就找了半天怎么设置tableHeaderView的高度,试了好多种方法都没有什么用。最后终于找到一个方法解决了这个bug,就是手动创建一个UIView ,设置这个UIView为tableHeaderVIew,再把xib的UIView放在这个UIView上面。代码如下:

    UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 44)];
    _dealsTable.tableHeaderView = headerView;
    WKLoadMoreFooter *loadMoreFooterView = [WKLoadMoreFooter loadMoreFooter];
    footerView.delegate = self;
    footerView.frame = CGRectMake(0, 0, _dealsTable.frame.size.width, 44);
    [headerView addSubview: loadMoreFooterView];

终于正常啦!.png

最后将xib的背景色改回来,将其改为tableFooterView,前面想要的效果就出来了。如果你也遇到这样类似的问题可以尝试用这个方法去解决。然而,虽然解决了这个问题,但心中充满疑惑,到底这个xib创建的UIView设置成tableFooterView的时候发生了什么呢,求大神们指教。

iOS新手,也求大神们勿喷。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 3 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-01-04
个人主页被 700 人浏览