Han's Blog Cell

Hi, My world!


  • 首页

  • 归档

  • 标签

原子操作

发表于 2016-11-25

1.为什么需要使用原子操作

对于一些(Read-Modify-Write)操作。在多个线程同时对同一地址进行操作时,根据线程的调度方式的不同,可能产生不同的结果(有的正确,有的错误)。而我们对于这种不确定性是绝对不能容忍的。

2.exmaple

在计算直方图问题中,如果直接采取GPU并行统计出现的次数,并将数据直接原子加到全局变量中。如下:

1
2
3
4
5
6
7
8
9
10
11
12
__global__ void histo_kernel( unsigned char *buffer,
long size,
unsigned int *histo ) {
// calculate the starting index and the offset to the next
// block that each thread will be processing
int i = threadIdx.x + blockIdx.x * blockDim.x;
int stride = blockDim.x * gridDim.x;
while (i < size) {
atomicAdd( &histo[buffer[i]], 1 );
i += stride;
}
}

性能是相当慢的,(比CPU的还慢了几倍)。
为了避免这种大量的线程往同一块较小内存中写数据的情况发生,我们利用shared memory提升性能。这种技巧并没有减少原子操作的次数,相反是增大了,同时又共享内存原子操作和全局内存原子操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
__global__ void histo_kernel( unsigned char *buffer,
long size,
unsigned int *histo ) {
// clear out the accumulation buffer called temp
// since we are launched with 256 threads, it is easy
// to clear that memory with one write per thread
__shared__ unsigned int temp[256];
temp[threadIdx.x] = 0;
__syncthreads();
// calculate the starting index and the offset to the next
// block that each thread will be processing
int i = threadIdx.x + blockIdx.x * blockDim.x;
int stride = blockDim.x * gridDim.x;
while (i < size) {
atomicAdd( &temp[buffer[i]], 1 );
i += stride;
}
// sync the data from the above writes to shared memory
// then add the shared memory values to the values from
// the other thread blocks using global memory
// atomic adds
// same as before, since we have 256 threads, updating the
// global histogram is just one write per thread!
__syncthreads();
atomicAdd( &(histo[threadIdx.x]), temp[threadIdx.x] );
}

正如在直方图计算中看到得,有时候依赖原子操作会带来性能问题,并且这些问题只能通过算法的某些部分进行重构来加以解决。但是在直方图实例中,我们使用了一种两阶段算法,该算法降低了在全局内存访问上竞争程度。通常,这种降低内存竞争程度的策略总能带来不错的效果,因此在使用原子操作的时候,要记住这种策略。

纹理内存

发表于 2016-11-24

与常量内存类似,纹理内存同样缓存在芯片上,因此在某些情况中,它能减少对内存的请求并提供更高效的内存带宽。纹理缓存是专门为那些在内存访问模式中存在大量空间局部性(Spatial Locality)的图形应用程序而设计的。在某个计算应用程序中,这意味着一个线程读取的位置可能与邻近线程读取的位置“非常接近”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//一维纹理
texture<float> texIn;
texture<float> texOut;
cudaBindTexture(NULL, texIn, data.inSrc, size);
cudaBindTexture(NULL, texOut, data.outSrc, size);
//不同与其它内存用方括号访问内存,纹理内存用内置函数访问
float f1 = tex1Dfetch(texIn, pos1);
float f2 = tex1Dfetch(texOut, pos2);
//核函数中无需将纹理内存作为参数传入
cudaUnbindTexture(texIn);
cudaUnbindTexture(texOut);
//二维纹理
texture<float, 2> texIn;
texture<float, 2> texOut;
//使用tex2D时,不需要担心发生溢出的问题。如果参数x,y小于0或某个值大于宽度,则会返回位于0或者宽度位置的值。(注意,在有些应用程序中需要这种行为,而有些则需要避免该行为的发生)
float t = tex2D(texIn, x, y);
//与一维纹理相同的是,都需要预先分配存储空间。但不同的是,二维纹理要求提供一个cudaChannelFormatDesc,对通道格式描述符的声明。
cudaChannelFormatDesc desc = cudaCreateChannelDesc<float>();
//这里使用默认的参数,仅仅指定浮点描述符。
cudaBindTexture2D(NULL, texIn, data.Insrc, desc, DIM, DIM, size);
//同样是使用cudaUnbindTexture(texIn)来解除绑定。

