C++学习的小Tips
2018-06-17 21:54:43来源:未知 阅读 ()
Classes的两个经典分类
Class without pointer member(s)
complex
Class with pointer member(s)
string
Header中的防卫式声明
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
//code
#endif
inline function
函数若在class body内定义完成,便自动成为inline function的候选人
class body之外定义的函数,需要加上inline关键字,以此建议编译器将其编译为inline function
constructor(ctor, 构造函数)
class complex
{
public:
complex (double r = 0, double i = 0) // 默认实参
: re (r), im (i) // 初值列
{ }
};
构造函数中用初值列初始化变量,而最好不在函数体中用"="来赋值
一个变量的构造有两个阶段:初始化,赋值;所以使用初值列的机制效率更好(省去了一个赋值阶段)
ctor可以有多个重载
ctor放在private区
Singleton单例模式
class A
{
public A& getInstance();
setup() {...};
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a;
return A;
}
A::getInstance().setup();
外界不能直接创建对象的实例,只能通过class内的函数来创建
const member function(常量成员函数)
double real() const { return re; }
这里的const关键字声明将一定不改变类内的成员变量re
例如:若不写上述的const,以下代码块将会编译错误
{
const complex c1(1, 2)
cout << c1.real() << c1.imag();
}
因为使用者创建了一个const的对象c1,即规定c1是一个常量,其中的成员变量也将是const的,没有可能被改变;而在调用成员函数real()时,并未声明其为一个常量成员函数,即类内的成员变量re将"有可能"被改变,这是矛盾的
参数传递:pass by value VS. pass by reference(to const)
按值传递将原封不动的复制参数,按引用传递相当于传递参数的地址(底层是指针);所以通常传引用效率更好
例如:complex& operator += (const complex&);
目的是按reference传入一个complex对象(的引用),并声明传入的对象本身禁止被函数修改,[pass by reference(to const)]
参数的传递尽量by reference
返回值传递:pass by value VS. pass by reference
什么情况下可以pass by reference(to const)?
什么情况下可以return by reference?
例如后面的"__doapl"函数,它的第一参数将会被改动,第二参数不会被改动
此时第二参数可以pass by reference(to const),第一参数可以return by reference
下面是一个return by reference的正确例子:
inline complex& __doapl(complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex& complex::operator += (const complex& r)
{
return __doapl(this, r);
}
注意第一参数传入的是一个指针,指向的对象不是函数体内临时创建的local变量,而是函数外已经存在的某个变量,因而可对其return by reference
什么情况下不能return by reference?
函数体内的local变量不能return by reference。因为当函数结束,其local变量也消亡了,若return by reference传出的一个"地址"指向的内容已经坏掉了
例如:
inline complex& __doapl(complex* ths, const complex& r)
{
return (ths->re + r.re);
}
friend(友元)
类中的private成员re与im不能被外界直接取用(可通过类内函数取用),但类内的友元函数可以直接取得其"朋友"的私有成员
class complex
{
private:
double re, im;
friend complex& __doapl(complex*, const complex&);
};
inline complex& __doapl(complex* ths, const complex& r)
{
ths->re += r.re; //自由取得friend的private成员
ths->im += r.im;
}
相同class的各个objects互为friends(友元)
class complex
{
public:
int func(const complex& param)
{ return param.re + param.im; }
// 这里直接取用了"朋友"的私有成员
private:
double re, im;
};
{
complex c1(2,1);
complex c2;
// c1与c2互为friends
c2.func(c1);
}
编写一个class的小总结
数据一定放private中
参数传递与返回值传递尽可能by reference
在class body中的成员函数,需要加const的一定要加(常量成员函数)
ctor尽量用它的初值列机制
Big Three 三个特殊函数
拷贝构造,拷贝赋值,析构函数
一定要在拷贝赋值中检查是否self assignment
inline String& String::operator=(const String& str)
{
if(this == &str)
return *this;
//...
}
内存管理
new:先分配内存,再调用ctor
Complex* pc = new Complex(1,2);
编译器转化为:
Complex *pc;
void* mem = operator new(sizeof(Complex)); //分配内存
pc = static_cast<Complex*>(mem); //强制类型转换
pc->Complex::Compelex(1,2); //调用构造函数
//Complex::Compelex(pc相当于隐藏在此的一个this指针,1,2);
delete:先调用dtor,再释放内存
delete pc;
编译器转化为:
Complex::~Complex(pc); //析构函数
operator delete(pc); //释放内存
注:"operator new()"和"operator delete()"是特殊的C++系统函数;前者用于分配内存,其内部调用malloc();后者释放内存,其内部调用free()
静态数据成员与静态成员函数
class内静态的数据只存在一份(存在于"全局/静态存储区")
静态函数没有this指针,所以静态函数只能处理位于全局/静态存储区的静态数据,而不能访问class内的非静态数据成员
例如:设计一个银行账户
class Account
{
public:
static double m_rate; //利率
static void set_rate(const double& x)
{ m_rate = x; }
};
double Account::m_rate = 0.01; //静态数据成员的赋值方式1(静态数据成员的初始化)
int main()
{
Account::set_rate(0.02); //静态数据成员的赋值方式2(通过class name调用静态函数)
Account a; //静态数据成员的赋值方式3(通过object调用静态函数)
a.set_rate(0.03);
}
class template类模板
示例:
template<typename T>
class complex
{
public:
complex(T r = 0, T i = 0)
:re (r), im (i)
{ }
private:
T re, im;
};
{
complex<double> c1(2.0, 1.0); //class中的"T"会全部替换成"double"
complex<int> c2(2, 1); //class中的"T"会全部替换成"int"
}
补充一个类模板的例子:
const std::size_t DefaultStackSize = 1024;
template<typename T, std::size_t n = DefaultStackSize>
class Stack
{
public:
void Push(const T const& element);
...
private:
std::vector<T> m_Members;
std::size_t m_nMaxSize = n;
};
类模板参数可以是任意类型名或常量(常量仅限int或enum类型);
模板实参可以是一个int型或enum型的常量(此处是size_t,其实质是无符号整型;size_t是标准C库中定义的,在32位系统中为unsigned int,占4个字节;在64位系统中为 long unsigned int,占8个字节);
n是编译时定义的常量,n可以有默认值;
成员变量m_nMaxSize用n进行了初始化;
function template函数模板
例如有一个stone类:
class
{
public:
stone(int w, int h, int we)
:width(w), height(h), weight(we)
{ }
bool operator < (const stone& rhs) const
{ return weight < rhs.weight; }
private:
int width, height, weight;
};
要创建两个stone对象,并且比较stone的重量:
stone r1(1,2,3), r2(2,3,4);
r3 = min(r1, r2);
若有函数模板:
template<class T>
inline const T& min(const T& a, const T& b)
{
return b < a ? b : a;
}
则函数模板中的"T"将会全部替换成类名"stone"
因为r1和r2都是stone类,编译器会对function template进行argument deduction参数推导
而在进行对象的大小比较时,因为"T"为"stone",于是调用stone::operator<
补充一个函数模板的例子:
template <typename T> T Max(T a, T b)
{
return a>b?a:b;
}
1.对于不同的实参类型,模板函数定义了一族函数;
2.当传递模板实参时,函数模板依据实参的类型进行实例化;
3.可以显式指定模板的实参类型,例如:
对函数模板
template <typename T>
inline T const& Max(const T const& a, const T const& b)
{...}
以下代码
Max<double>(1,2)
函数模板会实例化一个函数
inline double const& Max(const double const& a, const double const& b)
{...}
此时参数1,2也将被转换成double型;
4.函数模板可以重载;
5.当重载函数模板时,将改变限制在:显式指定模板参数;
6.所有的重载版本的声明必须位于它们被调用的位置之前;
组合与继承
Composition(复合) 表示”has-a”
复合关系下的构造与析构
构造由内而外
Container的构造函数首先调用Component的default构造函数,然后再执行自己
析构由外而内
Container的析构函数首先执行自己,然后再调用Component的析构函数
Delegation(委托) or Composition by reference
桥接模式(Handle/Body模式) or pImpl(Pointer to Implementation)
有一个String类,除了包含class必要的声明之外,实际的实现方式定义在另一个类StringRep中,在String类中设置一个指针指向具体实现的类StringRep
//file String.hpp
class StringRep; //声明
class String
{
public:
String(); //默认构造
String(const char* s); //拷贝构造
String(const String& s); //拷贝构造
String& operator=(const String& s); //拷贝赋值
~String(); //析构
private:
StringRep* rep; //Handle/body(pImpl)
}
//file String.cpp
#include "String.hpp"
namespace {
class StringRep {
friend class String;
StringRep(const char* s);
~StringRep();
int count;
char* rep;
};
}
Inheritance(继承) 表示"is-a"
构造由内而外
Derived(派生类/子类)的构造函数首先调用Base(基类/父类)的default构造函数,然后执行自己
析构由外而内
Derived的析构函数先执行自己,然后调用Base的在析构函数
base class的dtor必须是virtual,否则会出现undefined behavior
虚函数与多态
Inheritance(继承) with virtual functions(虚函数)
non-virtual函数:你不希望derived class重新定义(override/复写)它;
virtual函数:你希望derived class重新定义(override/复写)它,并且你对它也有默认定义;
pure virtual函数:你希望derived class一定要重新定义(override/复写)它,你对它没有默认的定义。
conversion function 转换函数
class Fraction
{
public:
Fraction(int num, int den=1)
:m_numerator(num), m_denominator(den) {}
operator double() const
{
return (double)(m_numerator*1.0 / m_denominator);
}
private:
int m_numerator; //分子
int m_denominator; //分母
};
在执行以下代码块时:
{
Fraction f(3,5);
double d=4+f;
}
先构造一个Fraction 3/5,然后尝试double与Fraction相加,并得到一个double值
编译器会先检查是否定义了double+Fraction的operator+,若是即调用operator+函数,否则编译器会检查class Fraction是否定义了Fraction to double的转换函数,若是则调用该转换函数,否则编译错误
non-explicit-one-argument ctor
class Fraction
{
public:
Fraction(int num, int den=1)
:m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction& f)
{
return Fraction(...);
}
private:
int m_numerator; //分子
int m_denominator; //分母
};
在执行以下代码块时:
{
Fraction f(3,5);
Fraction d2=f+4;
}
左操作数是Fraction、右操作数是double,而operator+函数左操作数是Fraction(隐含的this指针)、右操作数也是Fraction;因此编译器会尝试调用non-explicit ctor将"4"转"Fraction(4,1)",然后调用operator+
conversion function 与 non-explicit-one-argument ctor并存
class Fraction
{
public:
Fraction(int num, int den=1)
:m_numerator(num), m_denominator(den) {}
//conversion function
operator double() const
{ return (double)(m_numerator*1.0 / m_denominator); }
//non-explicit-one-argument ctor
Fraction operator+(const Fraction& f)
{ return Fraction(...); }
private:
int m_numerator; //分子
int m_denominator; //分母
};
在执行以下代码块时:
{
Fraction f(3,5);
Fraction d2=f+4;
}
因为二者任一均可调用,产生二义性,编译出错
explicit-one-argument ctor
class Fraction
{
public:
explicit Fraction(int num, int den=1)
:m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction& f)
{ return Fraction(...); }
private:
int m_numerator; //分子
int m_denominator; //分母
};
在执行以下代码块时:
{
Fraction f(3,5);
Fraction d2=f+4; //[error] 改成"Fraction d2=f+Fraction(4);"可通过
}
将会报错,因在"explicit"关键字的限定下,operator+函数的实参一定要是Fraction类型,就算仅需要一个int型参数也能构造Fraction(例如Fraction(4)也就是4/1),但explicit仍不允许此行为发生
pointer-like class 关于智能指针
智能指针将一个一般指针封装到一个class中,并且重载*与->,使得智能指针不仅能实现一般指针的操作,而且能在class内扩展其它的操作
智能指针的一般框架:
template<class T>
class shared_ptr
{
public:
T& operator* () const
{ return *px; }
T* operator-> () const
{ return px; }
shared_ptr(T* p) : px(p) {} //构造函数通常接受一个"真正的指针"来构造该"智能指针"
private:
T* px; //指向class T类型的指针
//...
};
相当于有一个"智能指针" shared_ptr(实际是一个class),其中包含一个"真正指针" px(私有变量;是一个指向class T类型的指针)
使用示例:
struct Foo
{
//...
void method(void) {...}
};
shared_ptr<Foo> sp(new Foo);
Foo f(*sp);
sp->method();
首先,声明一个指向class Foo类型的智能指针sp;
然后,*sp会返回*px(class Foo对象)的reference,以此通过class Foo的拷贝构造函数创建class Foo对象f
最后,sp->会返回px(指向class Foo类型的一般指针),所以sp->method()也就相当于px->method()
pointer-like class 关于迭代器
struct __list_node //链表元素
{
void* prev;
void* next;
T data; //这里T假定为struct Foo
}
struct __list_iterator //链表迭代器
{
//...
typedef __list_node<T>* link_type;
link_type node; //指向__list_node object的指针
//typedef T& reference
reference operator*() const
{
return (*node).data;
}
//typedef T* pointer
pointer operator->() const
{
return &(operator*()); //会调用class内的operator*函数,返回一个T对象(的reference)
//然后再用&取地址,返回一个T*类型的指针
}
//此外,迭代器不仅需要处理*和->,一般还需要处理++ --操作等,这里不扩展了...
};
使用示例:
list<Foo>::iterator ite;
ite->method();
首先,申请一个迭代器ite,该迭代器是元素类型为class Foo的链表
然后,有一个操作:ite->method(),通过迭代器ite调用class Foo的成员函数method()
意思是调用Foo::method();
相当于(*ite).method();因为*ite会获得一个Foo object;
相当于(&(*ite))->method();因为&(*ite)会获得Foo object的指针,最后即通过该指针调用method();
因为"ite->method()"最终相当于"(&(*ite))->method()",为了响应这种操作需求,这也就是为何struct __list_iterator内的operator->函数写法的原因 ["&(operator*())"响应"&(*ite)"]
partial specialization,模板偏特化 - 个数的偏
template<typename T, typename Alloc=...>
(1)
class vector
{
...
};
绑定第一个模板参数为bool类型:
(2)
class vector<bool, Alloc=...>
{
...
};
这样,当模板的第一个参数限定为bool类型时,将会使用代码片(2),而不是(1)
注:只能从第一个模板参数开始绑定一个或连续的多个参数
partial specialization,模板偏特化 - 范围的偏
template <typename T>
(1)
class C
{
...
};
当模板参数范围限定为指向T类型的指针:
(2)
class C<T*>
{
...
};
这样,当以T类型创建class C时将使用(1);而以T*类型创建class C时将使用(2)
例如:
C<string> obj1; //使用(1)创建obj1
C<string*> obj2; //使用(2)创建obj2
reference与pointer的一些对比
指针在声明时可以不初始化,而引用在声明时就必须对其初始化,并且在对引用初始化之后,不能再给它赋值
例如:
int x=0;
int* px = &x; //ok
int* px;
px = &x; //ok
int& rx = x; //ok
int& rx; //no;必须在声明时初始化
int y;
rx = y; //no;在初始化后不能再对其赋值
object与其reference的大小相同,地址也相同:
sizeof(rx) == sizeof(x)
&rx == &r
(虽然编译器会这么告诉你,但实际这是假象)
reference通常不用于声明变量,而通常用于参数类型(parameters type)和返回类型(return type)的描述
以下被视为"same signature",二者不能同时存在:
double imag(const double& im) ... {...}
double imag(const double im) ... {...}
例如:
double d=1.0;
imag(d); //ambiguity
若以上两个函数并存,imag(d)即产生二义性,因为二者均可调用,编译器无法决定选择哪一个
扩展:
问:const是不是函数签名的一部分?
答:是
例如:
int f(...) const {...}
int f(...) {...}
它们被认为是两个不同的函数(函数签名不同)
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:【noip2007】 树网的核
下一篇:【tyvj1520】 树的直径
- C++ 转换函数搭配友元函数 2020-06-10
- C++ 自动转换和强制类型转换(用户自定义类类型) 2020-06-10
- C++ rand函数 2020-06-10
- C++ 友元函数 2020-06-10
- C++ 运算符重载 2020-06-10
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash