1 stack 声明与定义
引入构造器实现自定义栈大小
#include <iostream>#include <stdio.h>#include <stdlib.h>#include <string.h>using namespace std;class Stack {public:Stack(int size = 1024);~Stack();void init();bool isEmpty();bool isFull();void push(int data);int pop();private:int* space;int top;};Stack::Stack(int size){space = new int[size];top = 0;}Stack::~Stack(){delete[] space;}//void Stack::init()//{// memset(space,0,sizeof(space));// top = 0;//}bool Stack::isEmpty(){return top == 0;}bool Stack::isFull(){return top == 1024;}void Stack::push(int data){space[top++] = data;}int Stack::pop(){return space[--top];}int main(){// Stack s;Stack s(100);// s.init();if (!s.isFull())s.push(10);if (!s.isFull())s.push(20);if (!s.isFull())s.push(30);if (!s.isFull())s.push(40);if (!s.isFull())s.push(50);while (!s.isEmpty())cout << s.pop() << endl;return 0;}
2 构造器(Constructor)
2.1 定义及意义
class 类名 {类名(形式参数)构造体};class A {A(形参) { }};
在类对象创建时,自动调用,完成类对象的初始化。尤其是动态堆内存的申请。
规则:
- 在对象创建时自动调用,完成初始化相关工作
- 无返回值,与类名同
- 可以重载,可默认参数
- 默认无参空体,一经实现,默认不复存在
Stack::Stack(int size){space = new int[size];top = 0;}
2.2 参数初始化表
下面代码中有错误吗? ```cppStack::Stack(int size): space(new int[size]),top(0) {}
include
include
using namespace std; class A { public: // 先定义了len,列表初始化时按声明顺序,这样strlen(name.c_str())会出现异常。 A(char* ps)
{ } void dis() {: name(ps), len(strlen(name.c_str()))
}cout << len << endl;
private:
int len;
string name;
};
int main()
{
A a(const_cast
结论:- 此格式只能用于类的构造函数。- 初始化列表中的初始化顺序,**与声明顺序有关**,与前后赋值顺序无关- 必须用此格式来初始化非静态const 数据成员(c++98)- 必须用此格式来实例化引用数据<a name="cQ1Ni"></a># 3 析构器(Destructor)<a name="deuf6"></a>## 3.1 对象销毁时期- 栈对象离开其作用域- 堆对象被手动delete<a name="XoNHN"></a>## 3.2.析构器的定义及意义```cppclass 类名 {~类名() { }};class A {~A() { }};
在类对象销毁时,自动调用,完成对象的销毁。尤其是类中己申请的堆内存的释放。
规则:
- 对象销毁时,自动调用。完成销毁的善后工作
- 无返值,与类名同,无参。不可以重载与默认参数
- 系统提供默认空析构器,一经实现,不复存在
Stack::~Stack() {delete []space;}
3.3.小结
析构函数的作用,并不是删除对象,而在对象销毁前完成的一些清理工作。4 构造与析构小结
```cppinclude
struct Student { char* name; int age; };
int main() { Student stu; stu.name = (char*)malloc(100); strcpy(stu.name, “tom”); stu.age = 100; free(stu.name);
Student* ps = new Student;ps->name = (char*)malloc(100);strcpy(ps->name, "bob");ps->age = 23;free(ps->name);delete (ps);
}
<a name="cVmlc"></a># 5 多文件编程通常我们将类的声明,放到.h 文件中去,而将实现放到.cpp 中去。<a name="qThpf"></a># 6 拷贝构造(Copy contructor)<a name="mvXrm"></a>## 6.1 拷贝构造的定义及意义由己存在的对象,创建新对象。也就是说新对象,不由构造器来构造,而是由拷贝构造器来完成。拷贝构造器的格式是固定的。```cppclass 类名 {类名(const 类名& another)拷贝构造体}class A {A(const A & another) {}}
规则:
- 系统提供默认的拷贝构造器。一经实现,不复存在。不完全正确
- 系统提供的是等位拷贝,也就是所谓的浅拷贝。
-
6.2 拷贝构造发生的时机
制作对象的副本
- 以对象作为参数和返回值
6.3 深拷贝与浅拷贝
系统提供默认的拷贝构造器,一经定义不再提供。但系统提供的默认拷贝构造器是等位拷贝,也就是通常意义上的浅拷贝。如果类中包含的数据元素全部在栈上,浅拷贝也可以满足需求的。但如果堆上的数据,则会发生多次析构行为。 ```cppinclude
include
using namespace std; class A { public: A(int d, char* p)
{: data(d)
} ~A() {pd = new char[strlen(p) + 1]; strcpy(pd, p);
} A(const A& another) {cout << "delete " << endl; delete[] pd;
} void dis() {pd = new char[strlen(another.pd) + 1]; strcpy(pd, another.pd);
}cout << "data=" << data << ", pd=" << pd << endl;
private: int data; char* pd; }; int main() { // A a(20); // a.dis(); // A b(a); // b.dis(); // A c = b; // c.dis();
A a(20, const_cast<char*>("china"));
a.dis();
A b(a);
b.dis();
A c = b;
c.dis();
return 0;
}
<a name="d0yMb"></a>
# 7 this 指针
<a name="MMT7Z"></a>
## 7.1 意义
系统在创建对象时,默认生成的指向当前对象的指针。这样作的目的,就是为了带来方便。
<a name="tQ0Wj"></a>
## 7.2 作用
- 避免构造器的入参与成员名相同
- 于this 指针的自身引用还被广泛地应用于那些支持多重串联调用的函数中,比如连续赋值
```cpp
#include <iostream>
using namespace std;
class Stu {
public:
Stu(string name, int age)
{ // :name(name),age(age)
this->name = name;
this->age = age;
}
Stu& growUp()
{
this->age++;
cout << &this->age << endl;
return *this; // return this; ??
}
void display()
{
cout << name << " : " << age << endl;
}
private:
string name;
int age;
};
int main()
{
Stu s("tom", 30);
s.display();
s.growUp().growUp().growUp().growUp().growUp();
s.display();
return 0;
}
8 赋值运算符重载(Operator=)
8.1 发生的时机
用一个己有对象,给另外一个己有对象赋值。两个对象均己创建结束后,发生的赋值行为。
8.2 定义
类名 {
类名& operator=(const 类名& 源对象)
拷贝体
}
class A {
A& operator=(const A& another) {
//函数体
return *this;
}
};
8.3 规则
- 系统提供默认的赋值运算符重载,一经实现,不复存在
- 系统提供的也是等位拷贝,也就浅拷贝,会造成内存泄漏,重析构
- 要实现深拷贝的赋值,必须自定义
- 自定义面临的问题有三个:自赋值、内存泄漏、重析构
- 返回引用,且不能用 const 修饰。a = b = c => (a+b) = c
9 返回栈上引用与对象
9.1 c语言返回栈变量
返回的过程产生了”中间变量”作为纽带。
不管是返回指针还是返回值,return 将return 之后的值存到eax 寄存器中,回到父函数再将返回的值赋给变量。#include <stdio.h> int func() { int a = 4; return a; } int main(void) { int i = 3; i = func(); return 0; }9.2 c++返回栈对象
```cppinclude
using namespace std; class A { public: A() {
} A(const A& other) {cout << this << " constructor" << endl;
} A& operator=(const A& other) {cout << this << " cp contructor from " << &other << endl;
} ~A() {cout << this << " operator = " << &other << endl;
} };cout << this << " destructor" << endl;
<a name="juLxH"></a>
### 9.2.1 本质推演
a 传值:发生拷贝
```cpp
void foo(A a)
{
cout << "fool(A a) id=" << &a << endl;
}
int main()
{
A a;
foo(a);
return 0;
}
/* 00000069EA8FFCB8 constructor
00000069EA8FFCB0 cp contructor from 00000069EA8FFCB8
fool(A a) id=00000069EA8FFCB0
00000069EA8FFCB0 destructor
00000069EA8FFCB8 destructor */
b 传引用没有发生拷贝
void foo(A& a) {
cout << "fool(A a) id=" << &a << endl;
}
int main() {
A a;
foo(a);
return 0;
}
/* 00000096852FFEF0 constructor
fool(A a) id=00000096852FFEF0
00000096852FFEF0 destructor */
c 返回对象
A foo(A& a) {
cout << "fool(A a) id=" << &a << endl;
return a;
}
int main() {
A a;
foo(a);
return 0;
}
/* 00000080D3EFFDC0 constructor
fool(A a) id=00000080D3EFFDC0
00000080D3EFFDC8 cp contructor from 00000080D3EFFDC0
00000080D3EFFDC8 destructor
00000080D3EFFDC0 destructor */
在main 的栈上事先开辟了一个临时空间,把这个空间的地址隐式的转到 foo 函数栈上。然后,把 a 内的东西,拷贝到临时空间中。所以发生一次构造,一次拷贝,两次析构。
测试:
A foo(A& a)
{
cout << "in foo :" << (void*)&a << endl;
return a;
}
int main()
{
A a;
A t = foo(a);
cout << "in main:" << (void*)&t << endl;
return 0;
}
/* 010FFE1B constructor
in foo :010FFE1B
010FFE0F cp contructor from 010FFE1B
in main:010FFE0F
010FFE0F destructor
010FFE1B destructor */
此时main 函数中产生的临时空间,由 t 来取而代之。所以也发生一次构造,一次拷贝,两次析构。此时t 的地址,同a 的地址不同。
A foo(A& a)
{
cout << "in foo :" << (void*)&a << endl;
return a;
}
int main()
{
A a;
A t;
t = foo(a);
cout << "in main:" << (void*)&t << endl;
return 0;
}
/* 0133FDB3 constructor
0133FDA7 constructor
in foo : 0133FDB3
0133FCDB cp contructor from 0133FDB3
0133FDA7 operator = 0133FCDB
0133FCDB destructor
in main : 0133FDA7
0133FDA7 destructor
0133FDB3 destructor */
此时main 栈上通过拷贝构造产生了中间变量,中间变量向t 发生了赋值。
9.2.2 本质结论
以上windows 和linux 相同,以下linux 作了更深层次的优化,
A foo()
{
A b;
cout << "in foo :" << (void*)&b << endl;
return b;
}
int main()
{
A t = foo();
cout << "in main:" << (void*)&t << endl;
return 0;
}
/* 0135FDAF constructor
in foo :0135FDAF
0135FEAF cp contructor from 0135FDAF
0135FDAF destructor
in main:0135FEAF
0135FEAF destructor */
此时发生了一次构造,一次析构。也就是main 中的t 取代了临时空间,而b 的构造完成了t 的构造。所在完成了一次构造,一次析构。此时t 的地址,同b 的地址相同。
A func()
{
A b;
cout << "in func &b " << &b << endl;
return b;
}
int main()
{
A t;
cout << "int main &t " << &t << endl;
t = func();
cout << "t:id" << &t << endl;
return 0;
}
/* 006FFC17 constructor
int main &t 006FFC17
006FFB03 constructor
in func &b 006FFB03
006FFB4B cp contructor from 006FFB03
006FFB03 destructor
006FFC17 operator = 006FFB4B
006FFB4B destructor
006FFC17 destructor */
此时发生了两次构造,一次赋值,两次析构,其中b 的构造,完成了main 函数中临时空间的构造,构造的临时对象作为赋值运算符重载的参数传入。然后发生赋值。两次析构分别是临时空间和t 的析构。
9.3 c++返回栈对象引用
返回栈对象的引用,多用于产生串联应用,比如连等式。栈对象是不可以返回引用的,除非,函数的调用者返回自身对象。
比如:
MyString &MyString::operator = (const MyString& another) {
if(this == &another) {
return *this;
} else {
delete []this->str;
int len = strlen(another._str);
this->_str = new char[len + 1];
strcpy(this->_str, another._str);
return *this;
}
}
提高:非要返回栈引用会发生什么
#include <iostream>
#include <typeinfo>
using namespace std;
class A {
public:
A()
{
cout << this << " constructor" << endl;
}
A(const A& a)
{
cout << this << "cp contructor from " << &a << endl;
}
~A()
{
cout << this << " destructor" << endl;
}
};
const A& func()
{
A b;
cout << "in func &a" << &b << endl;
return b;
}
int main()
{
A t = func();
cout << "int main &t" << &t << endl;
return 0;
}
/*
0000008B0D2FF550 constructor
in func &a0000008B0D2FF550
0000008B0D2FF550 destructor
0000008B0D2FF590cp contructor from 0000008B0D2FF550
int main &t0000008B0D2FF590
0000008B0D2FF590 destructor
*/
返回的引用,完成了一次拷贝,但是被拷贝的对象,己经析构。结果是未知的,所以不要返回栈上的引用。
10 栈和堆上的对象及对象数组
10.1 引例
#include <iostream>
using namespace std;
class Stu {
public:
Stu(string n)
: _name(n)
{
}
void dis()
{
cout << _name << endl;
}
private:
string _name;
};
int main()
{
// Stu s; // 没有无参构造器
// Stu s[5] = { Stu("zhangsan"), Stu("lisi") }; // 不能指定个数或部分初始化,则会报错。
Stu s[] = { Stu("zhangsan"), Stu("lisi") }; // OK
// Stu* ps = new Stu[4] { Stu("zhangsan") };
// C11 中支持此种初始化方法,但必须对指定的类个数初始化,否则会报错。
Stu* ps = new Stu[1] { Stu("zhangsan") };
return 0;
}
10.2 用new 和delete生成销毁堆对象
new 一个堆对象,会自动调用构造函数,delete 一个堆对象对自动调用析构函数。这同 c 中 malloc 和 free 不同的地方。
10.3 栈对象数组
如果生成的数组,未初始化,则必调用无参构造器。或手动调用带参构造器。
10.4 堆对象数组
如果生成的数组,未初始化,则必调用无参构造器。或手动调用带参构造器。
10.5 结论
构造器无论是重载还是默认参数,一定要把系统默认的无参构造器包含进来。不然生成数组的时候,可能会有些麻烦。
11 成员函数的存储方式
11.1.类成员可能的组成
用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数, 要分别为数据和函数的代码分配存储空间。
按理说,如果用同一个类定义了 10 个对象,那么就需要分别为 10 个对象的数据和函数代码分配存储单元。
11.2 类成员实际的组成
能否只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。
显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间。
#include <iostream>
using namespace std;
class Time {
public:
void dis()
{
cout << hour << minute << sec << endl;
}
private:
int hour;
int minute;
int sec;
};
int main()
{
cout << sizeof(Time) << endl; //12
//一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关
return 0;
}
11.3 调用原理
所的有对象都调用共用的函数代码段,如何区分的呢,执行不同的代码可能有不同的结果。原因是:c++设置了 this 指针,this 指针指向调用该函数的不同对象。当 t 调用dis()函数时,this 指向 t。当 t1 调用 dis()函数时,this 指向 t1。
#include <iostream>
using namespace std;
class Time {
public:
Time(int h, int m, int s)
: hour(h)
, minute(m)
, sec(s)
{
}
void dis() // void dis(Time *p)
{
cout << "this=" << this << endl;
cout << this->hour << ":" << this->minute << ":" << this->sec << endl;
}
private:
int hour;
int minute;
int sec;
};
int main()
{
Time t(1, 2, 3);
Time t2(2, 3, 4);
t.dis(); // 等价于 t.dis(&t)
t2.dis();
return 0;
}
11.4 注意事项
- 不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储
- 不要将成员函数的这种存储方式和 inline (内置)函数的概念混淆。inline 的逻辑意义是将函数内嵌到调用代码处,减少压栈与出栈的开支
- 应当说明,常说的“某某对象的成员函数”,是从逻辑的角度而言的,而成员函数的存储方式,是从物理的角度而言的,二者是不矛盾的。类似于二维数组是逻辑概念,而物理存储是线性概念一样
12 const 修饰符
12.1 常数据成员
const 修饰类的成员变量,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。可被 const 和非 const 成员函数调用,而不可以修改。 ```cpp class A { public: A()
{ }: iValue(199)
private: const int iValue; }
<a name="FRhxb"></a>
## 12.2 常成员函数
<a name="z3AEn"></a>
### 12.2.1 const 修饰函数的意义
承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数
<a name="LCaLZ"></a>
### 12.2.2 const 修饰函数位置
const 修饰函数放在声明之后,实现体之前,大概也没有别的地方可以放了
```cpp
void dis() const
{
}
12.2.3 const 构成函数重载
#include <iostream>
using namespace std;
class A {
public:
A()
: x(199)
, y(299)
{
}
void dis() const //const 对象调用时,优先调用
{
// input()不能调用非 const 函数, 因为本函数不会修改,无法保证所调的函数也不会修改
cout << "x " << x << endl;
cout << "y " << y << endl;
// y = 200; // const修饰函数表示承诺不对数据成员修改。
}
void dis() // 此时构成重载, 非 const 对象时,优先调用。
{
y = 200;
input();
cout << "x " << x << endl;
cout << "y " << y << endl;
}
void input()
{
cin >> y;
}
private:
const int x;
int y;
};
int main()
{
A a;
a.dis();
// const A a;
// a.dis();
return 0;
}
小结:
- 如果 const 构成函数重载,const 对象只能调用 const 函数,非 const 对象优先调用非 const 函数。
- const 函数只能调用 const 函数。非 const 函数可以调用 const 函数。
类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。
12.3 常对象
const A a; a.dis();小结:
const 对象,只能调用 const 成员函数
- 可访问 const 或非 const 数据成员,不能修改
13 static 修饰符
在 C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏(相比全局变量的优点)的原则,保证了安全性还可以节省内存。
13.1 类静态数据成员的定义及初始化
15.1.1 声明:
static 数据类型 成员变量; // 在类的内部
15.1.2 初始化
数据类型 类名::静态数据成员 = 初值; //在类的外部
15.1.3 调用
类名::静态数据成员
类对象.静态数据成员
13.1.5 小结
- static 成员变量实现了同簇类对象间信息共享。
- static 成员类外存储,求类大小,并不包含在内。
- static 成员是命名空间属于类的全局变量,存储在 data 区 rw 段。
- static 成员使用时必须实始化,且只能类外初始化。
- 可以通过类名访问(无对象生成时亦可),也可以通过对象访问。
13.2 类静态成员函数的定义
为了管理静态成员,c++提供了静态函数,以对外提供接口。静态函数只能访问静态成员。13.2.1 声明
static 函数声明13.2.2 调用.15.2.3.案例
类名::函数调用 类对象.函数调用13.2.3 案例
```cppinclude
using namespace std; class Student { public: Student(int n, int a, float s)
{ } void total() {: num(n) , age(a) , score(s)
} static float average();count++; sum += score;
private: int num; int age; float score; static float sum; static int count; };
float Student::sum = 0;
int Student::count = 0;
float Student::average() { return sum / count; }
int main() { Student stu[3] = { Student(1001, 14, 70), Student(1002, 15, 34), Student(1003, 16, 90) }; for (int i = 0; i < 3; i++) { stu[i].total(); } cout << Student::average() << endl; return 0; }
<a name="jXmN1"></a>
### 13.2.4 小结
- 静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。
- 静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时 this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。
<a name="wfmtk"></a>
# 15 static const 成员
如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。修饰成员函数,格式并无二异,修饰数据成员。必须要类内部实始化。
```cpp
class A {
public:
static const void dis()
{
cout << i << endl;
}
private:
const static int i = 100;
};
int main()
{
A::dis();
return 0;
}
16 指向类成员的指针
在 C++语言中,可以定义一个指针,使其指向类成员或成员函数,然后通过指针来访问类的成员。这包括指向属性成员的指针和指向成员函数的指针。
16.1 指向普通变量和函数的指针
#include <iostream>
using namespace std;
void func(int a)
{
cout << a << endl;
}
int main()
{
int a = 100;
int* p = &a;
cout << *p << endl;
void (*pf)(int) = func;
pf(10);
return 0;
}
16.2 指向类数据成员的指针
定义
<数据类型><类名>::*<指针名>
赋值&初始化
<数据类型><类名>::*<指针名>[=&<类名>::<非静态数据成员>]
指向非静态数据成员的指针在定义时必须和类相关联,在使用时必须和具体的对象关联。
解用引
由于类不是运行时 存在的对象。因此,在使用这类指针时,需要首先指定类的一个对象,然后,通过对象来引用指针所指向的成员。
<类对象名>.*<指向非静态数据成员的指针>
<类对象指针>->*<指向非静态数据成员的指针>
案例
#include <iostream>
using namespace std;
class Student {
public:
Student(string n, int nu)
: name(n)
, num(nu)
{
}
string name;
int num;
};
int main()
{
Student s("zhangsan", 1002);
Student s2("lisi", 1001);
// string *ps = &s.name;
// cout<< *ps<<endl;
string Student::*ps = &Student::name;
cout << s.*ps << endl;
cout << s2.*ps << endl;
Student* pp = new Student("wangwu", 1003);
cout << pp->*ps << endl;
return 0;
}
16.3 指向类成员函数的指针
定义一个指向非静态成员函数的指针必须在三个方面与其指向的成员函数保持一致:参数列表要相同、返回类型要相同、所属的类型要相同
定义
<数据类型>(<类名>::*<指针名>)(<参数列表>)
赋值&初始化
<数据类型>(<类名>::*<指针名>)(<参数列表>)[=&<类名>::<非静态成员函数>]
解用引
由于类不是运行时存在的对象。因此,在使用这类指针时,需要首先指定类的一个对象,然后,通过对象来引用指针所指向的成员。
(<类对象名>.*<指向非静态成员函数的指针>)(<参数列表>)
(<类对象指针>->*<指向非静态成员函数的指针>)(<参数列表>)
案例
#include <iostream>
using namespace std;
class Student {
public:
Student(string n, int nu)
: name(n)
, num(nu)
{
}
void dis()
{
cout << "name " << name << " num " << num << endl;
}
private:
string name;
int num;
};
int main()
{
Student s("zhangsan", 1002);
Student s2("lisi", 1001);
Student* ps = new Student("lisi", 1003);
void (Student::*pf)() = &Student::dis;
(s.*pf)();
(s2.*pf)();
(ps->*pf)();
return 0;
}
17.4 指向类成员指针小结:
与普通意义上的指针不一样,存放的是偏移量。
指向非静态成员函数时,必须用类名作限定符,使用时则必须用类的实例作限定符。指向静态成员函数时,则不需要使用类名作限定符。
17.5 指向类静态成员的指针
指向类静态数据成员的指针
指向静态数据成员的指针的定义和使用与普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联。指向类静态成员函数的指针
指向静态成员函数的指针和普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联。 ```cppinclude
using namespace std;
class A { public: static void dis(); static int data; };
void A::dis() { cout << data << endl; }
int A::data = 100;
int main() { int p = &A::data; cout << p << endl; void (*pfunc)() = &A::dis; pfunc(); return 0; } ```
