C++面向对象学习笔记

Object Based (基于对象) vs. Object Oriented (面向对象)

Object Based : 面对的是单一 class 的設計

Object Oriented : 面对的是多重 classes **的设计,classes classes 之间的关系。

类的两个经典分类:
  • Classes without pointer members
  • Classes with pointer members
一、Classes without pointer members

1、头文件的防卫式声明

#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif

作用:防止由于同一个头文件被包含多次,而导致了重复定义。

建议:为了不必要的麻烦,最好在每个头文件上加上这样的防卫式声明。即使现在只有一个cpp使用该头文件,万一之后再有别的cpp使用该头文件造成不必要的麻烦。
注意:防卫式声明一般这样写:(2个下划线,不是1个)+头文件名的大写(不包括.h)+(2个下划线,不是1个),例如头文件为head.h时,就使用__HEAD__,这是为了防止重复。

2、inline (内联)函数

内联函数 更快更好 但最终是否成为inline由编译器决定

函数若在类内完成定义,便自动成为inline候选函数

3、access level(访问级别)

数据 data 一般是 private , 大部分函数要给外界用 public

4、构造函数:

下面是构造函数特有的写法 ,(initialization list)初始化列表的形式 ,这样写更好,效率更高

  • 变量的生成有两个阶段
    • 1.初始化,冒号后
    • 2.赋值:大括号里

注意:不带指针的类多半不用写析构函数

complex (double r = 0, double i = 0)
  : re (r), im (i) { }

与下面的形式是不同的(这是赋值操作 assignments):

complex (double r = 0, double i = 0)
{ re = r; im = i; }

不允许此种写法,上面的构造函数已经有默认值了,冲突

complex () : re(0), im(0) { }

5、设计模式Singleton单例模式,构造函数放在private内,不允许被外界实例化对象,外界只允许用一份,使用static关键字

class A {
public:
  static A& getInstance();
  setup() { ... }
private:
  A();
  A(const A& rhs);
  ...
};

A& A::getInstance()
{
  static A a; // 自己创建了一份自己,且只有一份
  return a;
}

A::getInstance().setup(); // 外界调用时要靠getInstance()取得唯一的一个实例

6、const成员函数(常量成员函数)

  • 好习惯:不改变成员变量的成员函数一定要加const,否则使用时可能会报错。

  • const修饰函数 放在()后面 {}前面 const加在对象前面,修饰变量,不允许修改

    double real () const { return re; }
    double imag () const { return im; }
    
const complex c1(1, 2);
cout << c1.real();  // 若complex::real()没加const,const对象调用非const函数,会报错
cout << c1.imag();

7、参数传递

尽量不要使用值传递 ,整个传进来,很慢 引用传递 相当于指针传递 速度很快 尽量所有的参数传递都用引用传递 const+引用的形式 表明传进来的参数不能被函数内部修改 不希望被修改

8、返回值传递

