第二周:C++实现一个带指针成员变量的类【Boolean】
2017-02-14 17:06
405 查看
1.Big Three
class String // Big Three: 拷贝构造、拷贝复制、析构 { String (const String &str); String &operator=(const String &str); ~String(); };
(1).构造函数
inline String::String(const char*cstr = 0) { if (cstr) { m_data = new char[strlen(cstr)+1]; // C++ 使用'\0'表示字符存储的结束 ,strlen()不计算结束符'\0',所以要+1 strcpy(m_data, cstr); } else { m_data = new char[1]; //[1]是为了存储'\0'结束符,析构时方便 *m_data = '\0'; } }
(2).Big Three 之拷贝构造函数
String s1(“hello”);
String s2(s1);
….. ‖ 完全相同,都是调用的拷贝构造
String s2 = s1;
inline String::String(const String &str) { m_data = new char[strlen(cstr)+1]; strcpy(m_data, cstr); }
(3).Big Three 之拷贝复制函数
自我检查,如果是自己,直接返回
删除当前的内容
开辟新内存空间,获取右值str对象内容
返回自身引用
inline String& String::operator=(const String& str) { if(this == &str) { return *this;} delete[] m_data; m_data = new char[strlen(str)+1]; strcpy(m_data, str.m_data); return *this; }
(4).Big Three 之析构函数
inline String::~String() { delete [] m_data; }
如果类里面有指针,动态分配内存,一定要再析构函数中释放内存,避免内存泄漏
2.创建对象
Stack,是存在于某个作用域(scope)的一块儿内存空间(memoryspace)。例如当你调用构造函数,函数本身即会形成一个stack用来防止它所接收的参数,以及返回地址。
在函数体内(function body)内声明的任何变量,其所使用的内存块都取自上述stack。
Heap 所谓system heap,是指由操作系统提供的一块global内存空间,程序可动态分配(dynami
callocated)从中获得若干区块(blocks)。
{ Complex c1(1,2); //c1所占用的空间来自stack Complex* p = new Complex(3); // Complex(3)是个临时对象,其所占用的空间仍是以new自heap动态分配而得,并由p指向。 }
stack object的生命期
class Complex{...}; ... { Complexc1(1,2); }
c1是所谓的stack object,其生命在作用域(scope)结束之际结束。这种object,称为local object 或称 auto objcet,因为他会被自动清理。
class Complex{...}; ... { static Complexc2(1,2); }
c2是所谓的static object,其生命再作用于(scope)结束之后仍然存在,直到整个程序结束。
heap object的生命期
class Complex{...}; ... { Complex* p = new Complex; ... delete p; }
p 所指的是 heap object,其生命周期在它被deleted之际结束。
class Complex{ ... }; ... { Complex* p = new Complex; }
以上代码会出现内存泄漏(memory leak),因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)
global objects的生命周期
class Complex{...} ... Complex c3(1,2); int Main() { ... }
c3是 global objects,其生命在整个程序结束之后才结束。可以把它视为一种 static object,其作用域是【整个程序】。
【new的原理:先分配内存,在调用构造函数】
当new一个对象的时候,实际上是 先分配内存,再调用类的构造函数Complex *pc = new Complex(1,2); void mem = operator new (sizeof(Complex)); // 分配内存:内部使用malloc分配内存 pc = static_cast<Complex*>(mem); // 转型 pc->Complex::Complex(1,2); // 构造函数,相当于伪码: Complex::Complex(pc, 1, 2)
【delete:先调用析构函数,再释放内存】
String* ps = new String("Hello"); ... delete pc;
编译器转化为:
String::~String(ps); operator delete(ps) // 内部调用 free(ps),使用free释放内存
3.扩展
singleton模式
class A { public: static A &getInstance() { static A a; return a; } static int num; private: A() { } }; int A::num = 10; //类的静态成员初始化方法 int main(int argc, char *argv[]) { A::getInstance(); //通过类名来调用 return 0; }
类模板
template <typename T> class A { public: A(T a) :m_a(a) {} T m_a; }; //调用 A<int> a; //类模板调用需要指明模板类型
函数模板
template <class T> const T& min(const T&a, const T&b) { return b < a ? b:a; } //调用 class A { ... }; A a,b; min(a,b) ===> 函数模板调用不需要指明参数对象,编译器会对实参进行推导,然后确定类型
namespace 命名空间
#include <iostream> using namespace std; //打开标准库 的命名空间 //自定义命名空间 namespace mySpace { int a; } int main(int argc, char*argv[]) { mySpace::a = 10; return 0; }
参考链接:使用namespace的三种方式
operator type() const;
#include <iostream> #include <string> #include <string.h> #include <stdlib.h> #include <stdio.h> using namespace std; class Total { public: Total(float sum,float discount) { sum_ = sum; discount_ = discount; } ~Total(){} operator std::string() { cout<<"operator std::string() "<<endl; char str[128]; sprintf(str,"%f",sum_* discount_); return std::string(str); } operator int() { cout<<"operator int()"<<endl; return sum_* discount_; } operator float() { cout<<"operator float()"<<endl; return sum_* discount_; } float operator()() { cout<<"float operator()() "<<endl; return sum_* discount_; } float sum_; float discount_; }; ostream& operator<< (ostream &out, const Total & ths) { cout << "--------------------ostream& << (ostream &out, const Total & ths)"<<endl; return out; } int main(int argc, char const *argv[]) { Total to(89, 0.8); cout << to << endl; cout << to() << endl; cout << (std::string)to << endl; cout << (float)to << endl; cout << (int)to << endl; return 0; } 输出 --------------------ostream& << (ostream &out, const Total & ths) float operator()() 71.2 operator std::string() 71.200001 operator float() 71.2 operator int() 71
需要注意的是如果没有重载<<,并且Total里面有operator float(),operator int()编译器就会报错,有二义性,因为不确定cout << to << endl 是把Total转成int,还是float,然后进行输出,如果只有一个,就不会有问题
explicit complex():initialization list {}
关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。class Test1 { public: Test1(int n) { num = n; } //普通构造函数 private: int num; }; class Test2 { public: explicit Test2(int n) { num = n; } //explicit(显式)构造函数 private: int num; }; int main(int argc, char *argv[]) { Test1 t1 = 12; //隐式调用其构造函数, 成功 //Test2 t2 = 12; //编译错误,不能隐式调用其构造函数 Test2 t3(12); //显示调用成功 return 0; }
function-like object
参考链接:std::function from cppreference.com以下代码需要编译器C++11支持
#include <functional> #include <iostream> struct Foo { Foo(int num) : num_(num) {} void print_add(int i) const { std::cout << num_+i << '\n'; } int num_; }; void print_num(int i) { std::cout << i << '\n'; } struct PrintNum { void operator()(int i) const { std::cout << i << '\n'; } }; int main() { // store a free function std::function<void(int)> f_display = print_num; f_display(-9); // store a lambda std::function<void()> f_display_42 = []() { print_num(42); }; f_display_42(); // store the result of a call to std::bind std::function<void()> f_display_31337 = std::bind(print_num, 31337); f_display_31337(); // store a call to a member function std::function<void(const Foo&, int)> f_add_display = &Foo::print_add; const Foo foo(314159); f_add_display(foo, 1); // store a call to a data member accessor std::function<int(Foo const&)> f_num = &Foo::num_; std::cout << "num_: " << f_num(foo) << '\n'; // store a call to a member function and object using std::placeholders::_1; std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 ); f_add_display2(2); // store a call to a member function and object ptr std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 ); f_add_display3(3); // store a call to a function object std::function<void(int)> f_display_obj = PrintNum(); f_display_obj(18); } //输出 -9 42 31337 314160 num_: 314159 314161 314162 18
4. 作业知识点
虚析构
为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。class Shape { public: Shape(int no = 0) :m_no(no) { } virtual ~Shape(){} virtual int getArea()=0; void setNo(int no) { m_no = no; } int getNo() {return m_no;} protected: int m_no; }; class Rect:public Shape { public: Rect():m_p(0) {} ~Rect(){} private char *m_p; } int main(int argc, char *argv[]) { Shape *p = new Rect; delete p; return 0; }
上例代码,如果Shape的析构函数不是virtual的话,delete的时候就不会调用Rect的析构函数,可能会造成Rect::m_p的内存泄漏
还可以参考这个例子:析构函数什么情况下要定义为虚函数?
相关文章推荐
- 第一周:C++实现一个不带指针成员变量的类【Boolean】
- [Boolan] C++第二周(创建一个带指针成员变量的类)[注意事项]
- [Boolan] C++第一周(创建一个不带指针成员变量的类)[注意事项]
- C++利用类静态变量,实现伪类对象空指针成功访问含有成员变量的成员函数而不崩溃
- C/C++学习笔记:指向类成员变量的指针
- 实现一个类成员函数指针的数组,
- 从汇编看c++中指向成员变量的指针(一)
- C++ 的一个问题的理解(私有变量成员)
- 21天学通c++之第二周 指针 8.6 访问自由存储区中对象的成员数据
- C++第六周任务5:解决用一个项目多个文件的方式实现,其中两个类的声明放在一个.h文件中,每个类的成员函数分别放一个文件,main()函数用一个文件。体会这样安排的优点。
- C++面向对象—成员函数与成员变量的实现机制学习笔记(1)
- C++ 虚拟函数vs 回调函数 像有虚拟方法表一样有一个虚拟变量表就可以实现类级回调函数了
- 自己动手实现一个C++智能指针
- 基于数据成员是指向一个数组的指针来实现的list
- C++初始化列表问题,类中有一个对象类型的数组成员变量,在初始化列表中初始化时报错“[]”操作符语法错误
- 使用c++的成员指针实现类似Borland VCL组件的事件回调
- c++ 变量声明: 成员函数指针 成员变量指针
- C语言中实现C++静态类成员变量
- 利用C++重载实现类似“类成员指针”的功能
- 自己实现一个C++ 智能指针