打印

【连载】 C++14学习笔记 —— C++编程新时代.

[复制链接]
楼主: dong_abc
手机看帖
扫描二维码
随时随地手机跟帖
21
本帖最后由 elec921 于 2013-8-13 19:57 编辑

“如何不露声色地侮辱C++?”

“C艹”

使用特权

评论回复
22
dong_abc|  楼主 | 2013-8-13 23:13 | 只看该作者
本帖最后由 dong_abc 于 2013-8-30 22:04 编辑

1.2  委派构造函数

看书看得太快,赶紧来整理一下,加深印象!

若某个类的构造函数较多,委派构造函数就可以为程序员节省不少时间,而且程序本身更具可读性。
看下面实例:
class A {
public:
        A(): name(“plmm”),id(520) {init(); ...}
        A(int i): name(“plmm”),id(520) {init(); ...}
        A(char c): name(“plmm”),id(520) {init(); ...}
private:
       void init();
       char name;
       int id;
}

此类中的构造函数存在一些冗余代码,实际应用可能会有更多的冗余代码。
像这种情况,我们可以利用委派构造函数来简化代码。
我们可以抽象出一个基准的构造函数,然后利用其他构造函数来委托基准构造函数来进行构造。
class A {
public:
        A(): A("plmm",520) {...}
        A(int i):A("plmm",520) {...}
        A(char c): A("plmm",520) {...}
private:
       A(char c, int i):name(c),id(i) {init();}
       void init();
       char name;
       int id;
}
这里我抽象除了A(char c, int i):name(c),id(i) {init();}函数,然后其他构造函数就可以委托此函数来构造了。
我们还可以更抽象,更通用地实现——使用构造函数模板来产生目标构造函数。
class A {
          template<typename T1 ,typename T2> A(T1 c ,T2 i):name(c),id(i) {init();}
public:
         A():A(''plmm", 520) {...}
         A(int i):A(''plmm", 520) {...}
         A(char c):A(''plmm", 520) {...}
private:
       void init();
       char name;
       int id;
}
你应该能发现,委派构造函数可以用一个词来总结——抽象。
我一直都是以“少打字、少编码,多思考”的方针来写学习笔记,之后的例程争取都用少于10行代码来实现,
主题描述也尽量做到简洁易懂。

使用特权

评论回复
23
dong_abc|  楼主 | 2013-8-13 23:55 | 只看该作者
本帖最后由 dong_abc 于 2013-8-30 22:06 编辑

第4章  新手易学,老兵易用
1、基于迭代器的for循环。
请看,用for循环输出一个数组:
#include<iostream>
using namespace std;
int main()
{
    int arry[5] = {1,2,3,4,5};
    for(auto e: arry)   //在数组arry中用迭代器e进行遍历.
       cout<<e<<'\t';
}
只有数组中大小确定的情况下才能用迭代器的for循环。

2、auto自动推导的新特性(三大优势)
2.1  简化复杂的初始化表达式
void foo(std::vector<std::string> & v)
{
     std::vector<std::string>:: iterator i = v.begin();
     //此句可以简化为  auto i = v.begin();
     //因为foo函数的参数已经定义了向量v, 编译器完全可以推导出v.begin()是一个向量成员函数。
     ...
}

2.2  auto可以免除一些类型声明时的麻烦。
int main()
{
    int i =1;
    float j = 1.1;
    auto k = i+j; //如果i和j在别的比较隐蔽的地方,这样做就很省心了。
}

2.3  auto的“自适应”性,更好的支持泛型编程。

#include <iostream>
using namespace std;
template<typename T1, typename T2>
double Sum(T1 &t1, T2 &t2)
{
    auto s = t1 + t2;//s的类型会在模板实例化的时候被推导出来
    return s;
}
int main()
{
    int a=3;
    long b=5;
    //float c=1.0f, d=2.3f;
    auto e = Sum<int,long>(a,b);   //e的类型被推导为long
    //auto f = Sum<float,float>(c,d); //f的类型被推导为float
    e = Sum(a,b);
    std::cout << e << std::endl;
}

@  auto在以下4种情况下是不能推导的.
#include<vector>
using namespace std;
void foo(auto i = 1) {}         //1、auto不能作为函数参数
struct str{
     auto j = 2;                   //2、对于结构体来说,非静态成员变量不能用auto推导。
}
int main()
{
   auto k[3]=x;                  //3、不能用auto声明数组
   vector<auto> v = {1};    //4、在模板实例化时,不能用auto作为参数。
}

使用特权

评论回复
24
dong_abc|  楼主 | 2013-8-15 23:08 | 只看该作者
嘿嘿,好戏还在后头

使用特权

评论回复
25
ygl968| | 2013-8-15 23:21 | 只看该作者
mark

使用特权

评论回复
26
dong_abc|  楼主 | 2013-8-18 13:37 | 只看该作者
3、decltype的应用
decltpye与auto的用法类似,最典型的是这种:
using typename = decltype(sizeof(0));
decltype后面接表达式,并将表达式的类型推导为 typename .

使用特权

评论回复
27
dong_abc|  楼主 | 2013-8-18 13:43 | 只看该作者
4、追踪返回类型。
auto 与 decltype 联合使用可以解除类型依赖,释放泛型编程的能力。
#include <iostream>
using namespace std;
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
    static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
    static_assert(std::is_integral<T2>::value, "Type T2 must be integral");
    return t1 + t2;
}

int main()
{
    //std::cout << add(1, 3.14) << std::endl;
    std::cout << add(111, 2) << std::endl;
    return 0;
}
这个例程在前面静态断言主题中已经出现过,你可以发现整个程序中没有一个具体的类型,
全部都是动态的泛型。

使用特权

评论回复
28
dong_abc|  楼主 | 2013-8-18 13:50 | 只看该作者
dong_abc 发表于 2013-8-18 13:43
4、追踪返回类型。
auto 与 decltype 联合使用可以解除类型依赖,释放泛型编程的能力。
这个例程在前面静态 ...

注意上述函数模板中的“追踪返回类型”
template <typename T1, typename T2>
auto add(T1 t1, T2 t2) -> decltype(t1 + t2)
{
    static_assert(std::is_integral<T1>::value, "Type T1 must be integral");
    static_assert(std::is_integral<T2>::value, "Type T2 must be integral");
    return t1 + t2;
}
auto 和 ->return_type构成追踪返回类型函数的两个基本元素。 decltype(t1 + t2)推导出函数返回值后,置于函数尾返回。

使用特权

评论回复
29
信步看风景| | 2013-8-18 19:21 | 只看该作者
哈哈,世界不停地变

C走向面向对象,C++走向泛型编程,

使用特权

评论回复
30
dong_abc|  楼主 | 2013-8-18 19:48 | 只看该作者
信步看风景 发表于 2013-8-18 19:21
哈哈,世界不停地变

C走向面向对象,C++走向泛型编程,


世界一直向前奔跑,永不停歇~~~

使用特权

评论回复
31
dong_abc|  楼主 | 2013-8-19 00:57 | 只看该作者
本帖最后由 dong_abc 于 2013-8-19 18:05 编辑

看着C++11新特性还挺多的,没想到,不到半个月就敲了近一半。后面的难度大一些,会更注重应用,学以致用。

使用特权

评论回复
32
xiangchli| | 2013-8-21 15:00 | 只看该作者

使用特权

评论回复
33
姚立明4325| | 2013-8-21 19:15 | 只看该作者
支持。。。

使用特权

评论回复
34
dong_abc|  楼主 | 2013-8-22 00:37 | 只看该作者
本帖最后由 dong_abc 于 2013-8-31 12:21 编辑

第8章  接受新思想,C++ 模板元编程 .

模板是C++的重头戏,我学习C++的主要目的就是为了学习模板编程,改变固有的思维习惯。使用模板是为了使编程更简单。
当然模板程序也就是泛型程序,他是在编译期就进行解析的,这个最大的优点就是提高了运行性能,减少了运行时的出错。

1、最简单的模板。
#include <iostream>
template <typename T>
void Output(const T & value)
{std::cout << value << std::endl;}
int main()
{
     Output("Hello, world!"); //将value匹配成字符串.
}
//如果连Hello world都可以被模板化,还有什么不可以 ?

2、模板特化
1)模板全特化
// 主模板
template<int N>
struct Fib
{
    enum { Result = (5N+1); };
};
// 完全特化版
template <>
struct Fib<1>
{
    enum { Result = 1 };
};
// 完全特化版
template <>
struct Fib<0>
{
    enum { Result = 0 };
};

// 示例
int main()
{
    int i = Fib<10>::Result;
    // std::cout << i << std::endl;
}

这个实例来自荣耀2005年的 《C++模板元编程技术与应用》报告。
当N=1或者0时,分别运行下面的特化模板,否则运行上面的通用模板。

2)模板偏特化