注意,无论是使用2维纹理还是1维纹理,性能基本相同,只是在有些情况下,2维纹理的代码会更简单直观一些。

常量内存和GPU事件性能测试

发表于 2016-11-24

常量内存

常量内存首先,顾名思义,是不会发生变化的。
其次,在GPU中,访问常量内存,对常量内存的读操作可以广播到该线程的“邻近”线程,而什么是“邻近”线程呢,在此需要引入线程束的概念。

线程束Warp

这里的warp并不是《星际迷航》电影中的曲速引擎(Warp Drive),而是来自纺织(Weaving)领域的概念。线程束可以看成是一组线程通过交织而形成的一个整体。在cuda架构中,线程束是指一个包含32个线程的集合,这个线程集合被“编织在一起”并且以“步调一致(LOCKSTEP)”的形式执行。在程序中的每一行,线程束中的每个线程都将在不同的数据上执行相同的指令。

使用常量内存的优势

当处理常量内存时,NVIDIA硬件将把单词内存读取操作广播到每个半线程束(Half-Warp)。在半线程束中包含了16个线程。如果在半线程束中的每个线程都从常量内存的相同地址上读取数据,那么GPU只会产生一次读取请求并在随后将数据广播到每个线程。如果从常量内存中读取大量的数据,那么这种方式产生的内存流量只是使用全局内存的(1/16)。
同时,由于读取常量内存时,内容是不会发生变化的,因此硬件将主动把这个常量数据缓存在GPU上。那么在第一次从常量内存的地址上读取后,当其他半线程束请求同一地址时,那么将命中缓存,这同样减少了额外的内存流量。

使用常量内存的缺点

然而当使用常量内存时,也可能造成性能的下降。当半线程内的线程访问的数据不同时,将会串行的访问常量内存。如分别读取不同的地址时,则需要16倍的时间来发出请求,这是一个权衡的过程。

事件性能测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stoo);
cudaEventRecord(start, 0);
//在GPU上执行一些工作
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);//防止cpu代码在gpu代码还在执行时就记录了时间而导致测试不准。
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
cudaEventDestory(start);
cudaEventDestory(stop);

Effective C++ --让自己习惯C++(02)

发表于 2016-11-09

一,Use const whenever possible.(03)

主要内容

const是一个强大的武器,它允许你指定一个语义约束(指定一个“不该被改动”的对象),然后通过编译器来行使这项约束。而我们应该做的事情就是,只要这(某值应该保持不变)是一个事实,那么久该尽可能的说出来,获得编译器的襄助,减少程序出错的可能。

下面记录一些我觉得自己还不太熟的tips:
1,const在*左右分别表示 是指针指向的数值为const还是指针本身是const。

1
2
3
4
5
char greeting[] = "Hello"
char* p = greeting; //not-const pointer, non-const data
const char* p = greeting; //non-const pointer, const data
char* const p = greeting; //const pointer, non-const data
const char* const p = greeting; // const pointer, const data

2,const在函数前,表示其返回值是const不能被修改;const如果为函数参数,表示该参数在函数中不可被修改;const如果修饰函数本身,则这个函数是bitwise const的,不可修改任何关于对象的内容。

3,const 和 const-non函数,可以够成重载。但是这往往会导致大量代码的重复。我们可以通过让non-const函数调用const函数实现常量性转移(casting away constness)(不可反向)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//for example
class TextBlock{
public:
TextBlock(string s)
{
text = s;
}
char& operator [](size_t t)
{
return const_cast<char&> (
static_cast<const TextBlock&>(*this)[t]
);
}
const char& operator [](size_t t) const
{
return text[t];
}
private:
string text;
//即使在const函数中,也加油被修改
mutable bool flag;
};

Effective C++ --让自己习惯C++(01)

发表于 2016-11-08

