![第七章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024012819012084.webp)
接着上一章节继续:第六章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人 – 菜鸟资源 (xiciw.com)
4.9. 案例系统 string 与 MyString
4.9.1. string的使用
int main()
{
// string s("china");
string s = "china";
string s2(s);
string s3 = s2;
string s4;
s4 = s3;
}
4.9.2. MyString 声明
#ifndef MYSTRING_H_
#define MYSTRING_H_
#include <stddef.h>
#include <iostream>
class MyString {
public:
MyString(const char *str=NULL);
MyString(const MyString & other);
MyString & operator=(const MyString & another);
MyString operator+(const MyString & other);
bool operator==(const MyString &other);
bool operator>(const MyString &other);
bool operator<(const MyString &other);
char& operator[](int idx);
void dis();
virtual ~MyString();
private:
char * _str;
};
#endif /* MYSTRING_H_ */
4.9.3. 构造
MyString::MyString(const char *str) {
if(str == NULL)
{
_str = new char[1];
*_str = '�';
}
else
{
int len = strlen(str);
_str = new char[len+1];
strcpy(_str,str);
}
}
4.9.4. 析构
MyString::~MyString() {
delete []_str;
}
4.9.5. 拷贝构造(深拷贝)
MyString::MyString(const MyString & other)
{
int len = strlen(other._str);
this->_str = new char[len+1];
strcpy(this->_str,other._str);
}
4.9.6. 赋值运算符重载
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;
}
}
4.9.7. 加法运算符重载
MyString MyString::operator+(const MyString & other)
{
int len = strlen(this->_str) + strlen(other._str);
MyString str;
delete []str._str;
str._str = new char[len+1];
memset(str._str,0,len+1);
strcat(str._str,this->_str);
strcat(str._str,other._str);
return str;
}
4.9.8. 关系运算符重载
bool MyString::operator==(const MyString &other)
{
if(strcmp(this->_str,other._str) == 0)
return true;
else
return false;
}
bool MyString::operator>(const MyString &other)
{
if(strcmp(this->_str,other._str) > 0)
return true;
else
return false;
}
bool MyString::operator<(const MyString &other)
{
if(strcmp(this->_str,other._str) < 0)
return true;
else
return false;
}
4.9.9. []运算符重载
char& MyString::operator[](int idx)
{
return _str[idx];
}
4.9.10. 测试
#include <iostream>
#include "mystring.h"
using namespace std;
int main()
{
// MyString s = "china";
MyString s("china"); //构造器
s.dis();
// MyString s2 = s;
MyString s2(s); //拷贝构造器
s2.dis();
MyString s3;
s3 = s2 = s; //赋值运算符重载
s3.dis();
MyString s4;
s4 = "america"; //"america" 无名对象的构造器,赋值运算符重载
return 0;
}
4.10.课常练习
4.10.1. 实现钟表类
属性:时,分,秒
行为:run() 在屏幕上实现电子时钟 13:34:45 每隔一秒更新一个显示。
4.10.2. 分析:
构造时,初始化为当前系统时间,然后每隔一妙,刷屏。
4.10.3. 代码
#include <iostream>
#include <time.h>
#include <unistd.h>
using namespace std;
class Clock
{
public:
Clock()
{
time_t t = time(NULL);
tm local = * localtime(&t);
_hour = local.tm_hour;
_minute = local.tm_min;
_second = local.tm_sec;
}
void run()
{
for(;;)
{
tick();
show();
}
}
private:
void tick()
{
sleep(1);
if(++_second == 60)
{
_second = 0;
if(++_minute == 60)
{
_minute = 0;
if(++_hour == 24)
{
_hour = 0;
}
}
}
}
void show()
{
system("cls");
cout<<_hour<<":"<<_minute<<":"<<_second<<endl;
}
int _hour;
int _minute;
int _second;
};
int main()
{
Clock c;
c.run();
return 0;
}
4.11.栈和堆上的对象及对象数组
4.11.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")};
// Stu * ps = new Stu[4]{Stu("zhangsan")};
// C11 中支持此种初始化方法,但必须对指名的类个数初始化,否则会报错。
Stu * ps = new Stu[1]{Stu("zhangsan")};
return 0;
}
4.11.2. 用new 和delete生成销毁堆对象
new 一个堆对象,会自动调用构造函数,delete 一个堆对象对自动调用析构函数。这同 c 中 malloc 和 free 不同的地方。
4.11.3. 栈对象数组
如果生成的数组,未初始化,则必调用无参构造器。或手动调用带参构造器。
4.11.4. 堆对象数组
如果生成的数组,未初始化,则必调用无参构造器。或手动调用带参构造器。
4.11.5. 结论
构造器无论是重载还是默认参数,一定要把系统默认的无参构造器包含进来。不然生成数组的时候,可能会有些麻烦。
4.12.成员函数的存储方式
4.12.1. 类成员可能的组成
用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间。
按理说,如果用同一个类定义了 10 个对象,那么就需要分别为 10 个对象的数据和函数代码分配存储单元。
![第七章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024012909580439.webp)
4.12.2. 类成员实际的组成
能否只用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。
![第七章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024012909592035.webp)
显然,这样做会大大节约存储空间。C++编译系统正是这样做的,因此每个对象所占用的存储空间只是该对象的数据部分所占用的存储空间,而不包括函数代码所占用的存储空间。
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;
}
4.12.3. 调用原理
所的有对象都调用共用的函数代码段,如何区分的呢,执行不同的代码可能有不用的结果。原因,C++设置了 this 指针,this 指针指向调用该函数的不同对象。当 t 调用dis()函数时,this 指向 t。当 t1 调用 dis()函数时,this 指向 t1;
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;
}
4.12.4. 注意事项
1,不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式 存储。
2,不要将成员函数的这种存储方式和 inline(内置)函数的概念混淆。inline 的逻辑 意义是将函数内嵌到调用代码处,减少压栈与出栈的开支。
3,应当说明,常说的“某某对象的成员函数”,是从逻辑的角度而言的,而成员 函数的存储方式,是从物理的角度而言的,二者是不矛盾的。类似于二维数组是逻 辑概念,而物理存储是线性概念一样。
4.13.const 修饰符
4.13.1. 常数据成员
const 修饰类的成员变量,表示成员常量,不能被修改,同时它只能在初始化列表 中赋值。
可被 const 和非 const 成员函数调用,而不可以修改。
class A
{
public:
A():iValue(199){}
private:
const int iValue;
};
4.13.2. 常成员函数
4.13.2.1. const 修饰函数的意义
承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数
4.13.2.2. const 修饰函数位置
const 修饰函数放在,声明这后,实现体之前,大概也没有别的地方可以放了。
void dis() const
{}
4.13.2.3. const 构成函数重载
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;
}
小结:
1,如果 const 构成函数重载,const 对象只能调用 const 函数,非 const 对象优先 调用非 const 函数。
2,const 函数只能调用 const 函数。非 const 函数可以调用 const 函数。
3,类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。
4.13.3. 常对象
const A a;
a.dis();
小结:
1,const 对象,只能调用 const 成员函数。
2,可访问 const 或非 const 数据成员,不能修改。
4.14.static 修饰符
在 C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏(相比全局变量的优点)的原则,保证了安全性还可以节省内存。
类的静态成员,属于类,也属于对象,但终归属于类。
4.14.1. 类静态数据成员的定义及初始化
4.14.1.1. 声明:
static 数据类型 成员变量; //在类的内部
4.14.1.2. 初始化
数据类型 类名::静态数据成员 = 初值; //在类的外部
4.14.1.3. 调用
类名::静态数据成员
类对象.静态数据成员
4.14.1.4. 案例
中国校园设计的“一塔湖图”
#include <iostream>
using namespace std;
class School
{
public:
static void addLibBooks(string book)
{
lib += book;
}
public:
string tower;
string lake;
static string lib;
};
//类外实始化
string School::lib = "Beijing lib:";
int main()
{
School a,b,c,d;
//static 数据成员,属于类,并不属于对象。
//存储于 data 区的 rw 段
cout<<sizeof(a)<<sizeof(b)<<sizeof(c)<<sizeof(d)<<endl;
//类的方式访问,编译有问题,必须要初始化。
//在无对象生成的时候,亦可以访问。
School::lib = "China lib:";
cout<<School::lib<<endl;
//lib 虽然属于类,但是目的是为了实现类对象间的共享
//故对象也是可以访问的。
cout<<a.lib<<endl;
cout<<b.lib<<endl;
//为了搞好图书馆的建设,提设 static 接口
School::addLibBooks("mao xuan");
cout<<School::lib<<endl;
return 0;
}
4.14.1.5. 小结
1,static 成员变量实现了同类对象间信息共享。
2,static 成员类外存储,求类大小,并不包含在内。
3,static 成员是命名空间属于类的全局变量,存储在 data 区。
4,static 成员使用时必须实始化,且只能类外初始化。
5,可以通过类名访问(无对象生成时亦可),也可以通过对象访问。
4.14.2. 类静态成员函数的定义
为了管理静态成员,c++提供了静态函数,以对外提供接口。并静态函数只能访问静态成员。
4.14.2.1. 声明
static 函数声明
4.14.2.2. 调用
类名::函数调用
类对象.函数调用
4.14.2.3. 案例
#include <iostream>
using namespace std;
class Student
{
public:
Student(int n,int a,float s):num(n),age(a),score(s){}
void total()
{
count++;
sum += score;
}
static float average();
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;
r eturn 0;
}
4.14.2.4. 小结
1,静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。
2,静态成员函数只能访问静态数据成员。原因:非静态成员函数,在调用时 this 指针时被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针
4.14.3. 综合案例
4.14.3.1. 单例模式
4.14.3.2. cocos 渲染树的模拟
每生成一个节点,自动挂到静态表头上去。
#include <string.h>
using namespace std;
class Student
{
public:
Student(string n)
:name(n)
{
if(head == NULL)
{
head = this;
this->next = NULL;
}
else{
this->next = head;
head= this;
}
//可优化
}
static void printStudentList();
static void deleteStudentList();
private:
string name;
Student * next;
static Student * head;
};
void Student::printStudentList()
{
Student * p = head;
while(p != NULL)
{
cout<<p->name<<endl;
p = p->next;
}
}
void Student::deleteStudentList()
{
Student *p = head;
while(head)
{
head = head->next;
delete p;
p = head;
}
}
Student * Student::head = NULL;
int main()
{
string name;
string postName;
char buf[1024];
for(int i=0; i<10; i++)
{
name = "stu";
postName = (itoa(i,buf,10));
name += postName;
new Student(name);
}
Student::printStudentList();
Student::deleteStudentList();
return 0;
}
4.15.static const 成员在下一章才菜鸟资源精讲课程讲解