
2012年5月24日
最近要用C++开发项目需要操作memcached,查找到libmemcached是专门为C/C++提供的memcached客户端决定用它。
1. 在命令行下: wget https://launchpad.net/libmemcached/1.0/1.0.7/+download/libmemcached-1.0.7.tar.gz 下载文件。
2. 输入:sudo apt-get install libevent-dev 安装libevent-dev (据说这个很重要,不然libmemcached就不能运行,最好安装)
3. 解压缩下载文件:sudu tar -zxvf libmemcached-1.0.7.tar.gz
4. 编译: ./configure
5. 输入:make
这里很可能会出下面错误:
libmemcached/backtrace.cc: In function 'void custom_backtrace()':
libmemcached/backtrace.cc:57:6: sorry, unimplemented: Graphite loop optimizations can only be used if the libcloog-ppl0 package is installed
解决办法是修改Makefile,查找并去掉 floop-parallelize-all(应该有两处需要去掉),看了网上资料说是去掉后表示不使用Graphite loop 优化。
成功编译。
6. 安装:make install
7. 编写文件 MemCachedClient.h 输入下面内容:
#ifndef MEMCACHEDCLIENT
#define MEMCACHEDCLIENT
#include <libmemcached/memcached.h>
#include<iostream>
#include<string>
#include<time.h>
using std::string;
using std::cout;
using std::endl;
class MemCachedClient
{
public:
~MemCachedClient()
{
memcached_free(memc);
};
MemCachedClient()
{
memcached_return rc;
memcached_server_st *server = NULL;
memc = memcached_create(NULL);
server =memcached_server_list_append(server, "192.168.45.144", 11211, &rc);
rc=memcached_server_push(memc,server);
if (MEMCACHED_SUCCESS != rc)
{
cout <<"memcached_server_push failed! rc: " << rc << endl;
}
memcached_server_list_free(server);
};
int Insert(const char* key, const char* value,time_t expiration = 3)
{
if (NULL == key || NULL == value)
{
return -1;
}
uint32_t flags = 0;
memcached_return rc;
rc = memcached_set(memc, key, strlen(key),value, strlen(value), expiration, flags);
// insert ok
if (MEMCACHED_SUCCESS == rc)
{
return 1;
}
else
{
return 0;
}
};
string Get(const char* key)
{
if (NULL == key)
{
return "";
}
uint32_t flags = 0;
memcached_return rc;
size_t value_length;
char* value = memcached_get(memc, key, strlen(key), &value_length, &flags, &rc);
// get ok
if(rc == MEMCACHED_SUCCESS)
{
return value;
}
return "";
};
private:
memcached_st* memc;
};
#endif
再编写 test.cc 文件
#include<iostream>
#include"MemCachedClient.h"
using std::cout;
using std::endl;
int main()
{
MemCachedClient mc;
mc.Insert("kingcat","value123");
cout << mc.Get("kingcat") << endl;
return 1;
};
8. 用g++编译: g++ test.cc -o test -lmemcached (一定别把库链接忘了否则编译不通过)
9. 执行./test
执行时会报错:libmemcached.so.10: cannot open shared object file: No such file or director
原因是 libmemcached.so.10 被安装到了 /usr/local/lib下,而共享库默认位置是 /usr/lib 网上说可以把目录 /usr/local/lib 设置到环境变量,但我设置完后不起作用。
于是用建立一个链接: ln /usr/local/lib/libmemcached.so.10 /usr/lib/libmemcached.so.10
重新执行,顺利通过!
posted @ 2012-05-24 16:44 老金 阅读(37) 评论(0)
编辑

