c++ 面向对象学习笔记
代码格式
头文件的写法
防卫式声明 gurad
ifndef __COMPLEX__ #这个名次是自己取的
#define __COMPLEX__
. #前置声明
. #类-声明
. #类-定义
#endif
标准库中基于对象的两个典型案例
类中不带指针(复数complex)
#include <iostream>
using namespace std;
// 同一个class的各个object(对象)互为友元 可以直接用另一个对象的pricvate的数据
class complex
{
public: //公开
complex(double r = 0, double i = 0)
: re(r), im(i)
{ }
complex& operator += (const complex&);
// const 指这个函数不能改变对象里面的数据 传入的参数加const 指不能改变参数的数值
// const 放在类的前面是常对象 只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)
double real () const { return re; }
double imag () const { return im; }
private:
double re,im;
// 声明一个全局函数的友元
friend complex& __doapl ( complex*, const complex& );
};
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);
}
// cout 重载只能是作为友元函数,或者是全局函数,因为成员函数的调用是需要由这个对象引发的,而重载的符号只能作用在它前一个类型上,
// 也就是说当作为成员函数的时候必须要是 obj<<cout ,这样才能告诉编译器去成员函数里面去找重载类型,显然这样写是十分反人类的
ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << x.real() << ',' << x.imag() << ')';
}
int main()
{
complex c(1,2);
cout << c;
return 0;
}
类中带有指针的(字符串string)
#include <iostream>
#include <string.h>
using namespace std;
#ifndef __MYSTRING__
#define __MASTRING__
class String
{
public:
String(const char* cstr = 0);
String(const String &str);
~String( );
String& operator = (const String& str);
char * get_c_str() const { return m_date; }
private:
char * m_date;
};
#endif
inline
String::String(const char* cstr = 0)
{
if (cstr) {
m_date = new char [ strlen(cstr)+1];
strcpy(m_date, cstr);
}
else {
m_date = new char [1];
* m_date = '\0';
}
}
inline
String::~String()
{
delete[] m_date;
}
inline
String::String(const String &str)// string a(b) c++新创建的东西会自动调用构造函数 string a = b 一样
{
m_date = new char [strlen(str.m_date)+ 1];
strcpy(m_date, str.m_date);
}
inline
String & String::operator= (const String & str)
{
if (this == &str) // 检验自我赋值
return *this;
delete[] m_date;
m_date = new char [strlen(str.m_date)+ 1];
strcpy(m_date, str.m_date);
return *this;
}
inline
ostream & operator << (ostream & os, const String & str)
{
os << str.get_c_str();
return os;
int main(){
return 0;
}
类与类之间的关系
复合(Composition)
例子: 表示has-a
一个类复合另一个功能更强大的类只开放这个类的部分功能(开放的这个类与调用的类同时加载)
Untitled
构造由内向外构造
在掉用内部的构造函数时编译器只会调用默认构造函数,如果想要调用其他构造函数想要在外层的构造函数里面调用
析构由外向内析构
代码实现
tempale <class T>
class quene{
...
protected:
deque<T> c;
public:
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
referrece back() { return c.back(); }
void push (const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
}
委托(Delegation)
composition by reference
例子:
编译防火墙,客户端与实现类分离(reference counting)
用指针调用其他类(只有在调用的时候才会构造这个类,用指针相连寿命不一致)
代码实现
class StringREp;
class String {
public:
String();
String(const char *s);
String(const String& s);
String &operator=(const String& S);
~String ();
pricvate:
StringRep *rep;
}
继承(Inheritance)
继承语法:(is a关系)
父类的数据是可以完美继承下来的
子类的对象会有父类的成分
调用构造函数与析构函数的情况与复合关系时相似,调用的是默认构造函数
代码实现
struct _List_ndoe_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
tmplate<typename _Tp>
stuct _List_node
:public _list_ndoe_base // 三种继承方式public是一种
{
_Tp _M_date;
}
类中三种访问权限
public 类内可以用,类外可以访问
protected 类内可以访问 类外不可以访问
private 类内可以访问 类外不可以访问
两者主要在继承的时候有区别
在派生类中protected的数据可以访问,而private不能访问,通过类创建出来的对象也无法访问
struct 与 class 区别
最本质的一个区别就是默认的访问控制:默认的继承访问权限
struct是public的,class是private的。
struct A
{
char a;
};
struct B : A
{
char b;
}
这个时候B是public继承A的。
如果都将上面的struct改成class,那么B是private继承A的。这就是默认的继承访问权限
public继承还是private继承,取决于子类而不是基类。
struct可以继承class,同样class也可以继承struct,默认的继承访问权限是看子类到底是用的struct还是class
struct A{};
class B : A{}; //private继承
struct C : B{}; //public继承
要做的更像是一种数据结构的话,那么用struct。要做的更像是一种对象的话,那么用class
struct可以像c一样用大括号直接赋值,如A a={‘p’, 7, 3.1415926}; 但是在struct内部加入一个函数又不能赋值了,class把访问属性改为public也能赋值
虚函数
例子:
一个形状类(类似于框架,部分属性待定,与递归相似)
纯虚函数:父类不定义(大部分),留给子类定义(子类必须定义)
虚函数:父类有定义,子类可以重新定义
非虚汗:子类不能定义
继承一般要配合虚函数使用
template mathod
面向对象进阶
转换函数(conversion function)
当必要时,c++可以把创建的类自动转换为一个值
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den) { }
operator double() const { // 这里的意思是当需要分数化为double型的时候,编译器可以直接转
return (double)(m_numerator / m_denomination);
}
private:
int m_numerator; //分子
int m_denominator; // 分母
}
}
调用时
Fraction f(3,5);
Fraction d2 = f+4;// f被自动转换
转换函数要注意不要与构造函数冲突,当冲突的时候可以加在构造函数前加上 explict ,表示只有构造的时候,构造函数才能使用,不要自动转换。
pointer-like classes 智能指针
实例:迭代器
template<class T>
class shared_ptr
{
public:
T& operator*() cosnt // 注意c++对于这两种操作符的重载
{ return *px; }
T& operator->() cosnt
{ return px; }
shared_ptr(T* p) : px(p){ }
private:
T* px;
}
function-like classes 仿函数
重载小括号
struct identity
{
const T&
operator() (const T& x) const { return x; } // 括号里的就是参数
};
模板
类模板
调用的时候要用<>指名类型
函数模板
直接调用
成员模板
在模板类里面,又有模板
用于实现下面这种关系
模板全特化
//泛华代码
template<class Key>
struct hash { };
//------------------------------------
//特化
template<>
struct hash<int>{
sise_t operator()(int x) const { return x; }
};
类模板偏特化
类型偏特化:
当我们需要指定部分的模板变量的时候,需要偏特化
#include <iostream>
#include <cstring>
using namespace std;
template<typename T, typename T1>
class A {
public:
A() = default;
A(const T1& n) {
cout << n << endl;
}
bool cmp(const T &t1, const T &t2) {
return t1 == t2;
}
};
template<typename T1> // 篇特化
class A<char*, T1> {
public:
A() = default;
A(T1& n) {
cout << n << endl;
}
bool cmp(const char* t1, const char* t2) {
while(*t1 != '\0' && *t2 != '\0') {
if(*t1 != *t2) {
return false;
}
++t1;
++t2;
}
return true;
}
};
int main() {
char* p1 = "hello";
char* p2 = "hello";
A<int, char*>c(p1);
cout << c.cmp(1, 2) << endl;
A<char*, char*>c1(p2); // 即使是偏特化,也要全部声明模板
cout << c1.cmp(p1, p2) << endl;
return 0;
}
范围偏特化:
template <typename T>
class C {...}; //此泛化版本的T可以是任何类型
template <typename T>
class C<T*> {...}; //特化版本,T为指针类型 将范围缩小
函数在全特化的时候只需要在函数前面加上template<>就行。并且函数又函数重载的概念所以没有偏特化。
模板的模板参数:
当需要一个变化的容器,没有确定传入这个容器的变量类型时用模板的模板
template<typename T,
template <typename T>
class SmartPtr
>
class XCLs
{
private:
SmartPtr<T> sp;
public:
XCLs():sp(new T){ }
};
// 调用方式
XCLS<strin, shared_ptr> p1;
注意:这里待定的容器不能有默认参数,当有默认参数再使用模板,会报错,可以用c++2.0来解决
模板参数可变化
将传入的参数分为一个和一包
void print(){
}
template<typename T,typename... Types>
void print(cosnt T& firstArg,cosnt Type&... args)
{
cout << first << endl;
print(args...);
}
// sizeof...(args)
auto 语法糖
auto会自己推出类型,必须要在定义的时候就赋值
for循环
for( decl : coll ){ //coll是容器
statement;
}
for (auto i : {1,2,3,4,5,6,7}){ // i为后面的容器的值pass by value
cout << i << endl; // 如果要改后面的值 则需要auto&传引用
}
对象模型 虚指针 续表
带着虚函数的对象会多一个指针(虚指针),指向虚表。
后面继承的对象可以重写虚函数,改掉虚表的地址位置不变。
动态绑定条件:
1.通过指针调用
2.有向上转型的动作
3.调用虚函数
this
每个成员函数都有的一个this的指针(指向这对象的地址)
执行动态绑定
静态绑定与动态绑定
在vs中实际的测试带码和结果如下
#include <iostream>
using namespace std;
class father
{
public:
virtual void fun1(){
cout << "father" << endl;
}
};
class child:
public father
{
public:
virtual void fun1() {
cout << "child" << endl;
}
};
int main() {
child c;
father f;
// 静态绑定
father f_n = (father)c;
f_n.fun1();
// 动态绑定
father* p_f_n = (father *)&c;
p_f_n->fun1();
// 动态绑定
father* p_f_n_n = new father;
p_f_n_n->fun1();
// 动态绑定
child* p_c_n_n = new child;
p_c_n_n->fun1();
// 动态绑定
father& d_c = c;
d_c.fun1();
return 0;
}