天天看点

第十六章 16.1.1节练习

练习16.1

给出实例化的定义。

解答:

实例化,就是编译器将一个函数模板中的类型用一个具体类型替换的过程。

【引用百度百科】在面向对象的编程中,通常把用类创建对象的过程称为实例化。

练习16.2

编写并测试你自己版本的compare函数。

解答:

这个函数模板的实现,可以参考书中的实现。

不过这里要注意的是,当你的实现中涉及到比较运算符的时候,需要确定你使用的类型支持比较运算符。

练习16.3

对两个Sales_data对象调用你的compare函数,观察编译器在实例化过程中如何处理错误。

解答:

这个其实就是使用没有比较运算符的类进行比较。

#include <functional>
#include <iostream>

class A{
  public:
    A() = default;
    A(const int& n):num(n){}
    int num;
};

template<typename T>
int compare(const T& a, const T& b){
  if(std::less<T>()(a, b)){
    return 1;
  } else if(std::less<T>()(b, a)){
    return -1;
  } else {
    return 0;
  }
}

int main(){
  int ia(10), ib(12);
  std::cout << compare(ia, ib) << std::endl;
  A a(10), b(12);
  std::cout << compare(a, b) << std::endl;
}
           

这里就没有使用Sales_data类。自行定义了一个没有比较操作符的类。

然后,可以看一下编译器那边会给出什么提示。

我这边用的g++4.9.1出现的错误信息比较长,不过意思都差不多,就是没有找到‘<’这个比较运算符。

练习16.4

编写行为类似标准库find算法的模板。函数需要两个模板类型参数,一个表示函数的迭代器参数,另一个表示值的类型。使用你的函数再一个vector<int>和一个list<string>中查找给定值。

解答:

#include <iostream>
#include <iterator>
#include <vector>
#include <list>

template<typename Iterator, typename T>
Iterator find(const Iterator& begin, const Iterator& end, const T &element){
  for ( Iterator it = begin; it != end; ++it){
    if (*it == element){
      return it;
    }
  }
  return end;
}

int main(){
  int num[] = {1,2,3,4,5,6,7,8,9,10};
  std::vector<int> vint(std::begin(num), std::end(num));
  std::list<int> lint(std::begin(num), std::end(num));

  auto iit = find(vint.cbegin(), vint.cend(), 2);
  std::cout << *iit << std::endl;


  auto lit = find(lint.cbegin(), lint.cend(), 3);
  std::cout << *lit << std::endl;
}
           

练习16.5

为6.2.4节(第195页)中的print函数编写模板版本,它接受一个数组和引用,能处理任意大小、任意元素类型的数组。

解答:

#include <iostream>
#include <string>

template <typename T, std::size_t Num>
void print(T (&arr)[Num]){
  for (auto elem : arr){
    std::cout << elem << std::endl;
  }
}

int main(){
  int num[] = {1,2,3};
  std::string str[] = {"one", "two", "three"};

  print(num);
  print(str);
}
           

练习16.6

你认为接受一个数组实参的标准库函数begin和end是如何工作的?定义你自己版本的begin和end。

解答:

#include <iostream>

template <typename T, std::size_t Num>
T* begin(T (&Container)[Num]){
  return Container;
}

template <typename T, std::size_t Num>
T* end(T (&Container)[Num]){
  return (Container + Num);
}

int main(){
  int num[] = {1,2,3,4,5,6};
  std::cout << begin(num) <<": val is " << *begin(num) << std::endl;
  std::cout << end(num) - 1 <<": val is " << *(end(num) - 1) << std::endl;
  std::cout << end(num) << std::endl; //将打印最后一个元素后面的的地址
}
           

练习16.7

编写一个constexpr模板,返回给定数组的大小。

解答:

#include <iostream>

template <typename T, std::size_t Num>
size_t cconstexpr(T (&arr)[Num]){
  return Num;
}

int main(){
  int num[] = {1,2,3,4,5,6,7,8};
  std::cout << cconstexpr(num) << std::endl;
}
           

在c++11标准中constexpr已经是关键字了,所以不能使用其作为函数的名字,所以这里多加了一个c。

不过,在编译的时候将-std=c++11这个选项去掉,使用constexpr作为函数名就没有问题。

在VS2013中,应该也有对应C++11的选项,不过现在的环境没有VS2013,所以这个可能要晚些时候补上了。

练习16.8

再第97页的“关键概念”中,我们注意到,C++程序员喜欢用!=而不喜欢<。解释这个习惯的原因。

解答:

这里就是区别C和C++的地方,C++主打面向对象编程,而C是面向过程编程。

C++中编程的核心逐渐向不同类型的对象进行转移,这些对象不一定支持小于的操作,而不等于的操作更为方便使用。

同样,使用不等于也可以减少很多不必要的实现,从而能让泛型编程的代码更加优美。

C的话,大多数情况下都是对内置类型进行操作,所以在小于和不等于的选择中,肯定选择比较好理解的那个。

当然这里还是有历史原因的,教课书编写者的一些编程习惯,也会潜移默化的影响读者。

继续阅读