2012年5月23日
16.1 模板定义
模板和c#范型一样,建立一个通用的类或函数,其参数类型和返回类型不具体指定,用一个虚拟的类型来代表,通过模板化函数或类实现代码在的重用。
定义语法是:
template<typename 类型参数>
返回类型 函数名(模板形参表)
{
函数体
}
或 :
template<class 类型参数>
返回类型 函数名(模板形参表)
{
函数体
}
template是一个声明模板的关键字,类型参数一般用T这样的标识符来代表一个虚拟的类型,当使用函数模板时,会将类型参数具体化。typename和class关键字作用都是用来表示它们之后的参数是一个类型的参数。只不过class是早期C++版本中所使用的,后来为了不与类产生混淆,所以增加个关键字typename。
函数模板:
template <typename T> //加法函数模板
T Add(T x,T y)
{
return x+y;
};
int main()
{
int x=10,y=10;
std::cout<<Add(x,y)<<std::endl;//相当于调用函数int Add(int,int)
double x1=10.10,y1=10.10;
std::cout<<Add(x1,y1)<<std::endl;//相当于调用函数double Add(double,double)
long x2=9999,y2=9999;
std::cout<<Add(x2,y2)<<std::endl;//相当于调用函数long Add(long,long)
}
template内可以定义多个类型形参,每个形参用,分割并且所有类型前面都要用typename修饰。
template <typename T,typename Y> T Add(T x,Y y) ; // ok
template <typename T,Y> T Add(T x,Y y) ; // 错误,Y之前缺少修饰符
函数模板也可以声明inline 语法是 template <typename T,typename Y> inline T Add(T x,Y y) ;
类模板:
template <typename T,typename Y>
class base
{
public:
base(T a);
Y Get();
private:
T s1
T s2
};
int main()
{
base<int,string> it(1,"name"); // 类后面的类型参数不能缺省
}
和函数模板不一样,类模板无法使用类型推断,所以定义对象时一定要显示传递类型参数。
类型形参名称有自己的作用域:
typedef stirng T; // 该T与下面的类型形参不会产生冲突,不过最好不要重名以免混淆
template <typename T>
T Add(T x,T y)
{
typedef stirng T; // 错误,内部定义会产生名字冲突
//...
};
可以像申明一般函数或类一样声明(而不定义)。但类型形参不能省略 template <typename T,typename Y> class base ; 声明了一个类模板。
模板类型参数可以用typename 或者class 来修饰,大部分情况下二者可以互换。但有一种特殊用方法时需要typename
class base
{
public:
class inbase{}; // 内部类
};
template <typename T>
void test()
{
typename T::inbase p; // 这时候必须要在前面加上typename,表示要定义一个类型为T类(T是类型参数)内部定义的inbase类对象
T::inbase p; // 如果不加编译会报错,因为编译器认为T::inbase表示T类的静态成员inbase,所以这样书写语法是错误的
}
要注意,这种用法需要满足两个条件:拥有内部类的类base被潜在的认定为类型形参(如果类型形参T没有内部类inbase定义同样会编译错误),使用类型形参的内部类定义对象typename T::inbase p。
模板编程中还可以在类型形参列表中定义非类型形参,这时非类型形参会被当成常量
template <typename T,int i>
T Add(T x) // Add(T x,int i) 这样定义编译错误,i 和非形参i名称冲突
{
return x + i;
};
int main()
{
Add<int,10>(5);
}
范型编程有两个重要原则:形参尽量使用const引用(防止拷贝),形参本身操作尽量少(传递一个不支持函数形参体操作的类型会报错)
16.2 实例化
函数模板可以定义函数指针并予以赋值
template <typename T,typename Y> T Get(T x,Y y) ; // 声明函数
int(*pr) (int,string) = Get ; // 定义函数指针并赋值
pr(5,"str") ; // 用函数指针调用函数无需解引,或者(*pr)(5,"str") ;
函数模板指针作为形参时需注意重载情况。对二义性的调用要指定类型来消除
template <typename T> T Get(T x) ; // 声明函数
void fun(int (*) (int));
void fun(string (*) (string));
fun(Get); // 错误,有二义性,类型推断后重载的两个fun函数都能通过。
fun(Get<int>); // 指定类型,消除了二义性
16.3 模板编译模型
[1] 当编译器看到模板定义的时候,它不立即产生代码。 只有在看到用到模板时 ,如调用了函数模板或定义了类模板的对象的时候,编译器才产生特定类型的模板实例 。
[2] 一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。
[3] 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。 当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要哪些通常放在源文件中的代码。
[4] 标准C++为编译模板代码定义了两种模型。 所有编译器都支持第一种模型,称为“包含”模型( inclusion compilation model) ;只有一些编译器支持第二种模型,“分别编译”模型( separate compilation model) 。
[5] 在两种模型中,构造程序的方式很大程度上是相同的:类定义和函数声明放在头文件中,而函数定义和成员定义放在源文件中。两种模型的不同在于,编译器怎样使用来自源文件的定义 。
[6] 在包含编译模型,编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条#include指示使定义可用,该#include引入了包含相关定义的源文件 。
[7] 在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,可以使用export关键字来做这件事 。export关键字能够指明给定的定义可能会需要在其他文件中产生实例化 。
[8] 在一个程序中,一个模板只能定义为导出一次。 一般我们在函数模板的定义中指明函数模板为导出的 ,这是通过在关键字template之前包含export关键字而实现的。对类模板使用export更复杂一些 ,记得应该在类的实现文件中使用export,否者如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。
[9] 导出类的成员将自动声明为导出的。也可以将类模板的个别成员声明为导出的,在这种情况下,关键字export不在类模板本身指定,而是只在被导出的特定成员定义上指定。任意非导出成员的定义必须像在包含模型中一样对待:定义应放在定义类模板的头文件中。
16.4 类模板成员
普通类不但定义非模板函数成员,也能定义模板函数成员:
class base
{
public:
template<typename T> T Get(T a); // 模板函数成员申明
};
template<typename T> T base::Get(T a) //成员函数类外部定义
{
return a;
}
可这样调用:
base obj ;
obj.Get<int>(20) ;
obj.Get("str") ; // 类型推断,等价于obj.Get<string>("str") ;
如果是模板类
template<typename T>
class base
{
public:
template<typename Y> Y Get(Y a); // 模板函数成员申明
};
template<typename T> // 这一步不可少,确定T也是个模板类型参数
template<typename Y> Y base<T>::Get(Y a)
{
return a;
}
可这样调用:
base<string> obj ;
obj.Get<int>(20) ;
obj.Get("str") ; // 类型推断,等价于obj.Get<string>("str") ;
类模板或函数模板可以作为其他类的友元,不过由于其特殊性可以做一些限制。
template<typename T>
class he
{
// ...
}
template<typename T>
class base
{
template<typename Y> friend class he; // 表示所有类型的模板类对象都是友元
friend class he<int>; // 表示只有int类型形参的模板类对象才是友元
friend class he<T>; // 表示只有类型形参和base类型参数一致的模板类对象才是友元
}
友元函数和模板类情况相似。 第一种友元可以看做是完全申明,第二种和第三种友元则需要至少在base定以前有完全申明,否则会编译错误。
16.5 一个范型句柄类
如果对上一章句柄类有充分理解范型句柄类应该非常容易掌握。
16.6 模板特化
模板的特化(template specialization)分为两类:函数模板的特化和类模板的特化。
函数模板的特化:当函数模板需要对某些类型进行特别处理,称为函数模板的特化。例如:
bool IsEqual(T t1, T t2)
{
return t1 == t2;
};
int main()
{
char str1[] = "Hello";
char str2[] = "Hello";
cout << IsEqual(1, 1) << endl;
cout << IsEqual(str1, str2) << endl; //输出0
return 0;
}
最后一行比较字符串是否相等。由于对于传入的参数是char *类型的,IsEqual函数模板只是简单的比较了传入参数的值,即两个指针是否相等,因此这里打印0。显然,这与我们的初衷不符。因此,sEqual函数模板需要对char *类型进行特别处理,即特化:
template <> bool IsEqual(char* t1, char* t2) // 函数模板特化
{
return strcmp(t1, t2) == 0;
}
这样,当IsEqual函数的参数类型为char* 时,就会调用IsEqual特化的版本,而不会再由函数模板实例化。
类模板的特化:与函数模板类似,当类模板内需要对某些类型进行特别处理时,使用类模板的特化。例如:
template <class T>
class compare
{
public:
bool IsEqual(T t1, T t2)
{
return t1 == t2;
}
};
int main()
{
char str1[] = "Hello";
char str2[] = "Hello";
compare<int> c1;
compare<char *> c2;
cout << c1.IsEqual(1, 1) << endl; //比较两个int类型的参数
cout << c2.IsEqual(str1, str2) << endl; //比较两个char *类型的参数
return 0;
}
这里最后一行也是调用模板类compare<char*>的IsEqual进行两个字符串比较,显然这里存在的问题和上面函数模板中的一样,我们需要比较两个字符串的内容,而
不是仅仅比较两个字符指针。因此,需要使用类模板的特化:
template<>class compare<char *> //特化(char*)
{
public:
bool IsEqual(char* t1, char* t2)
{
return strcmp(t1, t2) == 0; //使用strcmp比较字符串
}
};
注意:进行类模板的特化时,需要特化所有的成员变量及成员函数。
posted @ 2012-05-23 16:14 老金 阅读(4) 评论(0)
编辑