一,View C++ as a federation of languages.(01)

主要内容

将C++视为一个由相关语言组成的语言联邦。
在某个次语言(sublanguage)中,各种守则与通例都倾向于简单,直观。然而当从一个次语言转移到另一个次语言中时,守则可能改变。而其主要的次语言总共有四个:

1,C语言。C++以C为基础,很多的概念来源于C,blocks, statements, preprocessor, built-in data types, arrays, pointers。但C也有其局限性:没有模板(templates),没有异常(exceptions),没有重载(overloading)

2,Object-Oriented C++。这一部分是面向对象设计古典守则在C++上最直接的实施。也是C with classes所诉求的:classes(包括构造函数,析构函数),封装(encapsulation),继承(inheritance),多态(polymorphism),virtual函数(动态绑定)。

3,Template C++。C++的泛型编程,也是大多数程序员经验最少的部分。Template相关考虑与设计已经弥漫整个C++,良好编程守则中“唯template”适用的特殊条款并不罕见。

4,STL。一个template程序库,对容器(containers),迭代器(iterators),算法(algorithms)已经函数对象的规约有极佳的紧密配合与协调。

总结

记住这四个次语言,当你从某个次语言切换到另一个,导致高效变成守则要求你改变策略时,不要感到惊讶。例如对内置类型而言pass-by-value通常比pass-by-reference高效,但当你从C part of C++移往Object-Oriented-C++时,由于用户自定义构造函数和析构函数的存在,pass-by-reference-to-const往往更好,运用TemplateC++时尤其如此,因为彼时你甚至不知道所处理的对象的类型。然而一旦跨入STL你就会了解,迭代器和函数都是在C指针之上塑造的,所以对STL的迭代器和函数对象而言,旧式的C pass-by-value守则再次适用。

因此C++是四个次语言组成的语言联邦政府,每个语言都有自己的规约。记住这四个次语言你就会发现C++容易理解很多。

Prefer consts, enums, and inlines to #defines.(02)

主要内容

尽量减少#define的使用,多让编译器去工作而不是让预处理器工作!!
原因一:

1
2
3
4
#define RATIO 1.635
//也许从未被编译器看见而被预处理器拿走从而会报错。
const double Ratio = 1.635;//这样则不会出问题

在声明常量(通常放在头文件里)的时候需要注意两个问题
1, 常量字符串
const char* const authorNmae = “Scott Meyers”
2,类常量,不需要每个对象都有自己的常量,故为静态。

1
2
3
4
5
6
class GamePlayer{
private:
static const int Number = 5;
int scores[Number];
...
}

注意,这里的number只是声明式,(相当于函数只声明了还没实现)。如果需要取这个常量的地址,则需要在实现文件里写它的定义式。

1
const int GamePlayer::Number; //Number 的定义,此时不需要再赋值

原因二:

#define是不重视域的。这意味着#define不仅不能够用来定义class的专属常量,而且不能提供任何的封装性。

原因三:
不推荐使用预处理器来实现宏(macros)。宏看起来很像函数,其优点是不会招致函数调用的额外开销。如:

1
#define CALL_WITH_MAX(a, b) f((a)>(b) > (a) : (b))

这种宏是有很多缺点的!
首先,再你写出这种宏的时候,你必须要为每一个实参加上小括号!否则某人在表达式中调用这个宏就会遭遇麻烦。但纵然如此,也不可避免会出现麻烦。

1
2
3
4
5
int a = 5;
int b = 0;
CALL_WITH_MAX(++a, b); //a = 7
CALL_WITH_MAX(++a, b + 10); //a = 6

结果甚至会因为比较的值不同而不同,相当于程序错误。(其原因是参数被核算多次)。
而推荐使用的是template inline函数,既可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性。

1
2
3
4
5
template <typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}

