练习16.1:给出实例化的定义。
当编译器实例化一个模板时,它使用实际的模板实参替代对应的模板参数来创建出模板的一个新实例。
练习16.2:编写并测试你自己版本的compare函数。
#include
#include
using namespace std;
template
int compare( const T&, const T& );
int main()
{
int i = 5, j = 7;
string s1 = "fuck";
string s2 = "duck";
cout << compare( i, j ) << endl;
cout << compare( s1, s2 ) << endl;
return 0;
}
template
int compare( const T &t1, const T &t2 )
{
if( t1 < t2 )
return -1;
if( t2 < t1 )
return 1;
return 0;
}
#include
#include"Sales_data.h"
using namespace std;
template
int compare( const T&, const T& );
int main()
{
Sales_data item1( "C++Primer" );
Sales_data item2( "Effective C++" );
cout << compare( item1, item2 ) << endl;
return 0;
}
template
int compare( const T &t1, const T &t2 )
{
if( t1 < t2 )
return -1;
if( t2 < t1 )
return 1;
return 0;
}
练习16.4:编写行为类似标准库find算法的模板。函数需要两个模板参数,一个表示函数的迭代器参数,另一个表示值的类型。使用你的函数在一个vector
#include
#include
#include
#include
using namespace std;
template
I find( I b, I e, const T &val )
{
while( b != e && *b != val )
++b;
return b;
}
int main()
{
vector ivec( { 1, 3, 5, 7} );
list slst( { "hello", "mr", "tree" } );
auto result = find( ivec.begin(), ivec.end(), 0 );
auto result2 = find( slst.begin(), slst.end(), "mr" );
if( result == ivec.end() )
cerr << "cant find 0" << endl;
else
cout << "successfully found " << *result << endl;
if( result2 == slst.end() )
cerr << "cant find mr";
else
cout << "successfully found " << *result2 << endl;
return 0;
}
#include
template
void print( const T (&arr)[ N ] )
{
for( const auto &elem : arr )
std::cout << elem << " ";
std::cout << std::endl;
}
int main()
{
int j[ 2 ] = { 0, 1 };
int k[ 10 ] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print( j );
print( k );
return 0;
}
#include
template
T* begin( const T ( &arr )[ N ] )
{
return &arr;
}
template
T* end( const T ( &arr )[ N ] )
{
return arr + N;
}
using namespace std;
int main()
{
int ia[ 5 ] = { 0, 1, 2, 3, 4 };
auto beg = begin( ia );
auto en = end( ia );
while( beg != en )
cout << *beg++ << " ";
cout << endl;
return 0;
}
略了。
练习16.8:在第97页的“关键概念”中,我们注意到,C++程序员喜欢使用!=而不喜欢使用<。解释这个习惯的原因。
答:
因为所有的标准库容器都定义了!=运算符,只有少部分的标准库容器定义了<运算符,所以使用!=运算符可以减少算法适用容器的限制。
练习16.9:什么是函数模板?什么是类模板?
答:
一个函数模板就是一个公式,可用来生成特定类型的函数版本。
类模板是用来生成类的蓝图的。
标准答案:
简单来说,函数模板是可以实例化出特定函数的模板,类模板是可以实例化出特定类的模板。在使用上,编译器会根据调用来为我们推断函数模板的模板参数类型,而使用类模板实例化特定类就必须显式指定模板参数。
练习16.10:当一个类模板被实例化时,会发生什么?
编译器根据尖括号内给出的类型实参绑定到模板参数,模板参数出现的地方都被替换成模板实参。这样就实例化出一个特定的类。
练习16.11:下面List的定义是错误的。应如何修正它。
template
template
public:
//黑色删除线 代表的是 可以删除的地方
List();
List ( const List &);
List& operator=( const List & );
~List();
//红色代表修改的地方。
void insert( ListItem
private:
ListItem *front, *end;
ListItem
//模板的名字不是一个类型。实例化成具体的类才是一个类型。
};
练习16.12:编写你自己版本的Blob和BlobPtr模板,包含书中未定义的多个const成员。
#ifndef BLOB_H_INCLUDED
#define BLOB_H_INCLUDED
#include
#include
#include
#include
#include
//模板前置声明
template class Blob;
template
bool operator==( const Blob&, const Blob& );
template class BlobPtr;
template class Blob{
//友元声明
friend class BlobPtr;
friend bool operator==( const Blob&, const Blob& );
public:
typedef typename std::vector::size_type size_type;
//构造函数
Blob():
data( std::make_shared>() ) { }
Blob( std::initializer_list lst ):
data( std::make_shared>( lst ) ) { }
template
Blob( It b, It e ):
data( std::make_shared>( b, e ) ) { }
Blob( T *p, size_t n ):
data( std::make_shared>( p, p + n ) ) { }
//公共接口
bool empty() const { return data->empty(); }
size_type size() const { return data->size(); }
//先搁置
BlobPtr begin() { return BlobPtr( *this, 0 ); }
BlobPtr end() { return BlobPtr( *this, data->size() ); }
T& front();
T& back();
const T& front() const;
const T& back() const;
T& at( size_type );
const T& at( size_type ) const;
void push_back( const T &val ) { data->push_back( val ); }
void push_back( T &&val ) { data->push_back( std::move( val ) ); }
void pop_back();
//重载运算符
T& operator[]( size_type i );
const T& operator[]( size_type i ) const;
private:
//数据成员
std::shared_ptr> data;
//辅助函数
void check( size_type, const std::string& ) const;
};
template
void Blob::check( size_type n, const std::string &msg ) const{
if( n >= size() )
throw std::out_of_range( msg );
}
template
T& Blob::front() {
check( 0, "front on empty Blob" );
return data->front();
}
template
const T& Blob::front() const{
check( 0, "front on empty Blob" );
return data->front();
}
template
T& Blob::back() {
check( 0, "back on empty Blob" );
return data->back();
}
template
const T& Blob::back() const{
check( 0, "back on empty Blob" );
return data->back();
}
template
void Blob::pop_back(){
check( 0, "pop_back on empty Blob" );
data->pop_back();
}
template
T& Blob::at( size_type i ){
check( i, "subscript out of range" );
return ( *data )[ i ];
}
template
const T& Blob::at( size_type i ) const{
check( i, "subscript out of range" );
return ( *data )[ i ];
}
template
T& Blob::operator[]( size_type i ){
check( i, "subscript out of range" );
return ( *data )[ i ];
}
template
const T& Blob::operator[]( size_type i ) const{
check( i, "subscript out of range" );
return ( *data )[ i ];
}
//友元运算符定义
template
bool operator==( const Blob &lhs, const Blob &rhs ){
if( lhs.size() != rhs.size() )
return false;
for( size_t i = 0; i != lhs.size(); ++i )
if( lhs[i] != rhs[i] )
return false;
return true;
}
template
bool operator==( const BlobPtr&, const BlobPtr& );
template class BlobPtr{
friend bool operator==( const BlobPtr&, const BlobPtr& );
public:
BlobPtr():
curr( 0 ) { }
BlobPtr( Blob &a, std::size_t sz = 0 ):
wptr( a.data ), curr( sz ) { }
//公共接口
T& operator[]( std::size_t i ){
auto ret = check( i, "subscript out of range" );
return ( *ret )[ i ];
}
const T& operator[]( std::size_t i ) const{
auto ret = check( i, "subscript out of range" );
return ( *ret )[ i ];
}
T& operator*() const{
auto ret = check( curr, "dereference past end" );
return ( *ret )[ curr ];
}
T* operator->() const{
return & this->operator*();
}
BlobPtr& operator++();
BlobPtr& operator--();
BlobPtr operator++(int);
BlobPtr operator--(int);
private:
//数据成员
std::weak_ptr> wptr;
std::size_t curr;
//辅助函数
std::shared_ptr> check( std::size_t, const std::string& ) const;
};
template
bool operator==( const BlobPtr &lhs, const BlobPtr &rhs )
{
return lhs.wptr.lock().get() == rhs.wptr.lock().get() && lhs.curr == rhs.curr;
}
template
bool operator!=( const BlobPtr &lhs, const BlobPtr &rhs )
{
return !( lhs == rhs );
}
template
std::shared_ptr>
BlobPtr::check( std::size_t i, const std::string &msg ) const
{
auto ret = wptr.lock();
if( !ret )
throw std::runtime_error( "unbound BlobPtr" );
if( i >= ret->size() )
throw std::out_of_range( msg );
return ret;
}
template
BlobPtr& BlobPtr::operator++(){
check( curr, "increment past end" );
++curr;
return *this;
}
template
BlobPtr& BlobPtr::operator--(){
--curr;
check( curr, "decrement past begin" );
return *this;
}
template
BlobPtr BlobPtr::operator++(int){
auto ret = *this;
++*this;
return ret;
}
template
BlobPtr BlobPtr::operator--(int){
auto ret = *this;
--*this;
return ret;
}
#endif // BLOB_H_INCLUDED
练习16.13:解释你为BlobPtr的相等和关系运算符选择哪些类型的友好关系?
对每个BlobPtr实例与用相同类型实例化的运算符建立起一对一的的友好关系。
==运算符使用友元关系。因为要访问到BlobPtr的私有成员。
!=运算符不需要友元关系。它可以委托 operator==完成任务。
练习16.14:编写Screen类模板,用非类型参数定义Screen的高和宽。
Screen模板的定义:
#ifndef SCREEN_H_INCLUDED
#define SCREEN_H_INCLUDED
#include
#include
#include
template class Screen{
public:
typedef std::string::size_type pos;
Screen( char c = ' ' ):
cursor( 0 ),contents( H * W, c ) { }
//接口成员
char get() const { return contents[ cursor ]; }
char get( pos row, pos col ) const
{ auto ret = check( row, col ); return contents[ret]; }
Screen& set( char ch ) { contents[cursor] = ch; return *this; }
Screen& set( pos row, pos col, char ch )
{ auto ret = check( row, col ); contents[ret] = ch; return *this; }
Screen& move( pos row, pos col ) { cursor = check( row, col ); return *this; }
Screen& display( std::ostream &os ) { do_display(os); return *this; }
const Screen& display( std::ostream &os ) const { do_display(os); return *this; }
private:
pos cursor;
std::string contents;
//辅助函数
pos check( pos row, pos col ) const{//检查下标是否越界。
if( row >= H )
throw std::out_of_range( "invalid row" );
if( col >= W )
throw std::out_of_range( "invalid column" );
return row * W + col;
}
void do_display( std::ostream &os ) const{
for( auto i = 0; i < H; ++i ){
for( auto j = 0; j < W; ++j )
os << contents[ i * W + j ];
os << std::endl;
}
}
};
#endif // SCREEN_H_INCLUDED
测试程序:
#include
#include"Screen.h"
using namespace std;
int main()
{
Screen<5, 5> myScreen( 'X' );
myScreen.move( 4, 0 ).set( '#' );
cout << myScreen.get( 4, 0 ) << "\n\n";
myScreen.display( cout );
return 0;
}
测试结果:
练习16.15:为你的Screen模板实现输入和输出运算符。Screen类需要哪些友元来令输入和输出运算符正确工作?解释每个友元声明为什么是必要的?
#ifndef SCREEN_H_INCLUDED
#define SCREEN_H_INCLUDED
#include
#include
#include
//前置声明
template class Screen;
template
std::ostream& operator<<( std::ostream&, Screen& );
template
std::istream& operator>>( std::istream&, Screen& );
template class Screen{
//友元声明
friend std::ostream& operator<< ( std::ostream&, Screen& );
friend std::istream& operator>> ( std::istream&, Screen& );
public:
typedef std::string::size_type pos;
Screen( char c = ' ' ):
cursor( 0 ),contents( H * W, c ) { }
//接口成员
char get() const { return contents[ cursor ]; }
char get( pos row, pos col ) const
{ auto ret = check( row, col ); return contents[ret]; }
Screen& set( char ch ) { contents[cursor] = ch; return *this; }
Screen& set( pos row, pos col, char ch )
{ auto ret = check( row, col ); contents[ret] = ch; return *this; }
Screen& move( pos row, pos col ) { cursor = check( row, col ); return *this; }
Screen& display( std::ostream &os ) { do_display(os); return *this; }
const Screen& display( std::ostream &os ) const { do_display(os); return *this; }
private:
pos cursor;
std::string contents;
//辅助函数
pos check( pos row, pos col ) const{//检查下标是否越界。
if( row >= H )
throw std::out_of_range( "invalid row" );
if( col >= W )
throw std::out_of_range( "invalid column" );
return row * W + col;
}
void do_display( std::ostream &os ) const{
for( auto i = 0; i < H; ++i ){
for( auto j = 0; j < W; ++j )
os << contents[ i * W + j ];
os << std::endl;
}
}
};
template
std::ostream& operator<<( std::ostream &os, Screen &scr ){
scr.do_display( os );
return os;
}
template
std::istream& operator>> ( std::istream &is, Screen &scr ){
std::string tmp;
is >> tmp;
s.contents = tmp.substr( 0, H*W );
return is;
}
#endif // SCREEN_H_INCLUDED
类模板的定义
#ifndef VEC_H_INCLUDED
#define VEC_H_INCLUDED
#include
#include
#include
#include
template class Vec{
public:
//构造函数
Vec():
elements(nullptr), first_free(nullptr), cap(nullptr) { }
Vec( std::initializer_list );
Vec( const T &val, std::size_t n );
//拷贝构造函数
Vec( const Vec& );
//拷贝赋值运算符
Vec& operator=( const Vec& );
//移动构造函数
Vec( Vec&& ) noexcept;
//移动赋值运算符
Vec& operator=( Vec&& ) noexcept;
~Vec();
//公共接口
bool empty() const { return elements == first_free; }
std::size_t size() const { return first_free - elements; }
std::size_t capacity() const { return cap - elements; }
T* begin() const { return elements; }
T* end() const { return first_free; }
void push_back( const T& );
void push_back( T&& );
void pop_back();
T& operator[]( std::size_t );
const T& operator[]( std::size_t ) const;
private:
//数据成员
static std::allocator alloc;//空间分配器
T *elements;
T *first_free;
T *cap;
//辅助函数
std::pair
alloc_n_copy( const T*, const T* );
void chk_n_alloc();
void free();
void reallocate();
};
//静态成员的类外定义(默认值初始化)
template std::allocator Vec::alloc;
template
inline
std::pair
Vec::alloc_n_copy( const T *b, const T *e )
{
auto data = alloc.allocate( e - b );
return { data, std::uninitialized_copy( b, e, data ) };
}
template
inline
void Vec::chk_n_alloc(){
if( first_free == cap )
reallocate();
}
template
inline
void Vec::free(){
//不能给deallocate传递一个空指针
if( elements ){
for( auto q = first_free; q != elements; )
alloc.destroy( --q );
alloc.deallocate( elements, cap - elements );
}
}
template
inline
void Vec::reallocate(){
auto newcapacity = size()? 2*size(): 1;
auto first = alloc.allocate( newcapacity );
auto dest = first;
auto elem = elements;
for( size_t i = 0; i != size(); ++i )
alloc.construct( dest++, std::move( *elem++ ) );
free();
elements = first;
first_free = dest;
cap = elements + newcapacity;
}
template
inline
Vec::Vec( std::initializer_list lst){
auto data = alloc_n_copy( lst.begin(), lst.end() );
elements = data.first;
cap = first_free = data.second;
}
template
inline
Vec::Vec( const T &val, std::size_t n ){
auto data = alloc.allocate( n );
elements = data;
std::uninitialized_fill_n( data, n, val );
cap = first_free = elements + n;
}
template
inline
Vec::Vec( const Vec &v ){
auto data = alloc_n_copy( v.begin(), v.end() );
elements = data.first;
cap = first_free = data.second;
}
template
inline
Vec& Vec::operator=( const Vec &v ){
auto newdata = alloc_n_copy( v.begin(), v.end() );
free();
elements = newdata.first;
cap = first_free = newdata.second;
return *this;
}
template
inline
Vec::Vec( Vec &&v ) noexcept:
elements( v.elements ), first_free( v.first_free ), cap( v.cap )
{
std::cout << "move constructor version" << std::endl;
v.elements = v.first_free = v.cap = nullptr;
}
template
inline
Vec& Vec::operator=( Vec &&v ) noexcept{
if( this != &v ){
free();
elements = v.elements;
first_free = v.first_free;
cap = v.cap;
v.elements = v.first_free = v.cap = nullptr;
}
return *this;
}
template
inline
Vec::~Vec(){
free();
}
template
inline
void Vec::push_back( const T &val ){
chk_n_alloc();
alloc.construct( first_free++, val );
}
template
inline
void Vec::push_back( T &&val ){
std::cout << "move push_back version" << std::endl;
chk_n_alloc();
alloc.construct( first_free++, std::move( val ) );
}
template
inline
void Vec::pop_back(){
if( !empty() )
alloc.destroy( --first_free );
}
template
inline
T& Vec::operator[]( std::size_t i ){
return elements[i];
}
template
inline
const T& Vec::operator[]( std::size_t i ) const{
return elements[i];
}
#endif // VEC_H_INCLUDED
#include
#include
#include"Vec.h"
using namespace std;
int main()
{
Vec svec;
Vec ivec( { 0, 1, 2, 3 } );
Vec ivec2( 0, 10 );
Vec useless = Vec( { 6, 6, 6 } );
Vec ivec3( ivec2 );
Vec ivec4( std::move( useless ) );
string tmp = "you";
svec.push_back( "fuck" );
svec.push_back( tmp );
ivec.pop_back();
for( size_t i = 0; i != ivec2.size(); ++i )
cout << ivec2[i] << " ";
cout << endl;
for( const auto &s : svec )
cout << s << " ";
cout << endl;
for( const auto i : ivec )
cout << i << " ";
cout << endl;
for( const auto i : ivec3 )
cout << i << " ";
cout << endl;
for( const auto i : ivec4 )
cout << i << " ";
cout << endl;
return 0;
}
练习16.17:声明为typename的类型参数和声明为class的类型参数有什么不同?什么时候必须使用typename?
在模板参数列表中,typename和class两个关键字的含义相同,都是表明模板参数是一个类型。
但是typename还有其他用途,当在模板类型参数上使用作用域运算符::来访问成员时,如T::value_type。默认下,C++语言假定通过作用域运算符访问的名字不是类型。因此如果我们希望使用一个模板类型参数的类型成员,就必须通过使用关键字typename显示告诉编译器该名字是一个类型。
练习16.18:解释下面每个函数模板声明并指出他们是否非法。更正你发现的每个错误。
(a) template
template
(b) template
编译器无法推断T的类型。而且函数参数部分变量名称T和模板参数T重名。
template
(c) inline template
模板定义时才能指定inline。
(d) template
没有返回类型
template
(e) typedef char Ctype;
template
合法。
返回类型处的Ctype是外层的char,而函数形参处的Ctype是模板参数
练习16.19:编写函数,接受一个容器的引用,打印容器中的元素。使用容器的size_type和size成员来控制打印元素的循环。
以下是我的程序。设计的时候,我的想法是:所实行的操作必须适用于所有标准容器,那么我就不能够使用下标运算符,因为list这些不是连续存储的容器不支持随机访问。所以我想到了用迭代器。但是我的设计有些浪费,因为我用了变量i来控制循环,但是实际上打印元素时是借助了另一个迭代器变量,而没有用i。
#include
#include
#include
template
void print( std::ostream &os, const container &con ){
auto iter = con.begin();
for( typename container::size_type i = 0; i != con.size(); ++i, ++iter )
os << *( iter ) << " ";
os << std::endl;
}
using namespace std;
int main()
{
vector ivec( { 0, 1, 2, 3, 4, 5, 6, 7 } );
list lst = { "i", "wanna", "feel" };
print( cout, ivec );
print( cout, lst );
return 0;
}
练习16.20:重写上一题的函数,使用begin和end返回的迭代器来控制循环。
#include
#include
#include
#include
template
void print( std::ostream &os, const container &con ){
for( auto iter = con.begin(); iter != con.end(); ++iter )
os << *( iter ) << " ";
os << std::endl;
}
using namespace std;
int main()
{
vector ivec( { 0, 1, 2, 3, 4, 5, 6, 7 } );
list lst = { "i", "wanna", "feel" };
set sset;
sset.insert( lst.begin(), lst.end() );
print( cout, ivec );
print( cout, lst );
print( cout, sset );
return 0;
}
练习16.22:修改12.3节中你的TextQuery程序,令shared_ptr成员使用DebugDelete作为它们的删除器。
练习16.23:预测在你的查询主程序中何时会执行调用运算符。如果你的预测和实际不符,确认你理解了原因。
以上三题略了。
练习16.24:为你的Blob模板添加一个构造函数,它接受两个迭代器。
这个之前就已经实现了。详见练习16.12。
练习16.25:解释下面这些声明的含义:
extern template class vector
实例化声明。当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。extern承诺在程序的其他文件有该实例化的一个非extern声明(定义)。
template class vector
用Sales_data实例化vector,在其他文件可以用extern声明此实例化,使用此定义。
练习16.26:假设NoDefault是一个没有默认构造函数的类,我们可以显式实例化vector
不可以显式实例化vector
当我们显式实例化vector
练习16.27:对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模板被实例化,解释为什么;如果未实例化,解释为什么没有。
template class Stack{ };
void f1( Stack ); //(a)
class Exercise{
Stack &rsd; //(b)
Stack si; //(c)
};
int main(){
Stack *sc; //(d)
f1( *sc ); //(e)
int iObj = sizeof( Stack ); //(f)
}
练习16.28:编写你自己版本的shared_ptr和unique_ptr。
本节的内容介绍了shared_ptr和unique_ptr处理删除器的不同方式。但是我还是想不出shared_ptr是如何保存类型能够随时改变的删除器,或者说shared_ptr怎么间接访问能够随时改变类型的删除器。
我参考了标准答案。。。发现它并没有实现能够重载删除器的功能。
所以,这题先不实现删除器,待以后再实现。
#ifndef MYSHARED_PTR_H_INCLUDED
#define MYSHARED_PTR_H_INCLUDED
template class myShared_ptr{
public:
myShared_ptr():
p(nullptr), use(nullptr) { }
explicit myShared_ptr( T *pt ):
p( pt ), use( new size_t( 1 ) ) { }
myShared_ptr( const myShared_ptr &sp ):
p( sp.p ), use( sp.use ) { if( use ) ++*use; }
myShared_ptr& operator=( const myShared_ptr& );
~myShared_ptr();
//公共接口
T& operator*() { return *p; }
T& operator*() const { return *p; }
private:
T *p;//指针
std::size_t *use; //保存共享此对象的指针数目
}
template
inline
myShared_ptr::~myShared_ptr(){
//注意要记得处理空指针的情况
if( use && --*use == 0 ){
delete p;
delete use;
}
}
template
inline
myShared_ptr myShared_ptr::operator=( const myShared_ptr &rhs ){
//注意要处理空指针的情况,不能够解引用一个空指针
if( rhs.use )
++*( rhs.use );
if( use && --*use == 0 ){
delete p;
delete use;
}
p = rhs.p;
use = rhs.use;
return *this;
}
#endif // MYSHARED_PTR_H_INCLUDED
练习16.29:修改你的Blob类,用你自己的shared_ptr代替标准库中的版本。
练习16.30:重新运行你的一些程序,验证你的shared_ptr类和修改后的Blob类。
(注意:weak_ptr类型超出了本书的范围,因此你不能将BlobPtr类与你修改后的Blob一起使用)
练习16.31:如果我们将DebugDelete与unique_ptr一起使用,解释编译器将删除其处理为内联形式的可能方式。
练习16.32:在模板实参推断过程中发生了什么?
对于一个函数模板,当我们调用它时,编译器会根据函数的实参类型来推断模板参数,这些模板实参实例化出的版本与我们的函数调用应该是最匹配的版本。
练习16.33:指出在模板实参推断过程中允许对函数实参进行的两种类型转换。
(1) const转换:可以将一个非const对象的引用或指针传递给一个const对象的引用或指针形参。
(2)数组和函数指针转换:如果函数参数不是引用类型,那么数组和函数类型的实参会转换成指针类型。
练习16.34:对下面的代码解释每个调用是否合法。如果合法,T的类型是什么?如果不合法,为什么?
template
(a) compare( "hi", "world" );
不合法。"hi"的类型是const char[3], "world"的类型是const char[6],两个形参的类型不相同
(b) compare( "bye", "dad" );
合法。两个实参的类型都是const char[4]。能够调用compare
(PS:标准答案里说以上两个调用都不合法,我已经试验过,(b)是可以通过编译的 )
练习16.35:下面调用中哪些是错误的(如果有的话)?如果调用合法,T的类型是什么?如果调用不合法,问题何在?
template
template
double d; float f; char c;
(a) calc( c, 'c' );
合法。T的类型是char,'c'类型转换成int传递给形参
(b)calc(d, f );
合法。T的类型是double, f类型转换成int传递给形参
(c)fcn( c, 'c' );
合法。T的类型是char。
(d)fcn( d, f );
非法。两个实参的类型不一致。
练习16.36:进行下面的调用会发生什么?
template f1( T, T );
template f2( T1, T2 );
int i = 0, j = 42, *p1 = &i, *p2 = &j;
const int *cp1 = &i, *cp2 = &j;
(a)f1( p1, p2 );
合法。T 的类型为int*
(b)f2( p1, p2 );
合法。T1 T2的类型都是int*
(c)f1( cp1, cp2 );
合法。T的类型为const int *
(d)f2( cp1, cp2 );
合法。T1 T2的类型都是const int*
(e)f1( p1, cp1 );
合法。T的类型是const int* //(p1先进行类型转换再传递给第一个形参)
非法。T的类型无论推断为int*还是const int*都无法匹配调用。
(f)f2( p1, cp1 );
合法。T1的类型是int*, T2的类型是const int*
练习16.37:标准库max函数有两个参数,它返回实参中的较大者。此函数有一个模板类型参数。你能在调用max时传递给它一个int和一个double吗?如果可以,如何做?如果不可以,为什么?
不可以。无法匹配参数而导致编译器报错。
因为max函数的两个形参都是同一个模板参数。
练习16.38:当我们调用make_shared时,必须提供一个显式模板实参。解释为什么需要显式模板实参以及它是如何使用的?
在调用make_shared时,有时不给出参数,表示进行值初始化。有时给出的参数与维护的动态对象的类型不直接相关,如make_shared
练习16.39:对16.1.1节中的原始版本的compare函数,使用一个显式模板实参,使得可以向函数传递两个字符串字面常量。
compare
练习16.40:下面的函数是否合法?如果不合法,为什么?如果合法,对可以传递的实参类型有什么限制(如果有的话)?返回类型是什么?
template
auto fcn3( It beg, It end ) -> decltype( *beg + 0 )
{
//处理序列
return *beg;
}
函数合法。
但有两个问题:
序列元素类型必须支持+运算符
*beg + 0 是右值,因此fcn3的返回类型被推断为元素类型的常量引用。
练习16.41:编写一个新的sum版本,它的返回类型保证足够大,足以容纳加法结果。
略了。
练习16.42:对下面的每个调用,确定T和val的类型。
--------------------------------------------------------------------------------------------------------------------------------------------------------
当我们将一个①左值传递给函数的②右值引用参数,且此③右值引用指向模板参数类型时,编译器推断模板类型参数为实参的左值引用类型。
--------------------------------------------------------------------------------------------------------------------------------------------------------
template
int i = 0; const int ci = i;
(a) g( i );
T的类型是int&, val的类型是int&
(b) g( ci );
T的类型是 const int&, val的类型是 const int&
(c) g( i * ci );
T的类型是int, val的类型是int&&
练习16.43:使用上一题定义的函数,如果我们调用g( i = ci ),g的模板参数将是什么?
因为赋值运算符的返回类型是左值的引用,所以g的模板参数将是int&。
练习16.44:使用与第一题中相同的三个调用,如果g的函数声明为T( 而不是T&& ),确定T的类型。如果g的函数参数是const T&呢?
如果函数参数是T:
那么,对于:
(a)T的类型是int,val的类型是int。
(b)T的类型是int, val的类型是int。顶层const无论在形参还是实参中都会被忽略。
(c)T的类型是int&&, val的类型是int&&
T的类型是int,val 的类型是int
如果函数参数是const T&
那么,对于:
(a)T的类型是int, val的类型是const int& 实现了非const引用向const引用的转换
(b)T的类型是int,val的类型是const int&
(c)T的类型是int,val的类型是const int& 右值可以传递给const的左值引用
练习16.45:给定下面的模板,如果我们对一个像42这样的字面常量调用g,解释会发生什么?如果我们对一个int类型的变量调用g呢?
template
如果是42:
T的类型将被推断为int,定义一个变量名为v的vector
如果是对一个int类型的变量:
T的类型将被推断为int&,本意是:定义一个变量名为v的vector
vector的元素不能是引用(管理动态内存保存元素,必须维护指向动态内存的指针,但是引用不是对象,引用没有实际的地址,无法定义指向引用的指针)。所以,编译会失败。
练习16.46:解释下面的循环,它来自13.5节中的StrVec::reallocate:
for( size_t i = 0; i != size(); ++i )
alloc.construct( dest++, std::move( *elem++ ) );
循环的每个单步详细步骤:
elem指向下一个位置但是返回原位置的值,解引用原位置的值,通过move函数将解引用后的值转换为右值引用类型,construct成员函数将对这个右值引用值将调用它的类型的构造函数构造一个元素,dest指向这个构造出来的元素,然后dest指向下一个位置。
整个循环就是:
将数据从旧的内存空间移动到新的内存空间。
练习16.47:编写你自己版本的翻转函数,通过接受左值引用和右值引用参数的函数来测试它。
#include
#include
template
void flip( Func f, T1 &&t1, T2 &&t2 ){
f( std::forward( t2 ), std::forward( t1 ) );
}
void f( int v1, int &v2 )
{
std::cout << v1 << " " << ++v2 << std::endl;
}
void g( int &&i, int &j )
{
std::cout << i << " " << j << std::endl;
}
int main()
{
int i1 = 0,i2 = 0;
int j = 1;
f( 42, i1 );
flip( f, i2, 42 );
std::cout << i1 << " " << i2 << std::endl;
flip( g, j, 42 );
return 0;
}
练习16.48:编写你自己版本的debug_rep函数。
#include
#include
#include
template
std::string debug_rep( const T &t ){
std::ostringstream ret;
ret << t;
return ret.str();
}
template
std::string debug_rep( T *p ){
std::ostringstream ret;
ret << "pointer: " << p;
if( p )
ret << " " << debug_rep( *p );
else
ret << " null pointer";
return ret.str();
}
//为非模板函数提供声明
template
std::string debug_rep( const std::string& );
//非模板函数
std::string debug_rep( char *p ){
return debug_rep( std::string(p) );
}
std::string debug_rep( const char *p ){
return debug_rep( std::string(p) );
}
int main()
{
int i = 0, *aPtr = &i;
const char *cp = "hello";
std::cout << debug_rep( i ) << std::endl;
std::cout << debug_rep( aPtr ) << std::endl;
std::cout << debug_rep( cp ) << std::endl;
return 0;
}
template
template
template
template
int i = 42, *p = &i;
const int ci = 0, *p2 = &ci;
g(42); f(42);
调用g( T ), T推断为int;
调用f( T ), T推断为int
g(p); f( p );
调用g( T* )。T推断为int;
①f(T). T推断为int*。不需要类型转换
②f( const T* ). T推断为int, 需要进行非指向const的指针向指向const的指针的转换
所以调用的是f(T)
g(ci); f( ci );
调用的是g( T )。T推断为int。顶层const 被忽略。
调用的是f( T )。T推断为int。顶层const 被忽略。
g( p2 );f( p2 );
调用的是g( T* )。T推断为const int。
调用的是f( const T*)。 T推断为int
练习16.50:定义上一个练习中的函数,令他们打印一条身份信息。运行该练习中的代码。如果函数调用的行为与你的预期不符,确定你理解了原因。
略了。
练习16.51:调用本节中的每个foo,确定sizeof...( Args)和sizeof...( rest)分别返回什么?
ps:当我们需要知道包中有多少元素时,可以使用sizeof...运算符。
练习16.52:编写一个程序验证上一题的答案。
我验证了书中的foo调用 外加一个包部分实参类型相同的foo调用。
#include
#include
template
void foo( const T &t, const Args &...rest ){
std::cout << sizeof...( Args ) << std::endl;
std::cout << sizeof...( rest ) << std:: endl;
}
int main()
{
int i = 0, j = 1;
double d = 3.14;
std::string s = "hello";
foo( i, s, 42, d );
std::cout << std::endl;
foo( s, 42, "hi" );
std::cout << std::endl;
foo( d, s );
std::cout << std::endl;
foo( "hi" );
std::cout << std::endl;
foo( s, i, j );
return 0;
}
#include
#include
template
std::ostream& print( std::ostream &os, const T &t ){
return os << t;
}
template
std::ostream& print( std::ostream &os, const T &t, const Args &...rest ){
os << t << " ,";
return print( os, rest... );
}
int main()
{
int i = 0;
double d = 3.14;
std::string s = "bitch";
const char *cp = "hi";
print( std::cout, 42 ) << std::endl;
print( std::cout, 42, "hello" ) << std::endl;
print( std::cout, i, d, s, cp, "world" ) << std::endl;
return 0;
}
练习16.54:如果我们对一个没有<<运算符的类型调用print,会发生什么?
编译会不通过。因为print需要类型支持<<运算符。
练习16.55:如果我们的可变参数版本print的定义之后声明非可变参数版本,解释可变参数的版本会如何执行。
如果在可变参数版本的print的定义之后声明非可变参数版本(递归的结束条件),那么非可变参数版本的print函数将在可变参数版本的print的定义中是不可见的,所以可变参数版本会在只剩两个实参的情况下继续递归调用自身,导致无限递归循环。
练习16.56:编写并测试可变参数版本的errorMsg。
#include
#include
#include
template
std::string debug_rep( const T &t ){
std::ostringstream ret;
ret << t;
return ret.str();
}
template
std::string debug_rep( T *p ){
std::ostringstream ret;
ret << "pointer: " << p;
if( p )
ret << " " << debug_rep( *p );
else
ret << " null pointer";
return ret.str();
}
//为非模板函数提供声明
template
std::string debug_rep( const std::string& );
//非模板函数
std::string debug_rep( char *p ){
return debug_rep( std::string(p) );
}
std::string debug_rep( const char *p ){
return debug_rep( std::string(p) );
}
template
std::ostream& print( std::ostream &os, const T &t ){
return os << t;
}
template
std::ostream& print( std::ostream &os, const T &t, const Args &...rest ){
os << t << ", ";
return print( os, rest... );
}
template
std::ostream& errorMsg( std::ostream &os, const Args &...rest ){
return print( os, debug_rep( rest )... );
}
int main()
{
int i = 6, *aPtr = &i;
const char *cp = "Mr.";
std::string str = "tree";
errorMsg( std::cerr, aPtr, "hello", cp, str );
return 0;
}
练习16.57:比较你的可变参数版本的errorMsg和6.2.6节中的err_msg函数。两种方法的优点和缺点各是什么?
err_msg只能够接受string类型的实参或者能转换成string类型的实参,
errorMsg能够接受多个任何不同类型的实参。但是errorMsg的编写相对繁琐。
练习16.58:为你的StrVec类及你为16.1.2节练习中编写的Vec类添加emplace_back函数。
template
template
inline
void Vec::emplace_back( Args&& ...args ){
chk_n_alloc();
alloc.construct( first_free++, std::forward(args)... );
}
由于s是一个左值,经过包扩展std::forward
练习16.60:解释make_shared是如何工作的?
make_shared接受一个显式的模板实参(如:T)指明它指向的类型,然后函数参数部分接受可变参数(args),它将这些可变参数传递给它指向的类型T的构造函数构造一个对象。然后构造一个shared_ptr对象指向构造的T类型对象。
练习16.61:定义你自己版本的make_shared。
template
myShared_ptr make_SP( Args&& args...){
return myShared_ptr( new T( std::forward( args )...) );
}
#ifndef SALES_DATA_H_INCLUDED
#define SALES_DATA_H_INCLUDED
#include
class Sales_data{
//友元函数
friend struct std::hash;
friend std::istream& operator >> ( std::istream&, Sales_data& );
friend std::ostream& operator << ( std::ostream&, const Sales_data& );
friend bool operator < ( const Sales_data&, const Sales_data& );
friend bool operator == ( const Sales_data&, const Sales_data& );
friend Sales_data add( Sales_data &rhs, Sales_data &lhs );
friend std::istream& read( std::istream& in, Sales_data &item );
friend std::ostream& print( std::ostream &out, const Sales_data &rhs );
public: //构造函数
/* Sales_data( const std::string &book, const unsigned units, const double sellingprice, const double saleprice ) :
bookNo( book ), units_sold( units ), selling_price( sellingprice ), sale_price( saleprice )
{
if( selling_price )
discount = sale_price / selling_price;
std::cout << "四个参数的构造函数的函数体" << std::endl;
}
*/
//委托构造函数:
/* Sales_data(): Sales_data( "", 0, 0, 0 ) { std::cout << "无参数的构造函数的函数体" << std::endl; }
Sales_data( const std::string &s ) : Sales_data( s, 0, 0, 0 ) { std::cout << "接受字符串的构造函数的函数体" << std::endl; };
Sales_data( std::istream &is ) : Sales_data() { read( is, *this ); std::cout << "接受输入流的构造函数的函数体" << std::endl; }
*/
Sales_data() = default;
Sales_data( const std::string &book ): bookNo( book ) { }
Sales_data( const std::string &book, const unsigned units, const double sellingprice, const double saleprice );
Sales_data( std::istream &is ) { is >> *this; }
public:
Sales_data& operator += ( const Sales_data& );
std::string isbn() const { return bookNo; }
Sales_data& combine( const Sales_data &rhs );
private:
std::string bookNo;
unsigned units_sold = 0;
double selling_price = 0.0;
double sale_price = 0.0;
double discount = 0.0;
};
inline bool compareIsbn( const Sales_data&lhs, const Sales_data&rhs )
{
return lhs.isbn() == rhs.isbn();
}
Sales_data operator + ( const Sales_data&, const Sales_data& );//声明
//友元函数定义
inline bool operator == ( const Sales_data &lhs, const Sales_data &rhs )
{
return lhs.units_sold == rhs.units_sold && lhs.sale_price == rhs.sale_price &&
lhs.isbn() == rhs.isbn();
}
inline bool operator != ( const Sales_data &lhs, const Sales_data &rhs )
{
return !( lhs == rhs );
}
inline bool operator<( const Sales_data &lhs, const Sales_data &rhs )
{
return lhs.bookNo < rhs.bookNo;
}
inline Sales_data add( Sales_data &rhs, Sales_data &lhs )
{
Sales_data sum = rhs;
sum.combine( lhs );
return sum;
}
inline std::ostream& print( std::ostream &out, const Sales_data &rhs )
{
out << rhs.bookNo << " " << rhs.units_sold << " " << rhs.selling_price << " "
<< rhs.sale_price << " " << rhs.discount;
return out;
}
inline std::istream& read( std::istream& in, Sales_data &item )
{
in >> item.bookNo >> item.units_sold >> item.selling_price >> item.sale_price;
return in;
}
//构造函数
Sales_data::Sales_data( const std::string &book, unsigned units, double sellingprice, double saleprice )
{
bookNo = book;
units_sold = units;
selling_price = sellingprice;
sale_price = saleprice;
if( selling_price != 0 )
discount = sale_price / selling_price;
}
//成员函数
Sales_data& Sales_data::operator += ( const Sales_data &rhs )
{
sale_price = ( rhs.sale_price * rhs.units_sold + sale_price * units_sold )
/( rhs.units_sold + units_sold );
selling_price = ( rhs.selling_price * rhs.units_sold + selling_price * units_sold )
/( rhs.units_sold + units_sold );
units_sold += rhs.units_sold;
if( sale_price != 0 )
discount = sale_price / selling_price;
return *this;
}
Sales_data operator + ( const Sales_data &lhs, const Sales_data &rhs )
{
Sales_data tmp( lhs );
tmp += rhs;
return tmp;
}
std::istream& operator >> ( std::istream &in, Sales_data &s )
{
in >> s.bookNo >> s.units_sold >> s.selling_price >> s.sale_price;
if( in && s.selling_price != 0 )
s.discount = s.sale_price / s.selling_price;
else
s = Sales_data();
return in;
}
std::ostream& operator << ( std::ostream &out, const Sales_data &s )
{
out << s.isbn() << " " << s.units_sold << " "
<< s.selling_price << " " << s.sale_price << " " << s.discount;
return out;
}
Sales_data& Sales_data::combine( const Sales_data &rhs )
{
units_sold += rhs.units_sold;
sale_price = ( units_sold * sale_price + rhs.units_sold * rhs.sale_price )
/ ( units_sold + rhs.units_sold );
if( selling_price != 0 )
discount = sale_price / selling_price;
return *this;
}
namespace std{
template <> struct hash{
typedef size_t result_type;
typedef Sales_data argument_type;
size_t operator()( const Sales_data & s ) const;
};
size_t hash::operator() ( const Sales_data &s ) const{
return hash()( s.bookNo ) ^
hash() ( s.units_sold ) ^
hash() ( s.selling_price ) ^
hash() ( s.sale_price );
}
}
#endif // SALES_DATA_H_INCLUDED
#include
#include
#include"Sales_data.h"
using namespace std;
int main()
{
unordered_multiset SDset;
Sales_data obj1( "C++Primer", 50, 128, 68 );
Sales_data obj2( "C primer plus", 40, 98, 52 );
Sales_data obj3( obj1 );
SDset.insert( obj1 );
SDset.insert( obj2 );
SDset.insert( obj3 );
for( const auto &sd : SDset )
cout << sd << endl;
return 0;
}
/*
练习16.63:定义一个函数模板,统计一个定值在一个vector中出现的次数。测试你的函数,分别传递给它一个double的vector,一个int的vector以及一个string 的vector。
先略了。
练习16.64:为上一题中的模板编写特例化版本来处理vector
略了。
练习16.65:在16.3节中我们定义了两个重载的debug_rep版本,一个接受const char*参数,一个接受char*参数。将这两个函数重写为特例化版本。
*/