2012年5月22日
15.1 面向对象编程:概述
继承:
虚函数:virtual
动态绑定:
15.2 定义基类和派生类
成员限制符:public private protected
protected:在子类中可访问,派生类内部可以访问本类对象protected成员,不能访问基类对象protected成员
class base{ protected: string name;};
class item : public base
{
void test(item &a , base &b)
{
a.name; // 可以访问本类对象protected成员
b.name; // 错误,不能访问基类对象protected成员
}
}
C++允许多重继承,例如 class item : public base1, base2...
原则上子类重写父类虚函数时声明和定义要于父类完全一致,但有一个例外:虚函数返回值是父类的指针或引用 可以在子类中将返回改成子类的指针或引用:比如父类有虚函数:base *test(); 子类可重写成: item *test();
声明一个包含派生列表的类(而不实现)是错误的。
class item: public base;
动态绑定需要符合两个条件:调用函数必须是virtual ;必须要通过指针或引用调用虚函数。 动态绑定时执行函数取决于实际执行的类型,而不取决于指针或引用变量类型。
// item 是子类
item b;
base a = b;
a.show(); // 不会动态绑定,a不是指针也不是引用。调用变量a的类方法(a类是base 所以调用base版方法)
base *a = &b;
a->show(); // 动态绑定,调用item版方法,调用实际数据的类方法(实际数据b是item类)
base &a = b;
a.show(); // 动态绑定,调用item版方法,调用实际数据的类方法(实际数据b是item类)
virtual函数版本是在运行时确定,非virtual函数是在编译时确定。
也可以指定执行virtual函数版本如:
item b;
base *a = &b;
a->base::show(); // 指针指定调用base版方法
base &a = b;
a.base::show(); // 引用指定调用base版方法
函数可以设定默认默认参数,默认参数定义的顺序为自右到左。即如果一个参数设定了缺省值时,其右边的参数都要有缺省值
c++三种继承方式:public, private, protected 假设B类继承A类,即B类是A类的直接子类。
public继承:A的访问属性在B类保持不变。
A的public-----------B仍是public;
A的protected-------B仍是protected;
A的private----------B无法访问(仍是private);
protected继承:
A的public-----------B变成protected;
A的protected-------B仍是protected;
A的private----------B无法访问(仍是private);
private继承:
A的public-----------B无法访问(变成private);
A的protected-------B无法访问(变成private);
A的private----------B无法访问(仍是private);
派生类可以恢复继承的成员访问级别(只能恢复子类可访问的成员级别),但不能使被恢复成员的级别比他原来的还大。
class base
{
public:
void show(){};
void show(int i){};
protected:
void log(){};
};
class item : private base
{
public:
using base::show; // 可以恢复所有重载版本到子类
using base::log; // 错误不能使被恢复成员的级别比他原来的还大
}
派生类继承基类默认级别是由派生类决定,如果派生类是struct则默认是public,若是class则是private。
class a : b // prvate 继承
struct a : b // public 继承
基类的友元关系是无法被子类继承的,所以要想基类的友元类访问子类的私有成员需要在子类中定义友元关系。
15.3 基类到派生类的转换
基类对象和派生类之间有单向转换关系。派生类可以转换成基类反过来则不允许。因为基类里的成员派生类中都包含所以转换无错,但派生类中所有对象基类并不全部包含所以转化会失败。
一个基类引用或指针指向派生类时实际执行的是派生类的代码。
一个基类对象指向派生类时会发生拷贝赋值操作,用派生类中数据成员初始化或赋值基类对应成员,而方法成员还是使用基类版本。所以这种情况下不会发生动态绑定virtual函数。
15.4 构造函数和复制控制
缺省情况下派生类创建对象时会先调用基类的默认构造函数,然后再调用自己的构造函数。
也可以在派生类构造函数中显示调用基类某个构造函数,甚至给基类构造函数传参。调用语法是
class item : public base
{
public:
item (int age,string name) : base(age,name),prage(age),prname(name) {}; // 调用基类构造函数并传参, 初始化本类成员
}
派生类只能调用直接基类构造函数。 如果不显示调用基类构造函数则基类一定要有默认构造函数否则会产生编译错误。
复制构造函数有点不同:子类使用合成复制构造函数则先调用基类默认构造函数再调用子类合成复制构造函数。如果定义了子类的复制构造则一定要显示调用基类赋值构造函数。否则会出现 子类成员是被复制对象副本,而基类成员却未初始化。
class item : public base
{
public:
item (cosnt item &it) : base(it) ... {}; // 一定要调用基类复制构造函数base(it)
}
赋值操作同复制类似,如果派生类定义了自己的赋值操作一定要显示为基类进行赋值
class item : public base
{
public:
item &operator=(const item &it)
{
base:: operator=(it); // 显示调用基类赋值操作
//...
};
}
析构函数无论如何总是会调用父类的析构函数。析构函数运行顺序和构造函数相反,总是先运行子类析构函数再运行父类析构函数。
class one
{
public: ~one(){ cout << "end one" << endl;}; one{ cout << "init one" << endl;};
}
class two : public one
{
public: ~tow(){ cout << "end two" << endl;}; tow(){ cout << "init two" << endl;};
}
class three : public two
{
public: ~three(){ cout << "end three" << endl;}; three(){ cout << "init three" << endl;};
}
three b; // 此时依次输出 "init one" "init two" "init three"
one *a = &b;
// 当超过作用域时依次输出 "end three" "end two" "end one"
当定义three *a =&b a在回收时输出和上面一样,因为输出内容层级和实际指向的对象类型有关系,和指针类型无关。
但是有一种情况输出层级和指针有直接关系:动态对象,下面代码只会执行指针对象的析构函数。
one *a = new three() ;
delete a ; // 只输出"end one"
如何才能输出 "end three" "end two" "end one"呢? 只要将类 one 中析构函数设置成虚析构函数即可 virtual ~one(){...} 。
构造函数和赋值函数不要定义成虚函数,因为会让人混淆且没有什么用处。
15.5 继承情况下的类作用域
子类可以定义和父类一样的非虚函数,此时子类会覆盖父类函数。
和虚函数动态绑定不同,调用版本并不是由指向的数据类型决定,而是由申明变量类型决定。
如果想调用父类成员需要如此调用
itm a;
a.base::show(); // 调用base类的show方法
item *b =&a;
b->base::show(); // 调用base类的show方法
base k = &a;
b->show(); // 申明变量类型是base,所以会调用base类的show方法,不需要b->base::show();
15.6 纯虚函数
纯虚函数申明很简单 void show()=0;拥有纯虚函数的类无法定义对象,但可以定义指针或引用。假设基类 base 定义了纯虚函数。
base c ; // 错误
base *c = &b ; // 正确
base &c = b ; // 正确
15.7 容器与继承
容器对象可以定义成存放基类对象,但可以给容器加入子类对象,这时候子类会被转换成基类对象,或者说基类部分会被系统删除。
可以定义基类指针或引用类型容器,再增加子类指针活引用,这时候会更具实际内容不同执行不同代码(动态绑定)。
15.8 句柄类与继承
我们知道C++中最令人头疼的当属指针,如果您申请了对象却没有释放它,时间一长就会造成系统崩溃,大量的内存溢出使得您的程序的健壮性出现问题而句柄类就是为了能够解决这一问题而出现的,句柄类有点类似于智能指针。
好了,废话不多说,我们来看代码,首先我们来看 head.h文件的代码:
#ifndef HEAD_H
#define HEAD_H
#include<iostream>
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::string;
//基类
class Item_base
{
public:
//基类的虚函数,用于智能地复制对象
virtual Item_base* clone() const
{
return new Item_base(*this);
}
};
//子类
class Bulk_item: public Item_base
{
//子类的虚函数的重载,用于智能地复制对象
virtual Bulk_item* clone() const
{
return new Bulk_item(*this);
}
};
//句柄类
class Sales_item
{
public:
//默认构造函数,用来初始化一个引用计数器(句柄类未绑定任何对象)
Sales_item(): p(0), use(new size_t(0)) { cout << "Sales_item定义了空句柄" << endl;};
//带有一个参数的,且该参数为基类引用的构造函数
Sales_item( const Item_base &i): p(i.clone()), use(new size_t( 1 )) { cout << "Sales_item的引用计数器初始化为1" << endl; };
//复制构造函数,需要注意的是,每复制一次就需要增加引用计数一次
Sales_item( const Sales_item &i ): p(i.p), use(i.use) { ++*use;};
void show(){cout<< "user: " << *use << endl;};
//析构函数,析构的时候会判断是否能够释放指针所指向的数据
~Sales_item() { decr_use();};
//赋值操作符重载
Sales_item& operator= ( const Sales_item& );
//访问操作符重载
const Item_base* operator-> () const
{
if( p )
{
return p;
}
else
{
cout << "p指针错误" << endl;
}
};
//解引用操作符重载
const Item_base& operator* () const
{
if( p )
{
return *p;
}
else
{
//重载虚函数,用于智能地复制对象
cout << "p指针错误" << endl;
}
};
private:
//两个指针存储着引用计数器以及数据的指针
Item_base *p;
size_t *use;
//减少引用
void decr_use()
{
if(*use == 0 && p == 0)
{
cout << "空句柄无需释放任何资源"<<endl;
return;
}
cout << "在 dec_use函数中引用计数减少了,当前计数值为:" << *use - 1 << endl;
if( --*use == 0 )
{
delete p;
delete use;
cout << "在 dec_use函数中计数器减为0,释放对象" << endl;
}
};
};
//赋值操作符重载,每次复制都会增加引用计数
Sales_item& Sales_item::operator= ( const Sales_item &si )
{
//这里需要特别注意的就是待复制的对象的计数器需要加1而被赋值的对象需要减1
//增加被复制对象的引用计数
++*si.use;
//将即将被赋值的对象的引用计数减1
decr_use();
//复制指针
p = si.p;
use = si.use;
//返回
return *this;
};
#endif //HEAD_H
接下来我们来看mail.cc的代码:
#include"head.h"
int main()
{
// 被包装类(实际上包装的是这个对象的副本)
Bulk_item item;
Sales_item a(item); // 输出 : Sales_item的引用计数器初始化为1
a.show(); // 输出 : user:1
Sales_item b(a);
a.show(); // 输出 : user:2
b.show(); // 输出 : user:2
Sales_item c; // 输出 : Sales_item定义了空句柄
c.show(); // 输出 : user:0
c = b; // 输出 : 空句柄无需释放任何资源
c.show(); // 输出 : user:3
b.show(); // 输出 : user:3
a.show(); // 输出 : user:3
}
当main函数执行完毕,c最先被释放:
// 输出 : 在 dec_use函数中引用计数减少了,当前计数值为: 2
b被释放:
// 输出 : 在 dec_use函数中引用计数减少了,当前计数值为: 1
a被释放:
// 输出 : 在 dec_use函数中引用计数减少了,当前计数值为: 0
在 dec_use函数中计数器减为0,释放对象
此时已经删除了被包装对象(item的副本)
最后item 对象被释放
结论:我们可以看到,句柄类能够很方便并且能够很安全地释放内存,不会导致内存的泄露。
posted @ 2012-05-22 15:14 老金 阅读(16) 评论(0)
编辑