有了consts, enums和inlines,我们对于预处理器(特别是#define)的需求降低了,但并非完全消除。#include依然是必需品,而#ifdef / #ifndef也继续扮演着控制编译的重要角色。目前还不到预处理全面而退的时候,但你应该明确地给予它更长更频繁的假期!

总结:

对于单纯常量,最好以const对象或者enum替换#define。
对于形似函数的宏,最好改用inline函数替代#define。

Trie树

发表于 2016-11-08

Trie(字典树)可以保存一些字符串->值得对应关系,其实它和hashmap的功能相同,以key-value映射的方式用空间换时间。(只不过Trie的key往往就是字符串)

Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key(也就是字符串的size) 的长度,与 Trie 中保存了多少个元素无关。Hash 表号称是 O(1) 的,但在计算 hash 的时候就肯定会是 O(k) ,而且还有碰撞之类的问题;Trie 的缺点是空间消耗很高。

Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
struct Trie_node
{
bool exist;
Trie_node* next[26];
int count;
};
Trie_node* createTrie()
{
Trie_node* root = new Trie_node();
root->count = 0;
root->exist = false;
memset(root->next, 0, sizeof(root->next));
return root;
}
void deleteTrie(Trie_node* root)
{
for (int i = 0; i < 26; ++i)
{
if (root->next[i] != NULL)
deleteTrie(root->next[i]);
}
delete root;
}
void insert(Trie_node* root, string word)
{
int nb = word.size();
if (nb == 0) return;
Trie_node* pt = root;
root->count++;
for (int i = 0; i < nb; ++i)
{
int p = word[i] - 'a';
if (pt->next[p] == NULL)
{
Trie_node* tt = new Trie_node();
tt->count = 1;
memset(tt, NULL, sizeof(tt->next));
if (i == nb - 1)
tt->exist = true;
else
tt->exist = false;
pt->next[p] = tt;
}
else
{
pt->next[p]->count++;
if (i == nb - 1)
pt->next[p]->exist = true;
}
pt = pt->next[p];
}
return;
}
int find(Trie_node* root, string word)
{
int nb = word.size();
if (nb == 0) return 0;
Trie_node* pt = root;
for (int i = 0; i < word.size(); ++i)
{
int p = word[i] - 'a';
if (pt->next[p] == NULL) return 0;
if (i == nb - 1) return pt->next[p]->count;
else
pt = pt->next[p];
}
}
int Trie_main()
{
int N, M;
vector<string> words;
vector<string> searches;
scanf("%d", &N);
getchar();
for (int i = 0; i < N; ++i)
{
string temp;
getline(cin, temp);
words.push_back(temp);
}
scanf("%d", &M);
getchar();
for (int i = 0; i < M; ++i)
{
string temp;
getline(cin, temp);
searches.push_back(temp);
}
Trie_node* root = createTrie();
for (int i = 0; i < words.size(); ++i)
{
insert(root, words[i]);
}
for (int i = 0; i < searches.size(); ++i)
{
printf("%d\n", find(root, searches[i]));
}
deleteTrie(root);
return 0;
}

C++程序设计---模板

发表于 2016-11-04

目录

. 1,文件操作
. 2,函数模板

文件操作

1,数据的层次

位 bit
字节 byte
域/记录 如一个结构体等。
将所有记录顺序写入一个文件->顺序文件

C++标准库: ifstream ofstream fstream

3,文件的读写指针

函数模板

泛型程序设计
Generic Programming
算法实现时不指定具体要操作的数据的类型。
泛型—算法实现一遍->适用于多种数据结构。
优势: 减少重复代码的编写。
大量编写模板,使用模板的程序设计

. 函数模板
. 类模板

函数模板
template
返回值类型 模板名(形参表)
{
函数体
}

C++程序设计--多态和虚函数

发表于 2016-11-04

目的: 提高程序的可扩展性,减少增加代码时的工作量。
代价: 每一个有虚函数的对象都会增加四个字节的空间来存放虚函数表(空间开销)。虚函数在查虚函数表的时候会有(时间)开销。

虚函数

在类的定义中,前面有virtual关键字的成员函数就是虚函数。
class base{
virtual int get();
}
int base::get(){};
virtual关键字只用在类定义里的函数声明中,写函数体时不用。
构造函数和静态成员函数不能是虚函数。
虚函数支持多态。

多态的表现形式

一,

派生类的指针可以赋给基类指针。
通过基类指针调用基类和派生类的同名虚函数时,
(1)若该指针指向一个基类的对象,那么被调用是基类的虚函数;
(2)若指针指向一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制就叫做“多态”。

二,

派生类的多态可以赋给基类引用。
通过基类引用调用基类和派生类的同名虚函数时,
(1)若该引用引用的是一个基类的对象,那么被调用的是基类的虚函数;
(2)若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制也是“多态”。

实际上,我们用一个基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,就能对各个派生类对象做各种操作,是很常用的做法(例如 pshapes[100])

同时,在非构造函数和非析构函数的成员函数中调用虚函数,是多态!
而在构造函数和析构函数中调用虚函数,不是多态。这在编译时就可以确定了调用的函数是自己的类或基类中定义的函数,不会等到运行时才确定要调用哪个函数。

多态的实现原理

在运行时决定调用基类还是派生类,“动态联编”。
多态实现的关键—虚函数表。每一个有虚函数的类(或者有虚函数的类的派生类),都有一个虚函数表与其对应。该类的任何对象中都放着虚函数表的指针。虚函数表中列出了该类的虚函数地址,多出来的四个字节就是用来放虚函数表地址的。
so, 多态的调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存放的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数的指令。

虚析构函数

针对基类的指针删除派生类对象时 只调用基类的析构函数这种情况。
设计一种虚析构函数。

类如果定义了额虚函数,则最好将析构函数也定义成虚函数。
当然,我们不允许构造函数是虚函数。

纯虚函数和抽象类。

纯虚函数: 没有函数体的虚函数。
抽象类 : 包含纯虚函数的类。
1, 抽象类住能作为基类。
2, 不能创建抽象类的对象。
3, 抽象类的指针和引用->由抽象类派生出来的类的对象

在抽象类中,
1,成员函数内可以调用纯虚函数/
2,在构造函数/析构函数内部不能调用纯虚函数。

如果一个类从抽象类派生而来
它实现了基类中所有的纯虚函数,才能成为非抽象类。

C++程序设计---继承和派生

发表于 2016-11-04

继承: 在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称为子类)。

