【问题】
在一个2k×2k个方格组成的棋盘中,若恰有一个方格与其他方格不同,则称该方格为一个特殊方格,该棋盘称为特殊棋盘。
求用L型骨牌覆盖特殊棋盘的一个方法。
【解答】
int L;//骨牌编号
void print(int n)
{
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
cout<<LBord[i][j]<<"\t";
}
cout<<"\n";
}
}
void LankeHelper::CoverChessbord(int or, int oc, int sr, int sc, int size)
{
if(size>128||(size & (size-1)!=0))
{
cout<<"size 有误!"<<endl;
}
if(size==1) return;
int s = size/2;
int t = ++L;
//下面考虑特殊骨牌的位置,依次是左上角、右上角、左下角、右下角
if(sr < or + s && sc < oc + s)
{
CoverChessbord(or, oc, sr, sc, s);
}
else
{
LBord[or+s-1][oc+s-1]=t;
CoverChessbord(or,oc,or+s-1,oc+s-1,s);
}
if(sr < or + s && sc >= oc + s)
{
CoverChessbord(or, oc + s, sr, sc, s);
}
else
{
LBord[or+s-1][oc+s]=t;
CoverChessbord(or,oc+s,or+s-1,oc+s,s);
}
if(sr >= or + s && sc < oc + s)
{
CoverChessbord(or + s, oc, sr, sc, s);
}
else
{
LBord[or+s][oc+s-1]=t;
CoverChessbord(or+s,oc,or+s,oc+s-1,s);
}
if(sr >= or + s && sc >= oc + s)
{
CoverChessbord(or+s, oc+s, sr, sc, s);
}
else
{
LBord[or+s][oc+s]=t;
CoverChessbord(or+s,oc+s,or+s,oc+s,s);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
LankeHelper lh;
L=0;
LBord[1][2]=0;
lh.CoverChessbord(0,0,1,2,8);
print(8);
system("pause");
return 0;
}
【结果】(43-1)/3=21个骨牌,符合预期。
本文链接
在C++中当创建一个空类时,C++就会默认的为这个类创建4个函数:默认的构造函数、析构函数、拷贝构造函数、以及赋值操作符。本文参考Effective C++介绍这几个函数。
目录:
1. 函数的原型以及创建函数的时机
2. 赋值操作符存在的问题
3. C++0x的新变化
1. 函数的原型以及函数创建的时机
C++中创建一个空类:
默认会生成4个函数,其函数的原型如下:
Empty() { ... }
Empty(const Empty& rhs) { ... }
~Empty() { ... }
Empty& operator=(const Empty& rhs) { ... }
说明:1) 这些函数只有在需要调用的时候,编译器才会生成。2) 4个函数都是public的。3) 4个函数都是inline的(即函数定义在类的定义中的函数)。4) 如果你显式的声明了这些函数中的任何一个函数,那么编译器将不再生成默认的函数。
比如,当遇到下列语句时,函数会被编译器生成:
//对象销毁时,析构函数
Empty e2(e1); //拷贝构造函数
e2 = e1; //赋值运算符
另外,还存在两种默认的函数:就是取地址运算符和取地址运算符的const版本,这两个函数在《Effective C++》中没有提及。
Empty* operator&() { ... }
const Empty* operator&() const { ... }
这两个函数是确实存在的,正如下面的代码可以正常工作:
class Empty {
};
int main(int argc, char** argv)
{
Empty a;
const Empty *b = &a;
printf("%p\n", &a); //调用取地址运算符
printf("%p\n", b); //调用const取地址运算符
}
一个容易被忽略的问题:自定义的拷贝构造函数不仅会覆盖默认的拷贝构造函数,也会覆盖默认的拷贝构造函数。下面的代码是编译不过的,用户必须再显式的定义一个无参的构造函数。
public:
Empty(const Empty& e) { } //拷贝构造函数
};
int main(int argc, char** argv)
{
Empty a;
}
2. 赋值操作符存在的问题
赋值操作符函数的行为与拷贝构造函数的行为基本是相同的,编译器生成赋值操作符函数是有条件的,如果会产生无法完成的操作,编译器将拒绝产生这一函数。那么什么时候编译器无法完成赋值这一行为呢?考虑如下情形(来源Effective C++):
class NameObject {
public:
NameObject(std::string& name, const T& value);
private:
std::string& nameValue; //引用成员变量
const T objectValue; //const成员变量
};
然后考虑下面的语句会发生什么事:
std::string oldDog("xxx");
NameObject<int> p(newDog, 2);
NameObject<int> s(oldDog, 10);
p = s; //将会发生什么?
赋值语句之前,p.nameValue指向newDog, s.nameValue指向oldDog。那么赋值之后呢?p.nameValue应该指向s.nameValue指向的对象吗?但是C++有一条规定:引用不能改指向另外一个对象。
对于变量objectValue,C++规定:更改const成员是不合法的。
因此如果上面两种情形中的任何一种发生了,C++编译器给出的响应是:拒绝编译这一行的赋值动作。如果你这么做了,C++编译器会报错。如果你执意要进行赋值操作,那么可以自己定义一个赋值操作符重载函数。
3. c++0x中的新变化
C++0x中引入了“右值引用”和“移动语义”的概念,可以实现对右值的引用。(左值和右值的解释可以见http://amyz.itpub.net/post/34151/411832)
移动语义,简单来说,就是在一个右值对象的生命期结束之前,将其资源偷过来,为我所用。有关移动语义的详细内容这里不做详述,大家可以参见csdn上一篇文章 http://blog.csdn.net/pongba/article/details/1684519。这里要说明的是移动构造函数和移动赋值运算符。
1. 移动构造函数和移动赋值运算符重载函数不会隐式声明,必须自己定义。
2. 如果用户自定义了拷贝构造函数或者移动构造函数,那么默认的构造函数将不会隐式定义,如果用户需要,也需要显式的定义。
3. 移动构造函数不会覆盖隐式的拷贝构造函数。
4. 移动赋值运算符重载函数不会覆盖隐式的赋值运算符重载函数。
本文链接
C++中的默认值
在C++中,编译器不会默认检查对象是否被初始化,这就会导致对象在不初始化的情况下被使用,造成不可预计的后果。因此,C++程序员必须保证自己所使用的变量已经有了一个明确的初始值。
一般情况下,在C++中,如果一个变量为全局代码段,则一般编译器会给一个初始的定值,如int类型会赋0。而定义为局部变量时,则默认值为随机数。但是在vs调试时,DEBUG版本和Release版本版本还有区别: http://www.dewen.org/q/8450/C%2B%2B+%E4%B8%AD+bool+%E5%8F%98%E9%87%8F%E7%9A%84%E9%BB%98%E8%AE%A4%E5%80%BC%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F
C++提供的标准模板库,会保证变量会有一个默认的初始值(非随机值),int初始化为0,string初始化为空串等。
确保对象使用前被初始化
尽管程序员对C++的默认行为很了解,但是默认的值和机器,编译器等都有关系,不确定性太多。因此最好的做法是,在使用对象之前,永远手动给对象初始化。如:
const char* text = "ABC";
double d;
std::cin >> d;
如果是程序员自定义的类型,则满足以下规则:确保每一个构造函数都将对象的每一个成员初始化。但是要注意一点,“赋值”和“初始化”这两个操作在构造函数中很容易被混淆。看下面的例子:
public:
A(const std::string& name, const std::list<std::string>& namelist);
private:
std::string name;
std::list<std::string> namelist;
int nameCount;
};
A::A(const std::string& n, const std::list<std::string>& nl)
{
name = n; //这些都是赋值,不是初始化。
namelist = nl;
nameCount = 0;
}
在上面的代码中,name,namelist在构造函数中的动作都是赋值而不是初始化。初始化的动作发生在进入构造函数本体之前。 但是nameCount属于基本类型,不一定会在本构造函数之前经过初始化。
对于构造函数一个比较好的写法是,采用C++定义的成员初值列(member initialization)来执行:
:name(n), namelist(nl), nameCount(0)
{
}
现在,这些都是初始化操作,name, namelist都调用了其copy构造函数进行初始化,且比赋值操作的构造函数效率较高,因为赋值的构造函数在赋值之前已经会对一些成员调用了默认的构造方法进行初始化。对大多数类型来说,直接调用copy构造函数比先调用默认构造再调用copy赋值都是高效的。
使用初值列还有一个好处就是:如果成员变量的类型是const或references,那么它们就不能在构造函数中进行赋值,而必须进行初始化。因此简单的做法是:永远使用初值列。而且在初值列中列出所有成员变量,即使有些不需要进行初始化,但是这样做的话利于维护和代码的阅读。
需要注意一点:C++的成员初始化的次序是固定的,它总是按照其声明的顺序进行初始化。在上面的例子中,初始化的顺序永远是:name, namelist, nameCount,而且这个顺序和初值列中写的顺序无关。(为了避免混淆,初值列中的顺序最好和声明的顺序相同)
“不同编译单元内定义之non-local static对象”的初始化
static对象是指:global对象,定义在namespace作用域的对象,在class,函数,文件作用域中的static对象。其中,函数内的static对象是local static,其它的static对象是non-local static对象。static对象在程序结束时被销毁。不同编译单元,是指产生的目标文件不同,基本上一个编译单元是指其源码文件加上其头文件。
我们遇到的问题是:如果在一个编译单元内的某个non-local static对象初始化用到了另一编译单元内的non-local对象,但是它所用到的这个对象尚未被初始化,就会出现错误。C++对这一初始化顺序并未进行定义。
如下面的例子(effective C++原书的例子):
public:
...
std::size_t numDisks() const;
...
};
extern FileSystem tfs;
在另一个编译单元中有如下代码:
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks = tfs.numDisk();
...
}
Directory tempDir( params );
那么,上述代码就必须要求tfs在tempDir之前进行初始化。但是C++编译器对这一顺序并无明确定义,因为要明确分析这一顺序是非常困难的。然而我们可以做一个设计来解决这一问题:将每个non-local static对象搬到自己的专属函数内,然后返回该对象的引用。这一设计类似Singleton模式。因为C++会保证:函数内的local static对象会在该函数被首次调用时被初始化。而且还有一个好处,如果从来没有调用过这个函数,这个对象将不会被初始化,减小析构成本,对于上述例子的non-local static对象不会做到这种效果。
用这个设计修改上述示例:
FileSystem& tfs()
{
static FileSystem fs;
return fs;
}
class Directory {}; //同前
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisk();
...
}
Directory& tempDir()
{
static Directory td;
return td;
}
上述三点:
1. 为内置类型对象进行手工初始化,因为C++不保证初始化它们。
2. 构造函数最好使用成员初值列(member initialization list)。初值列的成员变量,排列次序应与在class中声明的次序相同。
3. 以local static对象替换non-local static对象。
本文链接