2012年5月16日
14.1 重载操作符的定义
操作符(+ ,- , * , / , = , < , >)可以被内置类型使用,比如两个整数相加或相减,两个字符串相加,两个数组比较大小等等。自定义类默认是不能使用大多数操作符的。自定义类是复合类型,相加或想减或比较大小并没有相应的规则匹配:两个类相加等于什么? 两个类如何确定谁大谁小? C++允许我们通过重载运算符的技术让自定义对象支持这些操作。我们可以定义重载规则。
操作符重载语法很简单: 关键字 operator 后接操作符 比如 operator+
可以重载的操作符:
不能重载的操作符:
| :: |
. |
.* |
? : |
| sizeof |
typeid |
new |
delete |
| static_cast |
dynamic_cast |
const_cast |
reinterpret_cast |
重载操作符可以定义成类成员函数,也可以定义成非成员函数
class myclass
{
public:
myclass() : age(0){};
// 成员函数定义
myclass operator+(const myclass &obj) const
{
myclass cls;
cls.age = age + obj.age;
return cls;
};
int age;
}
// 等价的非成员函数定义
myclass operator+(const myclass &obj1, const myclass &obj2)
{
myclass cls;
cls.age = obj1.age + obj2.age;
return cls;
}
成员函数定义看起来少了一个参数,实际上语法将this限定为第一个操作数。大部分操作符允许定义为成员或非成员函数,具体如何定义看个人喜好。
+ 有些操作符只能定义为成员,如果定义成非成员会产生编译错误 赋值= 下标[] 调用() 箭头访问-> 都不允许定义成非成员函数。
+ 有些操作符只能定义为非成员,如 输入 << 输出 >> 操作符
+ 改变自身状态的建议定义为成员函数,例如 自增++ 自减-- 解引 复合操作+= -= 等
+ 对称操作符建议定义为非成员函数,例如加减 + - 比较 < == >
+ 成员函数中可以使用 this 而非成员函数中无法使用,因为函数不属于任何对象。
重载操作符至少要包含一个类类型操作数
myclass operator+(myclass *obj1, const myclass *obj2) // 操作数至少要包含一个类类型,防止用户修改内置类型的操作符,如果用户定义 int operator+(int a,int b) 意味着用户要修改int类型的加法操作符。
输入操作符:
&ostream operator<<(ostream &temp, const myclass &obj) // 第二个形参一般都作const限定因为它只做读取操作
{
temp << obj.size;
return temp;
}
输出操作符:
&istream operator<<(istream &temp, const myclass &obj) // 第二个形参不能const限定,因为需要输入内容到该参数中
{
tmp >> obj.size;
return temp;
}
输入输出操作符返回都必须是引用,且第一个形参也是引用,前面章节中已说明 IO对象无法复制或者赋值。
这两个操作符只能定义成非成员函数,原因在于第一形参必须是IO对象引用,而定义为成员函数时第一个参数被省略且被限定为 this 所以只能定义成非成员函数。
算数运算符:
myclass operator+(const myclass &obj1, const myclass &obj2)
{
myclass cls;
cls.age = obj1.age + obj2.age;
return cls;
}
myclass operator-(const myclass &obj1, const myclass &obj2)
{
myclass cls;
cls.age = obj1.age - obj2.age;
return cls;
}
bool operator==(const myclass &obj1, const myclass &obj2)
{
return obj1.age == obj2.age;
}
bool operator>(const myclass &obj1, const myclass &obj2)
{
return obj1.age > obj2.age;
}
赋值=操作符:
myclass& operator=(const myclass &obj) // 赋值符左操作数是this 指向对象(当前类对象),赋值符号右操作数是形参obj 返回值必须是*this的引用
{
age = obj.age;
return *this;
};
下标操作符:
string operator[] (const size_t index) // 返回左值(常量) 下标操作只读
string& operator[] (const size_t index) // 返回左值(引用) 下标操作可读可写
下标操作可实现const重载
string& operator[] (const size_t index) // 下标操作可读可写
const string& operator[] (const size_t index) const // 下标操作只读
调用规则是 const对象调用const版,非const调用非const版
myclass a ;
a[1] ; // 调用非const版
const myclass b ;
b[1] ; // 调用const版
解引操作符:
class cls
{
public:
void show(int p){cout << p << endl;};
}
class myclass
{
public:
myclass:sp(p) (cls *p)
cls &operator*(){return *sp;}; // 解引返回具体类
cls *operator->(){return sp;}; // 箭头返回类指针,实际使用时返回的指针会立刻再做系统的箭头操作
const cls &operator*() const {return *sp;}; // const重载版本
const cls *operator->() const {return sp;}; // const重载版本
private:
cls *sp;
}
cls a;
myclass t(&a) ;
cls b = *t ; // 调用解引操作符,返回cls类对象
t->show(3) ; // 调用箭头操作符,调用cls对象的show()方法
此时t是对象而不是指针,如果t是指针,则会调用系统解引和箭头操作:
cls *j = &t;
*j ; // 返回 myclass对象
j->show(3) ; // 调用myclass对象的show方法,本例中myclass没有定义方法,运行时报错
自增操作符:
class myclass
{
public:
int ls[4];
int *cur;
myclass:ls{2,3,1,5},cur(ls){};
myclass &operator++(){ cur ++; return *this;}; // ++myclass重载,返回引用或对象
myclass operator++(int){ myclass tmp(*this); cur ++; return tmp;}; // myclass++重载,只能返回对象(不允许返回局部对象的引用)
}
myclass++重载多了形参int,只起到标识作用。
使用和内置类自增没审美区别,自减和自增类似。
调用操作符:
class myclass
{
public:
int operator() (int i){ return i + 2;};
}
定义了调用操作符的类对象叫做 函数对象 ,因为他们的对象行为类似函数
myclass obj;
int j = obj(5) ; // 使用调用操作符重载函数 j = 7
posted @ 2012-05-16 11:51 老金 阅读(14) 评论(0)
编辑