派生类:

派生类拥有基类的全部成员函数和成员变量,不论是private, protected还是public。
派生类可以对基类进行扩充,添加新的成员变量和成员函数。派生类的使用和基类无关。

tip: 在派生类的成员函数中,不能访问继承自基类的private成员。

派生类对象的内存空间

派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。

覆盖

派生类可以有和基类同名同参数表的函数,将会覆盖掉基类中的成员函数而不算是重复定义。比较常见的是,在派生类的覆盖函数中,先调用基类的该函数,再处理自己不同于基类中该函数的部分。

继承关系和复合关系

继承: “是”关系。
基类A, B是基类A的派生类。
逻辑上要求:“一个B对象也是一个A对象”。

复合: “有”关系。
类C中“有”成员变量K, k是类D的对象,则C和D是复合关系。
一般逻辑上要求: “D对象是C对象的固有属性或者组成部分”。

tips:
1,在一个类中,如果有一个成员变量是其他类的指针,通常称为是“知道”关系。
2, 对于基类/派生类 同名成员,调用基类的public成员时得用 base::__ 来调用。

访问范围说明符

基类的 private成员:可以被下列函数访问
1,基类成员函数
2,基类友员函数

基类的 public成员:可以被下列函数访问
1,基类成员函数
2,基类友员函数
3,派生类成员函数
4,派生类友员函数
5,其它的函数(故用private和public并不能凸显派生类和其他类的区别,引入protected)
基类的 protected成员:可以被下列函数访问
1,基类成员函数
2,基类友员函数
3,派生类的成员函数可以访问当前对象的基类的保护成员

派生类的构造函数

派生类对象 包含 基类对象。
执行派生类构造函数前,首先必须执行基类的构造函数。
如果派生类中还包含成员对象,也是用参数表的形式构造。

具体形式:
构造函数名(形参表):基类名(基类构造函数实参表),…,成员对象(实参表),…
{
}