// 主模板
template<bool condition, typename T1, typename T2>
struct IfThenElse
{
    typedef T1 ResultType;
};
// 局部特化
template<typename T1, typename T2>
struct IfThenElse<false, T1, T2>
{
    typedef T2 ResultType;
};
// 示例
int main()
{
     IfThenElse<(1 + 1 == 2), int, char>::ResultType i; //我们什么也没做,只是定义 i的类型为int
}

主模板里边有一个bool型的condition条件选择变量。当condition参数定义为false,运行下面的偏特化模板,否则运行主模板。

3、递归模板
递归模板是非常有用的,稍后我会列举一些应用实例。

// 主模板
template<int N>
struct Fib
{
    enum { Result = Fib<N-1>::Result + Fib<N-2>::Result };
};
// 完全特化版
template <>
struct Fib<1>
{
    enum { Result = 1 };
};
// 完全特化版
template <>
struct Fib<0>
{
    enum { Result = 0 };
};
// 示例
int main()
{
    int i = Fib<10>::Result;
    // std::cout << i << std::endl;
}
还是上面的例子,将主模板改造成递归模板。
运作机理:当编译器实例化Fib<10>时,为了给其enum Result赋值,编译器需要对Fib<9>和Fib<8>进行实例化,同理,又需要针对Fib<7>和Fib<6>实例化同样的模板……,当实例化到Fib<1>和Fib<0>的时候,完全特化版被实例化,至此递归结束。这个处理过程类似于递归函数调用,编译器被用于解释元程序,生成的结果程序中仅包含一个常量值。

