C++学习的小Tips

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"
}

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<

组合与继承

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)"]

(待续)

时间: 2024-10-13 05:19:42

C++学习的小Tips的相关文章

Windows7驱动调试小Tips

v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);}/* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colb

git 小tips

git 小tips 总结一下最近遇到的一些git问题 1. 将已有代码推送到github,报non-fast-forward的错. 正确的步骤是: cd code git init git remote add origin [email protected]:- git fetch git merge git push origin master 第4,5布可以合并成git pull ,如果不做这步的话会报non-fast-forward 的错. 2 忽略一些文件 某些文件不需要跟踪的可以加入

Python 基础学习 网络小爬虫

<span style="font-size:18px;"># # 百度贴吧图片网络小爬虫 # import re import urllib def getHtml(url): page = urllib.urlopen(url) html = page.read() return html def getImg(html): reg = r'src="(.+?\.jpg)" pic_ext' imgre = re.compile(reg) imgli

[转] 小tips: 使用&amp;#x3000;等空格实现最小成本中文对齐 ---张鑫旭

by zhangxinxu from http://www.zhangxinxu.com本文地址:http://www.zhangxinxu.com/wordpress/?p=4562 一.重见天日第二春 11年的时候,写了篇文章“web页面相关的一些常见可用字符介绍”,这篇文章里面藏了个好东西,就是使用一些空格实现个数不等的中文对齐或等宽.见下表: 字符以及HTML实体 描述以及说明   这是我们使用最多的空格,也就是按下space键产生的空格.在HTML中,如果你用空格键产生此空格,空格是不

最近工作的一点小tips

最近工作比较忙,但也积累了一些小tips,比较杂,不成系统,也并不很深入,就开一篇笼统的先记录一下,以后再深入挖掘. 1.-webkit-tap-highlight-color -webkit-tap-highlight-color 是一个 不规范的属性,它没有出现在 CSS 规范草案中. 当用户点击iOS的Safari浏览器中的链接或JavaScript的可点击的元素时,覆盖显示的高亮颜色. 该属性可以只设置透明度.如果未设置透明度,iOS Safari使用默认的透明度.当透明度设为0,则会禁

机房收费系统一些小Tips

做重构已经有一段时间了,在这段时间里面学了很多新东西,感觉自己的鸡窝做得越来越好了.这里,写下一些小Tips,记录下一些自己感觉很有意思的东西. 一,还是分层的问题 在上下机加完模式后,总感觉很诡异,我的上下机是加完模式后的,第一次这么做,还有点儿生疏,看了半天,才发现我上下机的逻辑居然在U层,如图: 三个类定义在了U层: 然后在下机的时候调用: 虽然能实现下机,但是具体的逻辑还是在U层里面,因为我是在U层定义在U层引用的.想完这个,深感自己对分层的理解才刚刚开始唉~ 当自己不知道该把某块东西放

微信小程序最新开发资源汇总,对学习微信小程序的新手有一定帮助

微信小程序最新开发资源汇总,希望给想学习或正在学习微信小程序开发的同学们带来一定帮助,汇总的小程序资源有点繁杂,各种类型的小程序demo都有,大家可以选择自己想要的demo进行下载学习.这些微信小程序资源大多是整理自github,如果可以,希望大家能够给github上的原作者一颗star,感谢原作者的无私奉献. 这里整理的是资源的原帖子,下载链接也在帖子里,当然本人也只体验了部分demo,有兴趣的同学可以都下载试试. 下载地址: 仿微信聊天,朋友圈小程序源码wepy框架开发的小程序商城源码,功能

与大家分享学习微信小程序开发的一些心得

因为我也才开始学习微信小程序不久,下文也是现在的一时之言,大家有不同的想法也可以在评论里共同交流讨论,希望文章能给大家提供一点点帮助. 最近接触到了一些前端框架,像Vue.js,React,发现小程序的框架体系跟它们很像.它们都推崇模块化,组件化,数据与元素绑定.这样没有繁琐的DOM操作,组件之间完全分离,样式和逻辑全都封装在模板里,别人写好的组件可以拿来直接用,这会明显地提高我们前端开发的速度. 另外,微信小程序界面小,样式好调.这意味着,我们从纸面原型到能跑的APP的实现将会是很快的,所以我

小tips: 使用&amp;#x3000;等空格实现最小成本中文对齐

一.重见天日第二春 11年的时候,写了篇文章“web页面相关的一些常见可用字符介绍”,这篇文章里面藏了个好东西,就是使用一些空格实现个数不等的中文对齐或等宽.见下表: 字符以及HTML实体 描述以及说明   这是我们使用最多的空格,也就是按下space键产生的空格.在HTML中,如果你用空格键产生此空格,空格是不会累加的(只算1个).要使用html实体表示才可累加.为了便于记忆,我总是把这个空格成为“牛逼(nb)空格(sp – space)”,虽然实际上并不牛逼.该空格占据宽度受字体影响明显而强