public继承的赋值兼容规则

class base {};
class derived : public base {};
base b; derived d;
在加了public后,有以下几条赋值兼容规则。
1,派生类的对象可以赋值给基类对象。
b = d;(把d里面包含的base对象 赋值到b里去)
2,派生类对象可以初始化基类引用。
base& br = d;
3, 派生类对象的地址可以赋值给基类的指针
base* pb = &d;

tips : 声明派生类时,只需要列出它直接基类,构造时也只考虑直接基类的参数表。

C++程序设计--运算符重载

发表于 2016-11-04

目录

1, 运算符重载
2, 赋值运算符的重载
3, 流插入运算符重载
4,自加/自减运算符的重载

运算符重载

作用:对抽象数据类型也能够直接使用C++提供的运算符。
使得程序更简洁。
代码更容易理解。

运算符重载的实质是函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
返回值类型 operator运算符 (形参表)
{
...
}
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0);
Complex operator+(const Complex c1);
Complex operator-(const Complex c1);
double real;
double imag;
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
Complex Complex::operator+(const Complex c1)
{
return Complex(real + c1.real, imag + c1.imag);
}
Complex Complex::operator-(const Complex c1)
{
return Complex(this->real - c1.real, this->imag - c1.imag);
}
//普通类型的运算符重载
Complex operator+ (const Complex c1, const Complex c2)
{
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}

运算符可以重载成普通函数,也能重载为成员函数。前者的参数个数就是对应操作符的目数,而成员函数则是目数减一。

赋值运算符的重载

1,赋值运算符两边的类型可以不匹配。
如:将一个Int类型的变量赋值给一个Complex对象。
2,对一个自己写的类想要进行 = 赋值时,需要重载赋值运算符”=”
3,赋值运算符“=”只能重载为成员函数
一般来说,赋值运算符的重载的返回值是该对象本身的引用,来满足=的意义。

流插入运算符重载

cout本身就是ostream的一个对象,而cout<<其实是在ostream类中对<<进行了重载。
我们如果想通过<< >>来实现自己对应的对象的输入和输出,则可以自己来重载这两个运算符。
一般定义为全局函数,因为iostream类C++已经写好了。
比如实现对复数类的输入输出:

1
2
3
4
5
6
7
8
9
10
11
ostream& operator<<(ostream& o, Complex& c)
{
o << c.real << "+" << c.imag << "i";
return o;
}
istream& operator>>(istream& i, Complex& c)
{
//先读进string里面再分别剥离出real imag
return i;
}

自加/自减运算符的重载

前置和后置有区别,
前置是一元运算符,后置是二元运算符。多出来的一个参数具体是啥无意义,只是为了标记这是一个后置运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class CDemo
{
public:
CDemo(int _n) :n(_n){};
CDemo operator++();
CDemo operator++(int);
//强制类型转化
operator int(){ return n; }
friend CDemo operator--(CDemo&);
friend CDemo operator--(CDemo&, int);
private:
int n;
};
//前置
CDemo CDemo::operator++()
{
n++;
return *this;
}
CDemo operator--(CDemo& d)
{
d.n--;
return d;
}
//后置
CDemo CDemo::operator++(int)
{
CDemo temp (*this);
n++;
return temp;
}
CDemo operator--(CDemo& d, int)
{
CDemo t = d;
d.n--;
return t;
}

注意:上面的demo中int作为一个类型强制转换运算符被重载,Demo s; (int)s等效于s.int()。
类型强制转换运算符被重载时,不能写返回值类型,实际上其返回值就是类型转换所代表的类型。

Tips:
1,C++不允许定义新的运算符。
2,重载后的运算符应该符合日常习惯。
3,重载运算符不能改变运算符的优先级。
4,以下运算符不能被重载。”.” “*” “::” “?”,sizeof
5,重载运算符(), [], ->或者赋值运算符时,只能被声明为类的成员函数。

12
Storm han

Storm han

拿梦想做赌注,我怎么舍得输?

14 日志
GitHub zhihu
© 2016 Storm han
由 Hexo 强力驱动
主题 - NexT.Muse