2012年5月15日
复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。
复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用 const 修饰)是对该类类型的引用。
析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。不管类是否定义了自己的析构函数,编译器都会自动为类中非 static 数据成员执行析构函数。
赋值操作符与构造函数一样,赋值操作符可以通过指定不同类型的右操作数而重载。右操作数为类类型的版本比较特殊:如果我们没有编写这种版本,编译器将为我们合成一个。
class myclass
{
public:
myclass(const myclass &obj){}; // 复制构造函数
~myclass(){}; // 析构函数
myclass& operator=(const myclass &obj){}; // 赋值操作符
private:
int age;
string name;
}
13.1 复制构造函数
编译器合成的复制控制函数是非常精练的——它们只做必需的工作。但对某些类而言,依赖于默认定义会导致灾难。实现复制控制操作最困难的部分,往往在于识别何时需要覆盖默认版本。有一种特别常见的情况需要类定义自己的复制控制成员的:类具有指针成员。
复制构造函数在下列情况下会被调用:
myclass obj1;
myclass obj2 = obj1; // 根据另一个同类型的对象显式或隐式初始化一个对象
myclass fun(myclass par)
{
// ...
return par; // 从函数返回时复制一个对象
}
fun(obj1); // 复制一个对象,将它作为实参传给一个函数
vector<string> svec(5);; // 编译器首先使用 string 默认构造函数创建一个临时值来初始化 svec,然后使用复制构造函数将临时值复制到 svec 的每个元素
myclass ls[]{obj1,obj1,obj1,obj1};// 根据对象初始化数组
myclass ls[]{myclass(),myclass(),myclass()}; // 按照书上说是会调用复制构造函数但实际不会调用,据说是做了优化
如果不提供显示的复制构造函数系统会合成一个。合成的构造函数会在上述情况发生时会赋值对象副本并将对象数据成员逐一初始化成与原对象相同的值。
有个有趣的现象:数组是不能复制的,但如果对象数据成员是个数组类型却可以复制数组给对象副本的对应成员,合成复制构造函数模型如下
myclass(const myclass &obj):age(obj.age),name(obj.name)
{
// obj是源对象,用它来复制副本
}
如果想禁止复制可以显示声明私有的复制构造函数(最好不要这么做否则类只能作为指针或引用传递),复制构造函数属于构造函数,一旦定义复制构造函数应该给类显示同时定义一个默认构造函数。
13.2 赋值操作符
类的赋值操作符实际上是操作符重载(operator=)
赋值操作结果和拷贝构造函数类似,它会执行逐个成员赋值(复制构造是逐个成员初始化,然后也允许重新赋值)
myclass& operator=(const myclass &obj)
{
// obj是源对象,用它来为操作符左面对象赋值
age = obj.age;
name = obj.name;
return *this;
}
赋值操作符和复制构造函数几乎可以看做一个整体,如果需要其中一个几乎肯定也需要另外一个。
关于操作符重载会在后续章节做详细介绍。
13.3 析构函数
析构函数一个用途是对象在销毁之前做一些相关操作,比如清理资源,刷新缓冲区等。析构函数在对象即将销毁前执行
class he
{
public:
string name;
~he(){cout << name << " is delete!" << endl;};
};
int main()
{
he cls;
cls.name = "zhang san";
he *ls = new he[4];
ls[0].name = "item0";
ls[1].name = "item1";
ls[2].name = "item2";
ls[3].name = "item3";
delete [] ls;
cout << "delete list" << endl;
he *pr = new he();
pr->name = "li si"
cout << "delete li si" << endl;
}
// 输出:
item3 is delete!
item2 is delete!
item1 is delete!
delete list
li si is delete!
delete li si
zhang san is delete!
赋值操作和复制(拷贝)构造函数效果类似,在使用=号操作时有时候会调用赋值有时候会调用复制构造函数,怎么区分调用方式呢?
复制(拷贝)构造函数,是用一个已知的对象去初始化另一个正在创建的对象;赋值操作,是用一个已经存在的对象去更新另一个已经存在的对象。
myclass a ;
myclass b = a ; // 用一个已知的对象去初始化另一个正在创建的对象,调用复制构造函数
b = a ; // 用一个已经存在的对象去更新另一个已经存在的对象,调用赋值操作
赋值操作符可以通过指定不同类型的右操作数而重载,看代码
class myclass
{
public:
myclass& operator=(const myclass &obj){ name = obj.name; return *this;}; // 赋值操作符
myclass& operator=(string str){ name = str; return *this;}; // 赋值操作符重载
private:
string name;
}
myclass a;
myclass b;
b = a; // 调用 operator=(const myclass &obj)版
b = "tom"; // 调用 operator=(string str)版
本章最后介绍了智能指针的概念。它不是c++具体技术而是解决拷贝对象时指针字段会可能会引发错误的解决方案
// 类数据成员指针类
class myclass
{
public:
string name;
int age;
};
// 智能指针
class curr
{
// 将具体类设置成智能指针的友元类
firend class test;
private:
curr(myclass *ip):cur(ip),used(1) {};
// 最后一个拥有指针成员的对象消亡时会删除智能指针对象,析构函数执行删除真正指向的类对象
~curr()
{
cout << "已经没有任何指针指向myclass对象!"<< endl;
delete cur; // 构造函数参数*ip必须是动态创建的对象指针 delete才能正确删除,否则会产生无法预知的运行时错误
};
myclass *cur;
int used;
};
// 具体类
class test
{
public:
test(myclass *ip, string stname,int stage): pro(new curr(ip)) ,name(stname) ,age(stage) {};
test(const test &t): pro(t.pro) ,name(t.name) ,age(t.age)
{
++pro->used;
};
~test()
{
--pro->used;
// 最后一个引用对象消失,智能指针计数器等于0,删除智能指针动态对象(智能指针删除时会出发自身的析构函数,析构函数中负责删除类成员)
if(pro->used == 0)
{
delete pro;
}
};
private:
curr *pro;
string name;
int age;
};
int main()
{
myclass *pr = new myclss();
test t1 = new test(pr) ; // 此时智能指针私有成员used == 1
test t1(t1); // 具体类相互拷贝,此时智能指针私有成员used == 2
delete t1; // 删除一个具体类t1,此时智能指针私有成员used == 1
} // 方法的作用范围结束,t2被回收,会先后触发具体类的析构函数和智能指针的析构函数继而删除智能指针对象和数据成员类对象 *pr
智能指针基本思路是用智能指针对象替换数据成员类对象指针,由智能指针维护对象指向。当具体类发生拷贝或删除时更新智能指针维护的计数器。如果计数器==0说明所有具体类都消亡,删除智能指针。智能指针再负责删除数据成员对象。
posted @ 2012-05-15 14:54 老金 阅读(15) 评论(0)
编辑