再给一个简单实例。用递归模板 计算 1+2+3 ......+100 = ?

#include <iostream>
using namespace std;
template<int N>
class Sum
{
    public:
        enum {value = N + Sum<N-1>::value };
};
template<>
class Sum<0>
{
    public:
        enum {value = 0};
};

int main()
{
    cout << Sum<100>::value << endl;
}
//第一个主模板是一个递归模板,第二个是个主模板的偏特化模板,用来截止主模板的递归。

使用特权

评论回复
35
dong_abc|  楼主 | 2013-8-22 01:05 | 只看该作者
为什么 我们都会觉得C++复杂,看下面这段话,基本说出了其中原委。

使用特权

评论回复
评分
参与人数 1威望 +3 收起 理由
tee. + 3 赞一个!
36
缥缈九哥| | 2013-8-22 06:21 | 只看该作者
我也过来看看

使用特权

评论回复
37
邓5566| | 2013-8-23 11:22 | 只看该作者
好东西啊LZ

使用特权

评论回复
38
dong_abc|  楼主 | 2013-8-23 18:18 | 只看该作者
本帖最后由 dong_abc 于 2013-8-23 18:20 编辑
缥缈九哥 发表于 2013-8-22 06:21
我也过来看看


欢迎9G,c++在21ic属于非主流! 都是我自己 自编、自导、自演加自顶 !

使用特权

评论回复
39
dong_abc|  楼主 | 2013-8-23 21:03 | 只看该作者
本帖最后由 dong_abc 于 2013-8-24 15:55 编辑

4、再复习一下,前几天讨论的递归模板。我们来点实际应用的,CRC校验大家经常都会用到。