返回值的传递尽量返回引用 & 可以的情况下 尽量用引用(返回局部变量不可以返回引用,其余情况都尽量返回引用。

如果函数内部创建一个临时变量,返回此临时变量时就不可以返回引用,因为一旦离开此函数,临时变量就消失了

9、友元

  • 友元函数可以自由取得类的private成员,友元可以直接拿数据 否则用函数去拿 但打开了封装
  • 相同class的各个对象(objects)之间互为友元
class complex
{
public:
	complex (double r = 0, double i = 0)
	: re (r), im (i) 
	{ }
	int func(const complex& param)
	{ return param.re + param.im; }
private:
	double re, im;
};

{
complex c1(2,1);
complex c2;
c2.func(c1);
}

10、操作符重载

  • 成员函数 由this指针

有左右两个操作数的操作符,一定是作用到左边操作数上:

c2 += c1;// 这里this==c2

所有的成员函数一定带着一个隐藏的参数this,代表调用者(例如上面的c2)右边加到左边,this是个指针

inline complex&
__doapl(complex* ths, const complex& r)
{
	...
	return *ths;//这里的返回形式并不关心接收者是以引用的形式还是值的形式接受,使用引用会更快。
}
//连加操作 c2既做左值又作右值 ,返回值不可以是void  所以设计函数返回值的时候要特别注意
c3 += c2 += c1;
inline complex&
complex::operator += (const complex& r)
{
return __doapl(this,r);
}
  • 非成员函数 (全局函数) 无this指针
inline complex operator + (const complex& x, const complex& y) {
  return complex (real(x) + real(y), imag(x) + imag(y));  // 返回local object,不能return by reference
//这里可以返回引用    
inline complex
operator + (const complex& x)
{
	return x;
//这个函数绝不可return by reference,返回的一定是 local object。  
inline complex
operator - (const complex& x)
{
	return complex (-real (x), -imag (x));
}

临时对象 (temp object)形式:typename() 没有名字,就像是int i; 一样

//这里也是临时对象 没有名称  进行到下一行生命就消失了
complex();
#include <iostream.h>
ostream&
   //   os一直在变化这里不能加const
operator << (ostream& os, const complex& x)
{
	return os << '(' << real (x) << ',' 
		<< imag (x) << ')';
}

{
complex c1(2,1);
    //C++所有符号都作用到左边参数上 对于特殊的操作符<< 只能写成非成员函数 global function
cout << conj(c1);
    //连续输出  不能返回void  可以返回引用 os不是临时对象 上面的操作符重载函数
cout << c1 << conj(c1);
}

注意:

// 这里传引用和值是相同的效果  double4个字节
complex (double r = 0, double i = 0)
: re (r), im (i) 
{ }

小结:

  • 构造函数会用initialization list
  • 该加const的成员函数要加const
  • 参数传递尽量pass by reference
  • 返回值尽量return by reference
  • 数据放在private区
  • 成员函数和全局函数都可以声明为inline 实际上要看编译器是不是真的能变成inline函数
  • 临时对象形式,不能返回引用
  • 友元函数直接访问成员变量
二、Classes with pointer members

1、带有指针的类 一定要自己写拷贝函数

class String
{
public:
  String(const char* cstr = 0);
  String(const String& str);
  String& operator=(const String& str);
  ~String();
  inline char* get_c_str() const { return m_data; }
private
  char* m_data; // 动态分配内存

Big Three,三个特殊函数

//拷贝构造   接受的是类的类型   自己的东西
String(const String& str);
//操作符重载   拷贝赋值   传入的也是自己类的东西 只要类中有指针  拷贝构造 和 拷贝赋值  析构函数一定要写
String& operator=(const String& str);
// 加const
char* get_c_str() const { return m_data; }
//指针  动态分配内存
char* m_data;

构造函数:

inline String::String(const char* cstr = 0)
{
  if (cstr) {
    m_data = new char[strlen(cstr) + 1];
    strcpy(m_data, cstr);
  } else {
      //分配内存 放置空字符串
    m_data = new char[1];
    *m_data = '\0';
}

析构函数:

inline ~String()
{
  delete[] m_data;  // 释放动态分配的内存,否则内存泄漏
}

{
	String s1(), 
	String s2("hello");
	String* p = new String("hello");
    //3个字符串要调用3次析构函数 s1和s2在离开作用域的时候 自动调用析构函数释放p
	delete p;
}

2、注意:带有指针的类一定要有拷贝构造和=操作符重载函数

浅拷贝只是指针指向的内存相同 ,会造成 内存泄漏问题

  • 拷贝构造函数:
inline String::String(const String& str)
{
   	//直接取另一个 object 的 private data.(它们互为 friend)  +1  是结束符号
  m_data = new char[strlen(str.m_data) + 1];
  strcpy(m_data, str.m_data);
}
  • 拷贝赋值函数

    与拷贝构造函数的区别:拷贝赋值时,这个对象已经存在,所以需要先清空再拷贝

inline String::String operator= (const String& str)
{
    //一定要写 检测自我赋值,传进来的指针和 操作符左边的指针(原来的指针)如果指向同一块内存空间,直接返回,效率高。否则没有检测自我赋值,在真的自赋值时会发生不确定行为,造成错误。
  if (this == &str) // 检测自我赋值,否则自我复制会出错
    return *this; 
    //分为3步 首先delete s2  创造和s1相同的动态空间  拷贝过来
  delete[] m_data;
  m_data = new char[ strlen(str.m_data) + 1];
  strcpy(m_data, str.m_data);
  return *this;

3、stack(栈) 和 heap (堆)

**Stack,是存在于某作用域 (scope) 的一块內存空间 (memory space)。**例如当你调用函数,函数本身会形成一个stack 用来放置它所接收的参数,以及返回地址。在函数内部声明的任何变量、在其使用的内存块都来自stack。

**Heap,或称 system heap,是指由操作系统提供的 一块 global 內存空间,**程序可动态分配 (dynamic allocated) 获得若干内存块(blocks)。

class Complex {}; 
... 
{
    //当离开作用域的时候c1自然就会消失了 c1占用空间来自stack  栈 p 必须手动delete   堆   动态空间
	Complex c1(1,2);
	Complex* p = new Complex(3);
}

小结:

  • 变量的生命期

    • stack object (又被称为local object, auto object):离开作用域会被自动清理

      class Complex { ... }; 
      ... 
      {
      Complex c1(1,2);
      }
      
    • static object:作用域结束后仍然存在,在程序结束时才结束

      class Complex {}; 
      ... 
      {
      static Complex c2(1,2);
      }
      
    • global object:在程序结束时才结束,也可以看作是static object

      class Complex {}; 
      ... 
      Complex c3(1,2);
      int main()
      {
      ...
      }
      
    • heap object: 在被delete之后才结束,否则不delete的话会出现内存泄漏(离开作用域失去了对动态空间的控制,指针就死亡了,但空间还在,没有还给系统)

      lass Complex {}; 
      ... 
      {
      Complex* p = new Complex;
      ... 
      delete p;
      }
      class Complex {}; 
      ... 
      {
      Complex* p = new Complex;
      ... 
      delete p;
      }
      
      
  • new先分配内存,再调用构造函数(3个动作)

    • 1.分配内存:内部调用malloc(n) void* mem = operator new(sizeof(Complex));

    • 2.转型 pc = static_cast<Complex*>(mem);

    • 3.构造函数 pc->Complex::Complex(1,2);

      Complex *pc;
      void* mem = operator new( sizeof(Complex) ); //分配內存
      pc = static_cast<Complex*>(mem); //转型
      pc->Complex::Complex(1,2); //构造函数
      
  • delete先调用析构函数,再释放内存

    • 1.析构函数 String::~String(ps)

    • 2.释放内存:内部调用free(ps) operator delete(ps)

      Complex::~Complex(pc); // 析构函数
      operator delete(pc);  //释放内存
      
  • array new一定要搭配array delete,否则会造成内存泄漏,分配的数组的内存,析构函数调用

String* p = new String[3];
...
delete[] p;

注意:

  • 连续赋值时必须要返回引用 void的话就不适用连续赋值

  • &放在typename后面 是引用,&放在object前面 表示取地址 得到指针

  • 传出去的东西,(return )不会关心接收端是引用还是值

三、类模板 函数模板 static

1、static

**数据成员加上了static,data就不属于某一个对象,**在内存某一个区域,只有一份,所有的对象都需要同样的一个数据,这时候就可以把数据设置为static,比如银行利率。

小结:

  • static
    • static成员变量:与对象脱离,在内存中只有一份
      • 使用场景:与对象无关的一般数据
      • **静态数据一定要在类外面写一行对其的定义,给不给初值都可以。**要在类的外部赋值:double Account::m_rate = 8.0;
    • non static成员函数:在内存中只有一份,靠this pointer来区分要处理不同的对象 double real() const { return this->re; }
    • static成员函数:没有this pointer,只能处理static成员变量,不能访问、存取对象中的数据,静态函数只能存取静态数据。
      • 调用static函数的两种方式
        • 1.通过对象调用 a.set_rate(7,0);
        • 2.通过class name调用 Account::set_rate(5.0);

2、类模板

template<typename T>
class complex
{
public:
	complex (T r = 0, T i = 0)
	: re (r), im (i) 
	{ }
	complex& operator += (const complex&);
	T real () const { return re; }
	T imag () const { return im; }
private:
	T re, im;
	friend complex& __doapl (complex*, const complex&); 
};

{
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
...
}

3、函数模板

template <class T> // class 和typename 相同
inline
const T& min(const T& a, const T& b) 
{
	return b < a ? b : a;
}

与类模板不同的是函数模板会进行自动实参推导。

四、组合与继承
  • Composition复合

    • 表示has-a,这个queue类里有一个deque<T> c

    • 设计模式Adapter

      • 已经有一个功能强大的class deque了,现在开放它的部分接口,改装成class queue
    • 构造和析构(Container拥有Component)

      • 构造由内向外

        //    2                                                1
        Container::Container Container::Container((……): ): Component() Component() { { …… };
        
      • 析构由外向内

        // 1                                 2
        Container::~Container((……){ ){ …… ~Component() ~Component() };}
        
  • Delegation委托

    • 类(handle)里有一个指向另一个类(body) 的指针 StringRep* rep,虽然是指针指向另一个类,但在学术上也成为by reference 生命不同
    • 引用计数 共享特性
  • Inheritance继承

  • 三种继承方式
    public最重要 is- a的关系
    父类的数据被继承下来
    继承和虚函数搭配最有价值

    • 表示is-a class List_node : public List_node_base

    • 构造和析构

      • 构造由内向外(先父类后子类)

        //    2                                        1
        Derived::Derived Derived::Derived((……): ): Base() Base() { { …… };};
        
      • 析构由外向内(先子类后父类)

        //   1                                          2
        Derived::~Derived Derived::~Derived((……){ ){ …… ~Base() ~Base() };};
        
        • 父类的析构函数必须是virtual,否则会出现undefined behavior(未定义行为)
    • 虚函数

  • non-virtual函数,不希望重新定义(override)它。

  • virtual函数:希望 允许派生类或者子类 去重新定义 override

  • pure virtual 纯虚函数 子类 一定要定义 注意: 和空函数不一样,抽象类

  • 设计模式观察者

    委托+继承

class Subject
{
private:
  int m_value;
  vector<Observer*> m_views;
public:
  void attch(Observer* obs)
  {
    m_miews.push_back(obs);
  }
  void set_val(int value)
  {
    m_value = value;
    notify();
  }
  void notify()
  {
    for (int i = 0; i < m_views.size(); i++)
      m_views[i]->update(this, m_value);
  }
};
class Observer
{ 
public:
  virtual void update(Subject* sub, int value) = 0;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值