2012年5月14日
摘要: 简单地说,类就是定义了一个新的类型和一个新作用域。12.1 类的定义和声明 类由类成员组成。类成员包括属性,字段,成员函数,构造函数,析构函数等组成。 类设计应该遵从抽象封装性。 类抽象性指对于类的使用者来说只需知道类接口即可使用类功能。类的具体实现由设计者负责。即使某个功能发生了变更但由于使用者是以接口方式调用类所以用户代码无需做任何修改。 类封装性指类用户只需知道类的功能无需了解具体实现。实现代码对用户来说不可见。 C++类没有访问级别限限制,定义类时不能用public 或private 做修饰。类成员有访问级别,可以定义 public protect privateclassScre..
阅读全文
posted @ 2012-05-14 17:11 老金 阅读(18) 评论(0)
编辑
摘要: 标准库提供的 find 运算:vector<int>::const_iterator result =find(vec.begin(), vec.end(), search_value);只要找到与给定值相等的元素,find 就会返回指向该元素的迭代器。如果没有匹配的元素,find 就返回它的第二个迭代器实参,表示查找失败。由于 find 运算是基于迭代器的,因此可在任意容器中使用相同的 find 函数查找值。类似地,由于指针的行为与作用在内置数组上的迭代器一样,因此也可以使用 find 来搜索数组:int ia[6] = {27, 210, 12, 47, 109, 83};in
阅读全文
posted @ 2012-05-14 11:28 老金 阅读(7) 评论(0)
编辑

