一、C++编程介绍
你应具备哪些基础
·曾经学过某种语言(procedural language) C语言最佳
·变量(variable)
·类型(types):int,float,char,struct…
·作用域(scope)
·循环(loops):while,for
·流程控制:if-else,switch-case
·知道一个程序需要编译、链接才能被执行
·知道如何编译和链接
(如何建立一个可执行程序)
我们的目标
·培养正规的、大气的编程习惯
·以良好的方式编写C++ class Object Based(基于对象)
· class without pointer members
-Complex
·class with pointer members
-String
·学习classes之间的关系 Object Oriented(面向对象)
-继承(inheritance)
-复数(composition)
-委托(delegation)
你将获得的代码
complex.h complex-test.cpp
string.h string-test.cpp
oop-demo.h oop-test.cpp
C++的历史
·B语言(1969)
·C语言(1972)
·C++语言(1983)
(new C -> C with Class -> C++)
·Java语言
·C#语言
C++演化
·C++ 98(1.0)
·C++ 03(TR1)
·C++11(2.0)
·C++ 14
C++由C++和C++标准库组成
二、头文件与类的声明
对于数据和函数
函数是用来处理数据的
C语言中,数据是全局的,会造成麻烦
C++中,数据和函数被包裹在一起,在一起的函数和数据,该函数只能控制该数据,体现了"封装的思想"
字符串内中,其实含有一个指针,通过new的方法来申请内存空间(创建对象)。
Object Based(基于对象)vs Object Oriented(面向对象)
Object Based:面对的是单一 class 的设计
Object Oriented:面对的是多重classes的设计,classes和classes之间的关系。
我们的第一个C++程序
Classes的两个经典分类:
·Class without pointer member(s)
complex
·Class with pointer member(s)
string
C++ programs 代码基本形式
头文件+主程序
include(包含)自己写的,需要使用"",包含标准库,需要使用<>。
延展文件名(extension file name)不一定是.或.cpp,也可能是.hpp或其他或甚至无延伸名。
Output,C++ vs C
Header(头文件)中防卫式声明
防卫式声明是一种判断的声明,若COMPLEX头文件被include了,则不会再include。若没有,则被include。
//complex-test.h
#include<iostream>
#include"complex.h"
using namespace std;
int main()
{
complex c1(2,1);
complex c2;
cout << c1 << endl;
cout << c2 << endl;
c2 = c1 + 5;
c2 = 7 + c1;
c2 = c1 + c2;
c2 += c1;
c2 += 3;
c2 = -c1;
cout << (c1 == c2) << endl;
cout << (c1 != c2) << endl;
cout << conj(c1) << endl;
return 0;
}
Header(头文件)的布局
#ifndef _COMPLEX_
#define _COMPLEX_
1.//forward declarations(前置声明)
#include<cmath>
class ostream;
class complex;
complex& _doapl(complex* ths,const complex& r);
2.//class declarations(类-声明)
class complex
{
...
};
3.//class definition(类-定义)
complex::function ....
#endif1
class的声明(declaration)
class complex //class head
{ //class body
public:
complex (double r = 0,double i = 0): re (r), im (i) {}
//有些函数在此直接定义,另一些在body之外定义
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
{
complex c1(2,1);
complex c2;
...
}
class template(模板)简介
模板的出现原因是为了减少代码的重复性
template<typename T>
class complex //class head
{ //class body
public:
complex (double r = 0,double i = 0): re (r), im (i) {}
//有些函数在此直接定义,另一些在body之外定义
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);
//模板上的T就会变成double
complex<int> c2(2,6);
...
}
inline(内部)函数
class complex
{
public:
complex (double r = 0,double i = 0): re (r), im (i) {}
complex& operator += (const complex&);
/*函数若在class body内定义完成,便自动成为inline候选人
{ return re; } { return im; } */
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
inline double
imag(const complex& x)
{
return x.imag();
}
三、构造函数
access level(访问级别)
对于数据要进行封装操作,访问修饰符使用private。
class complex
{
public:
complex (double r = 0,double i = 0): re (r), im (i) {}
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
{
complex c1(2,1);
cout << c1.re;
cout << c1.im;
}
//错误
{
complex c1(2,1);
cout << c1.real();
cout << c1.imag();
}
//正确
constructor(ctor,构造函数)
class有一个经典的分类:带指针的和不带指针的,不带指针的类,多半不用写析构函数。
构造函数的函数名与类名相同,它可以拥有参数,无返回类型。
在创建对象时,构造函数自然而然地就会被调用。
class complex
{
public:
/*(double r = 0, double i = 0) default argument(默认实参)
re (r), im (i) initialization list (初值列,初始列)
complex (double r = 0, double i = 0){ re = r; im = i ;}*/
complex (double r = 0,double i = 0): re (r), im (i) {}
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
{
complex c1(2,1);
complex c2;
complex* p = new complex(4);
...
}
ctor(构造函数)可以有很多个 - overloading(重载)
重载:函数名相同,参数不同。
class complex 1.
{
public:
/*1*/complex (double r = 0,double i = 0): re (r), im (i) {}
/*2*/complex () : re(0), im(0) { }
complex& operator += (const complex&);
/*1*/double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
/*2*/void real (double r) { re = r; }
real函数编译后的实际名称可能是:
?real@Complex@@QBENXZ
?real@Complex@@QAENABN@Z
取决于编译器
{
complex c1;
complex c2();
...
}
constructor(ctor,构造函数)被放在private内
class complex
{
public:
complex (double r = 0, double i = 0)
:re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
//将构造函数放入到该处
friend complex& _doapl (complex*,const complex&);
};
{
complex c1(2,1);
complex c2;
...
}//错误
ctors放在private内
class A{ //Singleton 单一,单件,一份
public:
static A& getInstance();
setup() { ... }
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a;
return a;
}
A::getInstance().setup();
const member functions(常量成员函数)
const 放在函数名的后面,函数体的前面。(即小括号的后面,大括号的前面); 也可以出现在对象变量)的前面(意思是我的这个对象或者变量一定是不动的,不可以改)
class(类)中的函数又分为两种,一种是可以改变对象的数据,一种是不会改变对象的数据。
不改变数据的函数加上 const (const 的意思就是不改变数据的内容,只是拿数据)
class complex
{
public:
complex (double r = 0,double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
{
complex c1(2,1);
cout << c1.real();
cout << c1.imag();
}
//正确
{
const complex c1(2,1);
cout << c1.real();
cout << c1.imag();
}
四、参数传递与返回值
参数传递:pass by value vs. pass by reference (to const)
参数传递,尽量都传引用,
class complex
{
public:
complex (double r = 0,double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << real (x) << ','
<< imag (x) << ')';
}
{
complex c1(2,1);
complex c2;
c2 += c1;
cout << c2;
}
返回值转递: return by value vs. return by reference (to const)
class complex
{
public:
complex (double r = 0,double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real() const { return re; }
double imag() const { return im; }
private:
double re,im;
friend complex& _doapl (complex*,const complex&);
};
ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << real (x) << ','
<< imag (x) << ')';
}
{
complex c1 (2,1);
complex c2;
cout << c1;
cout << c2 << c1;
}
friend(友元)
class complex
{
public:
complex (double r = 0,double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
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)
{
//自由取得friend的private成员
ths->re += r.re;
ths->im += r.im;
return *ths;
}
相同 class 各个objects互为friends(友元)
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);
}
class body 外的各种定义(definitions)
什么情况下可以 pass by reference
什么情况下可以 return by reference
(复习:拿到一个类,注意:第一,数据放到private (封装)第二,参数尽可能地以reference(引用)来传 要不要加 const 看情况 第三,返回值也尽量用 reference 来传)
尽量使用传引用的方式才传递参数,因为引用的底层是指针(C语言中指针作为参数传递类似),传递的数据大小为4个字节,速度会很快。同样,值的返回也尽量返回引用(如果可以的话)。
- 数据放在
private
里- 参数用
reference
,是否用const
- 在类的
body
里的函数是否加const
- 构造函数的
initial list
- return by reference,不能为local object.
指针&引用:
- 都是地址的概念;指针是一个实体,指向一块内存,它的内容是所指内存的地址;引用则是某块内存的别名。
- 使用sizeof指针本身大小一般是(4),而引用则是被引用对象的大小;
- 引用不能为空,指针可以为空;指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
- 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
- 引用没有
const
,指针有const
,const
的指针不可变;
具体指没有int& const a
这种形式,而const int& a
是有的,前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变)- 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
- 指针可以有多级指针(**p),而引用至于一级;
- 指针和引用使用自增(++)运算符的意义不一样;
- 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。
- 引用是类型安全的,而指针不是 (引用比指针多了类型检查)
const作用
当一个方法不会对数据进行修改时,尽量将方法指定为const。细分顶层const、底层const
"effective c++"第三条讲到: 只需要判断const是在 * 的左边还是右边即可。左边则是修饰被指物,即被指物是常量,不可以修改它的值;右边则是修饰指针,即指针是常量,不可以修改它的指向;在左右两边,则被指物和指针都是常量,都不可以修改。
- 修饰变量,说明该变量不可以被改变;
- 修饰指针,分为指向常量的指针和指针常量;
- 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;
- 修饰成员函数,说明该成员函数内不能修改成员变量。
do assignment plus
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);
}
五、操作符重载与临时对象
在C语言中对一个数据进行操作设计一个函数。实际上在C++中操作符就是一种函数,可以重新定义(最大的特点)
operator overloading(操作符重载-1,成员函数)this
所有的成员函数一定带着一个隐藏的参数,this指使用该函数的对象。
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);
}
{
complex c1(2,1);
complex c2(5);
c2 += c1;
}
//this代表c2,r代表c1
inline complex&
complex::operator += (this,const complex& r)
{
return _doapl (this,r);
}
return by reference 语法分析
传递者无需知道接收者是以reference形式接收
inline complex&
_doapl (complex* ths,const complex& r)
{
...
return *ths;
}
inline complex&
complex::operator += (const complex& r)
{
return _doapl(this,r);
}
{
complex c1 (2,1);
complex c2 (5);
c2 += c1;
}
c3 += c2 += c1; //加操作的返回类型不可以是
operator overloading(操作符重载-2,非成员函数)(无this)
为了对付client(客户)的三种可能用法,这儿对应开发三个函数
inline complex
operator + (const complex& x, const complex& y)
{
return complex (real (x) + real (y),
img (x) + imag (y));
}
inline complex
operator + (const complex& x, double y)
{
return complex (real (x) + y, imag (x));
}
inline complex
operator + (double x, const complex& y)
{
return complex (x + real (y), imag (y));
}
{
complex c1(2,1);
complex c2;
c2 = c1 + c2; //调用第一个函数
c2 = c1 + 5; //调用第二个函数
c2 = 7 + c1; //调用第三个函数
}
temp object(临时对象)typename ();
下面这些函数绝不可 return by reference,因为,它们返回的必定是个local object。
typename (),用于创建temp object(临时对象)。
{
int (7);
complex c1 (2,1);
complex c2;
complex ();
complex (4,5);
cout << complex (2);
}
class body 之外的各种的定义(definitions)
inline complex
operator + (const complex& x)
{
return x;
}
/*negate 反向(取反)
这个函数绝不可return by reference,
因为其返回的必定是个local object。
*/
inline complex
complex - (const complex& x)
{
return complex (-real (x), -imag (x)); //创建临时对象
}
{
complex c1 (2,1);
complex c2;
cout << -c1;
cout << +c1;
}
operator overloading(操作符重载),非成员函数
inline bool
operator == (const complex& x,
const complex& y)
{
return real (x) == real (y)
&& imag (x) == imag (y);
}
inline bool
operator == (const complex& x, double y)
{
return real (x) == y && imag (x) == 0;
}
inline bool
operator == (double x, const complex& y)
{
return x == real (y) && imag (y) == 0;
}
{
complex c1 (2,1);
complex c2;
cout << (c1 == c2);
cout << (c1 == 2);
cout << (0 == c2);
}
思考函数的返回值是value还是reference?
operator overloading(操作符重载),非成员函数
inline bool
operator != (const complex& x,
const complex& y)
{
return real (x) != real (y)
|| imag (x) != imag (y);
}
inline bool
operator != (const complex& x, double y)
{
return real (x) != y || imag (x) != 0;
}
inline bool
operator != (double x, const complex& y)
{
return x != real (y) || imag (y) != 0;
}
{
complex c1 (2,1);
complex c2;
cout << (c1 != c2);
cout << (c1 != 2);
cout << (0 != c2);
}
operator overloading(操作符重载),非成员函数
inline complex
conj (const complex& x)
{
return complex (real (x), -imag (x));
}
#include<iostream.h>
ostream& operator << (ostream& os, const complex& x)
{
return os << '('<< real (x) << ','
<< imag (x) << ')';
}
{
complex c1 (2,1);
cout << conj (c1); //(2,-1)
cout << c1 << conj (c1); //(2,1) (2,-1)
}
函数的分类:全局函数(非成员函数),和成员函数
操作符有两种写法:一种是成员函数的写法,一种是非成员函数的写法。
返回类型改为void可以么?只要不连续输出就可以。
六、复习Complex类的实现过程
总结: 设计一个类,构造函数的形式
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
函数该不该加const。参数的传递是传值value还是引用reference
数据放在private,函数放在public
二、三、四、五部分的代码
#ifndef _COMPLEX_
#define _COMPLEX_
class complex
{
public:
complex (double r = 0, double i = 0)
: re(r), im(i)
{ }
complex& operator += (const complex&);
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.real;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator += (const complex& r)
{
return _doapl (this,r);
}
inline complex
operator + (const complex& x, const complex& y)
{
return complex ( real (x) + real (y),
imag (x) + imag (y) );
}
inline complex
operator + (const complex& x,double y)
{
return complex ( real (x) +y,imag (x));
}
inline complex
operator + (double x,const complex& y)
{
return complex (x + real (y), imag (y));
}
#include<iostream.h>
ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << real (x) << imag (x) << ')';
}
#endif
complex c1 (9,8);
cout << c1;
c1 << cout; //可以这么设计,但是使用者不会这样使用;
cout << c1 << endl;
七、三大函数:拷贝构造,拷贝复制,析构
Classes的两个经典分类
·Class without pointer member(s)
complex
·Class with pointer member(s)
string
String class
#ifndef _MYSTRING_ string.h
#define _MYSTRING_
class String
{
....
};
String::function(...) ...
Global-function(...) ...
#endif
int main() string-test
{
String s1(),
String s2("hello");
String s3(s1); //拷贝构造
cout << s3 << endl;
s3 = s2; //拷贝复制
cout << s3 << endl;
}
Big Three,三个特殊函数
class String
{
public:
String (const char* cstr = 0);
//只要创建的类带着指针,一定要写出拷贝构造和拷贝复制这两个函数
String (const String& str); //拷贝构造
String& operator = (const String& str) //操作符重载,拷贝复制
~String (); //析构函数,当对象死亡时,析构函数会被调用起来
char* get_c_str() const { return m_}
private:
char* m_data;
};
一般设计字符串时,里面有一个指针,在需要内存的时候,才创建另外一个空间去存放字符本身,是因为字符本身有大有小,所以采用动态分配的方式。
从C语言以来延续的概念,字符串是什么?有一个指针指着头,后面就一串,最后会有一个0,这样的结束符
ctor和dtor(构造函数 和 析构函数 )
class with pointer members 必须有 copy ctor (拷贝构造)和 copy op = (拷贝复制)
这个是浅拷贝,字符串a,b都含有指针,a,b中的指针指向内存中存放Hello的地址,而内存存放World的地址就会消失。
copy ctor(拷贝构造函数)
inline
String::String (const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy (m_data, str.m_data);
}
//深拷贝
//str.m_data 直接取另一个object的private
{
String s1 ("hello ");
String s2 (s1);
// String s2 = s1;
}
copy assignment operator(拷贝赋值函数)
{
String s1 ("hello ");
String s2 (s1);
s2 = s1;
}
//s2 = s1,调用的就是拷贝复制函数。this 就是s2。
if (this == &str)
return *this
//检测自我赋值 (self assignment)
浅拷贝——>拷贝构造函数 ,浅拷贝的影响:
1、造成内存泄漏;
2、造成有两个指针 指向同一块内存
深拷贝——>拷贝赋值函数
步骤:delete ;new; strcpy;
class里面有默认的拷贝构造和拷贝赋值函数。如果自己不定义一个拷贝构造函数,在调用拷贝构造函数的时候,就会调用默认的浅拷贝构造函数,就会造成问题,所以一定要自己定义拷贝构造函数——深拷贝。
output函数
#include <iostream.h>
ostream& operator << (ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
{
String s1 ("hello ");
cout << s1;
}
八、堆,栈与内存管理
所谓 stack(栈),所谓 heap(堆)
Stack,是存在于某作用域(scope)的一块内存空间(memory space)。例如当你使用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本身(function body)内声明的任何变量,其所使用的内存块都取自上述stack。
Heap,或谓 system heap,是指由操作系统提供的一块global(全局)内存空间,程序可动态分配(dynamic allocated)从某中获得若干个块(blocks)。
class Complex { ... };
...
{
Complex c1 (1,2);
Complex* p = new Complex(3);
}
c1 所占用的空间来自stack
Complex(3)是个临时对象,其所占用的仍是以 new 自heap动态分配而得,并由p指向
c1 会因函数的结束,会自动释放内存资源。
而Complex(3)是动态分配的内存(new),函数结束,Complex(3)的内存资源依然存在,需要手动清除资源空间(free)
stack objects 的生命期
class Complex { ... };
...
{
Complex c1 (1,2);
}
c1便是所谓stack object(栈对象),其生命在作用域(scope)结束之际结束。
这种作用域内的object,又称为auto object,因为它会被自动清理
static local objects 的生命期
class Complex { ... };
...
{
static Complex c2 (1,2);
}
c2便是所谓static object(静态对象),其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。
global objects 的生命期
class Complex { ... };
...
Complex c3(1,2);
int main()
{
...
}
c3便是所谓global object(全局对象)任何大括号之外的,其生命在整个程序结束之后才结束。你也可以把它视为一种static object,其作用域是 整个程序。
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)
new:先分配memory(内存),再调用ctor(构造函数)
谁调用Complex构造函数,谁就是this
pc是m_real和m_imag在内存空间中的首地址
delete:先调用dtor(析构函数),再释放memory(内存空间)
动态分配所得的内存块(memory block),in VC
在调试模式底下,会多灰色的内存空间(一个小矩形是4个字节,一共是32+4个字节)再加上砖红色内存空间(4*2)(叫cookie作用:记录整块内存空间的大小,为什么要记录内存空间的大小呢?因为将来系统回收时,只给它一个指针,不知道回收多大的内存空间,所以必须有一个地方记录这个长度,malloc和free规定在最上面是申请的内存空间大小)内存空间总共:8+(32+4)+(4×2)
结果是64 用十六进制表示应该是0x40,而结果是0x41,借用最后一个bite位,0/1来表现这块内存空间是给出去还是收回来,对于操作系统是给出去了,对于程序而言是获得了所以最后一位是1。为什么可以通过查看最后一位是0还是1来判断内存空间是申请还是释放?因为申请空间是16的倍数最后4个bite都是0,所以可以借其中一位来使用。
不进入调试模式:就没有灰色的内存空间,则内存空间总共:8+(4×2)
对于字符串而且,字符串内含一个指针,所以字符串申请内存空间时,是申请存放指针大小的内存空间,所以是4个字节
在非调试模式下没有灰色的内存空间,4个字节+上下cookie,结果是12个字节,但是申请内存空间必须是16的倍数,所以会调整到16的倍数。
动态分配所得的是array
注意:array new,搭配array delete
m_data = new char [ strlen(cstr)+1 ];
delete [] m_data;
进入调试模式,会申请橘黄色的内存空间。
不进入调试模式,不会申请橘黄色的内存空间。
字符串内含有一个指针。一个指针占4个字节。
清空内存空间要查看cookie
写成delete [ ], 告诉编译器删除的是一个数组,编译器才知道原来下面有三个,这才调用三次析构函数(每一个对象在死亡之前,都要调用析构函数)。这三个析构函数就各自负责把各自的动态分配的内存杀掉。
如果不写 [ ],编译器以为下面只有一个所以调用一次析构函数而已。
对于class对象使用delete,而不是delete [ ],不会造成影响。
而对于字符串而言,使用delete,而不是delete [ ],会造成影响。
记住一点:使用 array new 就使用 array delete。
九、复习String类的实现过程
注意使用防卫式声明
设计一个class 总先思考使用什么样的数据,字符串里会放很多字符,里面放一个数组,里面都是字符,但是这种想法不好,因为需要指明该数组的大小。但是设置太大或者太小的数组都不好。
让里面放一个指针,将来要放多大的字符串的内容,就动态分配这个大小。用 new 的方式(对于C语言就是使用malloc的方式)在32位的电脑里面,一个指针是4个字节,不管字符串里面放的是hello world…,字符串对象本身就是4个字节。
Class String
{
public:
//构造函数没有返回值,接收一个指针指向字符(就是字符串),这种形式是传统的C语言形式字符串。传进来当然不会改变传进来的值,拿来当初值而已,所以加const,并且给一个默认值0。
String (const char* cstr = 0);
//拷贝构造
String (const String& str);//str是要拷贝构造自己
//拷贝赋值,把来源端拷贝到目的端,并不打算改它,所以加const 得到的结果就是目的端这种东西。要不要return by reference 返回引用? 这个函数执行的结果,放到什么地方去,放到的地方是不是local object,只要不是local object 就可以传reference,从来源端赋值到目的端,这个目的端本身就存在,不需要在这个拷贝赋值函数中创建。
String& operator=(const String&)
//析构函数
~String ();
//辅助函数
char* get_c_str() const { return m_data }
private:
char* m_data;
};
ctor和dtor(构造函数和析构函数)
//分配足够的空间来存放初值
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::~String ()
{
delete [] m_data;
}
copy ctor(拷贝构造函数)
inline
String::String (const String& str)
{
m_data = new char[ strlen(str.m_data)+1];
strcpy(m_data,str.m_data);
}
copy assignment operator(拷贝赋值函数)
inline
String& String::operator=(const String& str)
{
//首先判断是否是自我赋值,即来源端是否和目的端相同
if (this == &str)
return *this;
delete [] m_data;
m_data = new char[ strlen(str.m_data) + 1]
strcpy(m_data,str.m_data);
return *this;
}
十、扩展补充:类模块,函数模板,及其他
进一步补充:static
static静态
在class里面数据或者函数前面+static,就会成为静态的数据或者静态的函数
静态数据和静态函数,和对象就脱离了。单独有一份
c1的地址 &c1,c2的地址&c2 就是this pointer,通过不同的地址处理不同的数据
中间灰色的部分是在复数类中创建的对象(非静态成员)。
右上3个灰色图片是在内存中的信息,中间的是成员函数。
成员函数只有一份(中间的那个)但是他要处理很多个对象,一定要有一个数据告诉它你要处理谁,靠得就是这个this pointer
最下面一行表示的是this pointer 传递到成员函数中的过程
注意:成员函数有一个隐藏的参数----this pointer
静态的数据只有一份(多个对象共同使用)含有this pointer
什么时候使用静态的函数呢?
静态函数和静态数据的区别就是没有this pointer,不能像一般的成员去取,去访问,去处理c1,c2,c3,这些对象中的的数据。若要处理数据,只能处理静态数据。
class Account{
public:
static double m_rate;
static void set_rate(const double& x) { m_rate = x };
};
//如果是静态的数据,一定要在class外做初始化。
double Account::m_rate = 8.0; //定义
int main(){
Account::set_rate(5.0);
Account a;
a.set_rate(7.0);
}
//调用static函数的方式有二:
(1)通过object(对象)调用
(2)通过class name(类名)调用
进一步补充:把ctors放在private内
//singleton
class A{
public:
//该函数是对外界的唯一窗口,外界通过这个函数使用类中唯一一个对象,a。
static A& getInstance( return a; );
setup() { ... }
private:
A();
A(const a& rhs);
static A a;
...
};
A::getInstance().setup();
不想让用户创建该类的对象,把构造函数放private内。该类只有一个对象。缺点:如果外界没有使用到对象a,但是这个静态a仍然存在,有点浪费内存空间。
//Meyers
class A{
public:
static A& getInstance( return a; );
setup() { ... }
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a;
return a;
}
A::getInstance().setup();
//调用getInstance()该函数,才会创建该类对象。
进一步补充:class template,类模板
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);
...
}
进一步补充:function template,函数模板
stone r1(2,3),r2(3,3),r3;
r3 = min(r1,r2);
//编译器会对function template 进行
//引导推论(argument deduction)
template <class T>
inline
const T& min(const T& a,const T& b)
{
return b < a ? b : a;
}
//引导推理的结果,T为stone,于是调用 stone::operator<
class stone
{
public:
stone (int w,int h,int we)
: _w(w), _h(h), _weight(we)
{ }
bool operator< (const stone& rhs) const
{ return _weight < rhs._weight; }
private:
int _w, _h, _weight;
};
进一步补充:namespace
//std:标准库中所有的东西都被包再std
namespace std
{
...
}
//using directive
#include <iostream.h>
using namespace std;//使用std这个命名空间
int main()
{
cin << ...;
cout << ...;
return 0;
}
//using declaration
#include <iostream.h>
using std::cout;
int main()
{
std::cin << ...;
cout << ...;
return 0;
}
更多细节深入
· operator type() const;
· explicit complex(...):initialization list{}
· pointer-like object
· function-like object
· Namespace
· template specialization
· Standard Library
· variadic template
· move ctor
· Rvalue reference
· auto
· lambda
· range-base for loop
· unordered containers
....