一般情况,我们都是像下面这样来计算CRC校验码,将CRC余式表存在一个数组里,然后循环计算。
/* 按字节查表求CRC */
unsigned int byte_crc(unsigned char *ptr, unsigned char len)
{
unsigned int crc;
unsigned char da;
/* CRC 余式表 */
unsigned int xdata crc_ta[256]=
{        
  0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
  0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
  0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
  0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
  0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
  0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
  0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
  0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
  0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
  0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
  0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
  0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
  0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
  0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
  0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
  0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
  0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
  0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
  0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
  0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
  0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
  0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
  0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
  0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
  0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
  0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
  0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
  0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
  0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
  0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
  0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
  0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
crc = 0;
while(len--!=0)
{
  da=(uchar) (crc/256);  // 以8位二进制数的形式暂存CRC的高8位
  crc<<=8;     // 左移8位,相当于CRC的低8位乘以2的8次方
  crc^=crc_ta[da^*ptr];  // 高8位和当前字节相加后再查表求CRC,再加上以前的CRC   
  ptr++;
}
return(crc);
}

int main()
{
unsigned char len;
/* 8字节测试数据 */
unsigned char testdata[8]={0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88};
crc_byte = byte_crc(testdata,len);   // 调用按字节查表求CRC子函数
}


再看下面这个例子,用C++模板来计算crc,代码非常少,非常简洁,这段代码在我第一次看时,完全看不懂;看第二次的时候,看得我瘫痪了两天。最后在john_lee老师的指导下,终于看懂了。
#include <cstdint>
#include <iostream>
#include <iomanip>
namespace crc32 {
template <uint32_t CRC, uint8_t BIT = 0>
struct byte : byte <(CRC >> 1) ^ (-int32_t (CRC & 1) & 0xedb88320), BIT + 1> { };
template <uint32_t CRC>
struct byte <CRC, 8> { uint32_t value{ CRC }; };
template <uint16_t I = 0>
struct array : byte <I>, array <I + 1> { };
template <>
struct array <256> { };
struct table : private array <> {
  const uint32_t& operator [] (int i) const
  {
    return reinterpret_cast<const uint32_t*>(this);
  }
};
}
const crc32::table data;
int main ()
{
  using namespace std;
  uint_fast16_t n = sizeof (data) / sizeof (data[0]);
  uint_fast16_t i = 0;
  uint_fast8_t l = 8;
  do {
    cout << "0x" << setfill('0') << setw(8) << hex << data << ',';
    if (--l == 0) {
      cout << endl;
      l = 8;
    }
  } while (++i < n);
}
够简洁吧,看看CRC数据是如何生成的,如何存储的,你看懂了吗?
...
...
下一贴来分析一下这段程序。

使用特权

评论回复
40
dong_abc|  楼主 | 2013-8-24 15:42 | 只看该作者
本帖最后由 dong_abc 于 2013-8-24 15:51 编辑

上面 先定义了一个名字空间
namespace crc32 {
...
}
然后在这个名字空间里定义了两个模板
template <uint32_t CRC, uint8_t BIT = 0>
struct byte : byte <(CRC >> 1) ^ (-int32_t (CRC & 1) & 0xedb88320), BIT + 1> { };
template <uint32_t CRC>
struct byte <CRC, 8> { uint32_t value{ CRC }; };
这两个模板,是不是有点熟悉,一对模板啊。
第一个是个递归模板,struct byte : byte < ... > ,   byte在递归继承,
原始模板byte <CRC = 0, BIT = 0>继承了
原始模板byte <CRC =?, BIT = 1>继承了
原始模板byte <CRC =?, BIT = 2>继承了
...
...
当BIT自加到8的时候就调用了上面第二个偏特化模板。
struct byte <CRC, 8> { uint32_t value{ CRC }; };
这个偏特化模板仅仅将value的值初始化为CRC,CRC是前面经过递归计算而来的,因此,value即是计算而得的CRC值。
跟前面的递归模板计算1+2+3+...+100=?是不是类似呢。

既然我们可以用递归模板计算得到CRC值,那么如何存储呢,
template <uint16_t I = 0>
struct array : byte <I>, array <I + 1> { };
template <>
struct array <256> { };
仍然用了一对递归模板,这个模板里边array <I + 1> { }什么也没干,仅仅将I加1,以至于byte<I>形成递归。
直到array <256> 进入偏特化模板struct array <256> { }; 此偏特化模板为空,什么也没干,起一个递归截止作用。
就这样我们得到了256个CRC初始值,然后用一个类数组(不知道这样称呼是否合适,table是个array模板的派生类)来保存。
struct table : private array <> {
const uint32_t& operator [] (int i) const
{
return reinterpret_cast<const uint32_t*>(this)[i];
}
};
}
const crc32::table data;
//table 继承了array模板,返回了array模板里的数据成员。

这是递归模板的第二个实例,有些复杂,要好好再琢磨一下。光看懂了并不表示就学会了,我们应该能灵活运用。
附上john_lee老师的课件 再谈元编程.pdf (281.42 KB)

后面更精彩!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则