2012年5月11日
摘要: 关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。 关联容器(Associative containers)支持通过键来高效地查找和读取元素。两个基本的关联容器类型是 map 和 set。 map 的元素以键-值(key-value)对的形式组织:键用作元素在 map 中的索引,而值则表示所存储和读取的数据。set 仅包含一个键,并有效地支持关于某个键是否存在的查询。 关联容器类型 map ...
阅读全文
posted @ 2012-05-11 15:05 老金 阅读(17) 评论(0)
编辑

2012年5月10日
摘要: 标准库定义了三种顺序容器类型:vector、list 和 deque(是双端队列“double-ended queue”的简写,发音为“deck”)。 它们的差别在于访问元素的方式,以及添加或删除元素相关操作的运行代价。 标准库还提供了三种容器适配器(adaptors)。 实际上,适配器是根据原始的容器类型所提供的操作,通过定义新的操作接口,来适应基础的容器类型。顺序容器适配器包括 stack、queue 和 priority_queue 类型 顺序容器 ...
阅读全文
posted @ 2012-05-10 17:04 老金 阅读(11) 评论(0)
编辑

2012年5月9日
摘要: 学习本章内容之前有必要对缓冲区的概念做一个基本了解,我引用了网上一片文章《C++编程对缓冲区的理解》,内容如下: 什么是缓冲区 缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。 缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。为什么要引入缓冲区 比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大...
阅读全文
posted @ 2012-05-09 15:19 老金 阅读(23) 评论(0)
编辑