面向对象的基本特征: 抽象、封装、继承、多态
class T
{
};
sizeof(T) = 1;
this指针: 在类的定义中用来表示当前对象自己的一个指针(该指针不占用对象的内存空间)
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <time.h>
using namespace std;
typedef enum CPU_Rank{P1 = 1, P2, P3, P4, P5, P6, P7}CPU_Rank;
class CPU
{
private:
CPU_Rank rank; //系列
int frequency; //频率
float voltage; //电压
public:
CPU() //无参构造
{
frequency = 2100000;
voltage = 3.3f;
rank = P3;
cout << "无参构造函数" << endl;
}
CPU(CPU_Rank rank, int frequency, float voltage) : rank(rank), frequency(frequency), voltage(voltage)
{
/*
//this指针专门用在类的定义中,在成员函数你们this指针用来表示执行当前对象自己的指针
this->rank = rank;
this->frequency = frequency;
this->voltage = voltage;
*/
cout << "带参数的构造函数" << endl;
}
~CPU()
{
cout << "析构函数" << endl;
}
void run()
{
cout << "CPU的等级为: " << rank << ", 工作电压: " << voltage << "V, 工作频率: " << frequency << "Hz" << endl;
}
void stop()
{
cout << "CPU停止运行" << endl;
}
};
int main()
{
CPU intel;
CPU stm32(P7, 72000000, 3.3f);
stm32.run();
sleep(1);
stm32.stop();
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> g++ .\CPU类测试.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
无参构造函数
带参数的构造函数
CPU的等级为: 7, 工作电压: 3.3V, 工作频率: 72000000Hz
CPU停止运行
析构函数
析构函数
复杂类型:
在一个类中,其数据成员可以是任何类型,也就是说在一个类中可以包含另外一个类的对象(类的数据成员是另外一个类的对象)
a. 在一个类中包含另外一个类的对象 -- has a -- 组合
组合是部分和整体的关系,所包含的对象的生命周期和包含它的对象的生命周期是一致的
class Wings
{
};
class Goose
{
private:
Wings w;
}
b. 聚合关系: 在一个类中包含另外一个类的指针(一个类的数据成员是另外一个类的指针对象);指针对象的值(指针所指向的另外一个类型的对象是独立存在的)
class Goose
{
private:
Wings *w;
}
作业:
定义一个简单的Computer类,有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,cpu为CPU类的一个对象,ram为RAM类的一个对象,cdrom为CDROM类的一个对象,定义并实现这个类。
#include <iostream>
#include <cstring> //string.h
using namespace std;
//类的定义: 从同类对象得到共同具有的属性和行为的过程是抽象
class Student
{
private: //一般在类的定义中,属性(数据成员)私有化 -- 私有成员在类的外部是不能访问的
int s_no;
char s_name[12];
float s_sc;
public: //公有成员在类的外部是可以访问的
void show() //类的成员函数可以实现在类的内部
{
cout << "学号: " << s_no << ", 姓名: " << s_name << ", 成绩: " << s_sc << endl;
}
Student() : s_no(0), s_sc(0)
{
cout << "无参构造函数" << endl;
strcpy(s_name, "");
}
Student(int no, const char *name, float sc) : s_no(no), s_sc(sc)
{
cout << "类的带参构造函数" << endl;
strcpy(s_name, name);
}
//析构函数
~Student()
{
cout << s_name << "析构函数" << endl;
}
void set_no(int no)
{
s_no = no;
}
void set_name(const char *name)
{
strcpy(s_name, name);
}
};
int main()
{
Student st; //在实例化对象的时候会自动的调用构造函数来进行空间分配和成员的初始化;调用无参构造函数
st.show();
Student zhangsan(1003, "Smith", 88.5f); //调用带参数构造函数
zhangsan.show();
Student zhang = zhangsan;
zhang.set_name("张三");
zhang.set_no(1005);
zhang.show();
zhangsan.show();
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> g++ .\类的定义2.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
无参构造函数
学号: 0, 姓名: , 成绩: 0
类的带参构造函数
学号: 1003, 姓名: Smith, 成绩: 88.5
学号: 1005, 姓名: 张三, 成绩: 88.5
学号: 1003, 姓名: Smith, 成绩: 88.5
张三析构函数
Smith析构函数
析构函数
注意: 从程序的运行结果可以看到,程序中定义的对象zhang分配了独立的内存空间但没有调用无参构造也没有调用带参数的构造函数
当从一个已有的对象构建一个新的对象的时候使用拷贝构造函数(系统会默认生成一个拷贝构造函数)
拷贝构造函数的原型:
类名 (类名 &对象)
#include <iostream>
#include <cstring> //string.h
using namespace std;
//类的定义: 从同类对象得到共同具有的属性和行为的过程是抽象
class Student
{
private: //一般在类的定义中,属性(数据成员)私有化 -- 私有成员在类的外部是不能访问的
int s_no;
char s_name[12];
float s_sc;
public: //公有成员在类的外部是可以访问的
void show() //类的成员函数可以实现在类的内部
{
cout << "学号: " << s_no << ", 姓名: " << s_name << ", 成绩: " << s_sc << endl;
}
Student() : s_no(0), s_sc(0)
{
cout << "无参构造函数" << endl;
strcpy(s_name, "");
}
Student(int no, const char *name, float sc) : s_no(no), s_sc(sc)
{
cout << "类的带参构造函数" << endl;
strcpy(s_name, name);
}
//拷贝构造函数
Student(Student &t)
{
/*
默认的拷贝构造函数的实现:
s_no = t.s_no;
s_name = t.s_name; //对于字符串采用字符串拷贝的方式
s_sc = t.s_sc;
*/
s_no = t.s_no;
s_sc = t.s_sc;
strcpy(s_name, t.s_name);
cout << "拷贝构造函数" << endl;
}
//析构函数
~Student()
{
cout << s_name << "析构函数" << endl;
}
void set_no(int no)
{
s_no = no;
}
void set_name(const char *name)
{
strcpy(s_name, name);
}
};
int main()
{
Student st; //在实例化对象的时候会自动的调用构造函数来进行空间分配和成员的初始化;调用无参构造函数
st.show();
Student zhangsan(1003, "Smith", 88.5f); //调用带参数构造函数
zhangsan.show();
Student zhang = zhangsan;
zhang.set_name("张三");
zhang.set_no(1005);
zhang.show();
zhangsan.show();
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
无参构造函数
学号: 0, 姓名: , 成绩: 0
类的带参构造函数
学号: 1003, 姓名: Smith, 成绩: 88.5
拷贝构造函数
学号: 1005, 姓名: 张三, 成绩: 88.5
学号: 1003, 姓名: Smith, 成绩: 88.5
张三析构函数
Smith析构函数
析构函数
思考:
Student lisi;
lisi = zhangsan;
lisi会调用无参构造函数;赋值语句会调用赋值运算符重载函数
#include <iostream>
#include <cstring> //string.h
using namespace std;
//类的定义: 从同类对象得到共同具有的属性和行为的过程是抽象
class Student
{
public: //一般在类的定义中,属性(数据成员)私有化 -- 私有成员在类的外部是不能访问的
int s_no;
char *s_name;
float s_sc;
public: //公有成员在类的外部是可以访问的
void show() //类的成员函数可以实现在类的内部
{
cout << "学号: " << s_no << ", 姓名: " << s_name << ", 成绩: " << s_sc << endl;
}
Student() : s_no(0), s_sc(0)
{
cout << "无参构造函数" << endl;
//对于类中的指针成员在构造函数中要动态分配空间
//对于字符指针类型的数据成员的操作是先分配空间,然后使用字符串拷贝函数将初始值拷贝到分配的空间中
s_name = new char[8];
strcpy(s_name, "");
}
Student(int no, const char *name, float sc) : s_no(no), s_sc(sc)
{
cout << "类的带参构造函数" << endl;
s_name = new char[strlen(name) + 1]; //根据初始值所需要的内存空间来进行分配
strcpy(s_name, name);
}
//拷贝构造函数: 当类中有指针成员并且指针成员是在构造函数中动态分配空间的时候,一般都需要手动实现自己的拷贝构造函数
Student(Student &t)
{
s_no = t.s_no;
s_sc = t.s_sc;
s_name = new char[strlen(t.s_name) + 1];
strcpy(s_name, t.s_name);
cout << "拷贝构造函数" << endl;
}
//析构函数
~Student()
{
delete[] s_name;
cout << s_name << "析构函数" << endl;
}
};
int main()
{
Student zhangsan(1002, "Smith", 88.5f);
Student lisi = zhangsan;
zhangsan.show();
lisi.show();
zhangsan.s_name[2] = 'x';
zhangsan.show();
lisi.show();
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> g++ .\拷贝构造函数.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
类的带参构造函数
拷贝构造函数
学号: 1002, 姓名: Smith, 成绩: 88.5
学号: 1002, 姓名: Smith, 成绩: 88.5
学号: 1002, 姓名: Smxth, 成绩: 88.5
学号: 1002, 姓名: Smith, 成绩: 88.5
Smith析构函数
Smxth析构函数
浅拷贝和深拷贝
浅拷贝: 所谓浅拷贝就是在拷贝构造函数中是通过赋值运算符号来将已有对象的数据成员依次赋值给新创建的对象的数据成员(默认拷贝构造函数的实现方式就是这样的);如果类的数据成员中有指针成员,如果在构建对象的时候使用赋值运算符来来初始化就表示两个对象的指针成员指向一个相同的位置(第一,两个对象操作该成员的时候会相互影响; 第二, 在析构函数中释放该数据成员动态分配的空间的时候,会发生二次释放(double free)的错误 -- 段错误 -- 对同一块内存空间释放两次)。
深拷贝: 在拷贝构造函数中不直接使用赋值运算符来初始化新对象的指针成员,而是采用先动态分配空间然后再将已有对象的指针指向的数据成员的值拷贝到新对象中(新旧两个对象的指针成员分别指向自己对象中分配的独立的空间),这样两个对象的指针成员部分相互影响
class Test
{
private:
int len;
int *data;
public:
Test(){}
Test(int *data, int len)
{
this->len = len;
this->data = new int[len];
for(int i = 0; i < len; i++)
{
(this->data)[i] = data[i];
}
}
Test(Test &t)
{
this->len = t.len;
data = new int[len];
//可以采用内存拷贝函数memcpy来进行内存复制
//也可以使用循环将t的data数组一个一个复制到data中
}
};
在下述三种情况下,需要用拷贝初始化构造函数
1)明确表示由一个对象初始化另一个对象时,TPoint P2(P1);或者TPoint P2 = P1;
2)当对象作为函数实参传递给函数形参时,P=f(N);
3)当对象用为函数返回值时,如:return R;
#include <iostream>
#include <cstring> //string.h
using namespace std;
//类的定义: 从同类对象得到共同具有的属性和行为的过程是抽象
class Student
{
public: //一般在类的定义中,属性(数据成员)私有化 -- 私有成员在类的外部是不能访问的
int s_no;
char *s_name;
float s_sc;
public: //公有成员在类的外部是可以访问的
void show() //类的成员函数可以实现在类的内部
{
cout << "学号: " << s_no << ", 姓名: " << s_name << ", 成绩: " << s_sc << endl;
}
Student() : s_no(0), s_sc(0)
{
cout << "无参构造函数" << endl;
//对于类中的指针成员在构造函数中要动态分配空间
//对于字符指针类型的数据成员的操作是先分配空间,然后使用字符串拷贝函数将初始值拷贝到分配的空间中
s_name = new char[8];
strcpy(s_name, "");
}
Student(int no, const char *name, float sc) : s_no(no), s_sc(sc)
{
cout << "类的带参构造函数" << endl;
s_name = new char[strlen(name) + 1]; //根据初始值所需要的内存空间来进行分配
strcpy(s_name, name);
}
//拷贝构造函数: 当类中有指针成员并且指针成员是在构造函数中动态分配空间的时候,一般都需要手动实现自己的拷贝构造函数
Student(Student &t)
{
s_no = t.s_no;
s_sc = t.s_sc;
s_name = new char[strlen(t.s_name) + 1];
strcpy(s_name, t.s_name);
//s_name = t.s_name;
cout << "拷贝构造函数" << endl;
}
//析构函数
~Student()
{
delete[] s_name;
cout << s_name << "析构函数" << endl;
}
};
void show_Stu(Student t) //当函数的参数是类的对象的引用的时候
{
t.show();
}
Student test()
{
Student a;
return a; //当对象作为函数的返回值的时候,会构建一个临时对象作为返回值(因为函数中对象的生命周期是到函数结束就销毁了)
}
int main()
{
Student zhangsan(1002, "Smith", 88.5f);
Student lisi = zhangsan;
show_Stu(lisi);
lisi = test();
/*
zhangsan.show();
lisi.show();
zhangsan.s_name[2] = 'x';
zhangsan.show();
lisi.show();
*/
return 0;
}
常成员: 当对象的数据成员或者成员函数使用const修饰的时候,称为常成员
#include <iostream>
using namespace std;
class Test
{
private:
int a;
const int b; //常成员
public:
Test() : a(0), b(0)
{}
Test(int n) : a(n), b(19)
{}
Test(int n1, int n2) : a(n1), b(n2)
{}
void show()
{
cout << "a = " << a << ", b = " << b << endl;
}
};
int main()
{
Test obj(12, 66);
obj.show();
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
a = 12, b = 66
#include <iostream>
using namespace std;
class Test
{
private:
int a;
const int b; //常成员
public:
//类的常数据成员必须在构造函数的初始值列表中进行初始化
Test() : a(0), b(0)
{
/*
a = 0;
b = 0;*/
}
Test(int n) : a(n), b(19)
{}
Test(int n1, int n2) : a(n1), b(n2)
{}
//常成员函数: 在函数的原型后面加上const修饰;在常成员函数中不能修改数据成员的值
void show() const
{
int x = 99;
//a = 99;
cout << "a = " << a << ", b = " << b << endl;
x = 88;
}
};
int main()
{
Test obj(12, 66);
obj.show();
return 0;
}
const可以区分重载: 有const和没有const可以构成重载函数
类的常对象调用const修饰的函数,普通对象默认的调用普通版本的函数;如果要调用其他版本的可以采用强制类型转换的方式来调用
#include <iostream>
using namespace std;
class Test
{
private:
int a;
const int b; //常成员
public:
//类的常数据成员必须在构造函数的初始值列表中进行初始化
Test() : a(0), b(0)
{
/*
a = 0;
b = 0;*/
}
Test(int n) : a(n), b(19)
{}
Test(int n1, int n2) : a(n1), b(n2)
{}
//常成员函数: 在函数的原型后面加上const修饰;在常成员函数中不能修改数据成员的值
void show() const
{
int x = 99;
//a = 99;
cout << "a = " << a << ", b = " << b << endl;
x = 88;
}
void show() //const可以用来区分重载
{
cout << "show()" << endl;
}
};
int main()
{
Test obj(12, 66);
obj.show(); //是调用const的还是没有const的? -- 调用普通的函数
const Test obj2;
obj2.show(); //调用const版本的
((Test)obj2).show();
//将非const的对象强制转换为const可以调用到const的成员函数
((const Test)obj).show(); //两个括号都是必须的
Test *a = new Test(10);
const Test *b = new Test(9);
a->show(); //调用普通版本的show
b->show(); //调用const修饰的show
((const Test *)a)->show(); //调用const修饰的show
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> g++ .\常成员.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
show()
a = 0, b = 0
show()
a = 12, b = 66
show()
a = 9, b = 19
a = 10, b = 19
mutable: 可变成员
mutable表示不稳定的意思,与const相反,而且mutable的引入正是为了突破const的限制
一个成员变量一旦被mutable修饰,就表示这个成员变量永远处于可变状态,即使是在const结尾的成员函数中
比如有一个成员为:mutable int myhour;
void noone()const
{
myhour += 3; //可以修改成员变量的值
}
volatile 易变类型: 通过volatile修饰的变量在进行运算的时候每次都是直接读取内存中的数据,而不使用寄存器中原来的值去进行运算(多线程中共享的数据、和硬件相关的寄存器的数据、中断处理程序中使用的数据、实时采集有关的数据等)
volatile int d;
隐式类型转换
#include <iostream>
#include <cstring>
using namespace std;
class Time
{
private:
int hour;
int minute;
int second;
char *p; //时辰
public:
Time();
Time(int n);
Time(int h, int m, int s);
/*
Time(Time &t)
{
hour = t.hour;
}
*/
~Time();
void show() const;
};
int main()
{
Time now = 19;
now.show();
return 0;
}
Time::Time()
{
hour = 0;
minute = 0;
second = 0;
p = new char[8];
strcpy(p, "子时");
}
Time::Time(int n) //n是从1970年1月1日0是0分0每秒到现在的秒数
{
hour = 14;
minute = 35;
second = 28;
p = new char[8];
strcpy(p, "未时");
}
Time::Time(int h, int m, int s)
{
hour = h;
minute = m;
second = s;
p = new char[8];
strcpy(p, "现时");
}
Time::~Time()
{
delete[] p;
}
void Time::show() const
{
cout << hour << " : " << minute << " : " << second << endl;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> g++ .\隐式类型转换.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
14 : 35 : 28
注意:
当类中实现了单参数的构造函数并且没有拷贝构造函数(如果有拷贝构造函数会出现二义性的错误), 可以使用单参数构造函数的形参类型的值来直接初始化对象(Time now = 19;)
如果要避免隐式转换,可以在单参数的定义中加上explicit来禁止隐式转换
explicit Time(int n);
静态static
使用static修饰普通变量的时候,限定了该变量的作用域为当前文件,限定了该变量的存储域是全局静态区,区局静态区中的变量只初始化一次。
#include <iostream>
using namespace std;
void click()
{
static int times = 0;
times++;
cout << "当前点击量为: " << times << endl;
}
int main()
{
for(int i = 0; i < 10; i++)
{
click();
}
return 0;
}
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> g++ .\静态成员.cpp -o test
PS E:\LinuxShared\vscode\cpp_source\CPP_202308\CPP_03 类的定义> ./test
当前点击量为: 1
当前点击量为: 2
当前点击量为: 3
当前点击量为: 4
当前点击量为: 5
当前点击量为: 6
当前点击量为: 7
当前点击量为: 8
当前点击量为: 9
当前点击量为: 10
在类的定义中,static可以修饰数据成员,也可以修饰成员函数
#include <iostream>
#include <cstring>
using namespace std;
class Time
{
private:
int hour;
int minute;
int second;
char *p; //时辰
public:
//const static int count = 0;
static int count; //静态成员不影响对象的内存空间;静态成员不属于对象,它属于类
Time();
explicit Time(int n);
Time(int h, int m, int s);
/*
Time(Time &t)
{
hour = t.hour;
}
*/
~Time();
void show() const;
void click()
{
count++;
}
//类的静态成员函数
static void disp()
{
//在static成员函数中使用类的非静态成员必须通过对象
Time a;
a.show();
//在类的静态成员函数中能够直接访问的只有类的静态成员
cout << "count = " << count << endl;
}
};
//类中静态成员的初始化必须在类的外部
int Time::count = 0;
int main()
{
/*Time now = 19;
now.show();
Time thetime = (14, 46, 59);
//Time thetime = Time(14, 46, 59);
thetime.show();
*/
Time now(15, 02, 18);
now.click();
now.show();
now.click();
now.show();
Time thetime;
thetime.click();
thetime.show();
thetime.click();
thetime.show();
//类中的静态成员可以直接通过类名来进行访问
cout << "count = " << Time::count << endl;
cout << "sizeof(Time) = " << sizeof(Time) << endl;
return 0;
}
Time::Time()
{
hour = 0;
minute = 0;
second = 0;
p = new char[8];
strcpy(p, "子时");
}
Time::Time(int n) //n是从1970年1月1日0是0分0每秒到现在的秒数
{
hour = 14;
minute = 35;
second = 28;
p = new char[8];
strcpy(p, "未时");
}
Time::Time(int h, int m, int s)
{
hour = h;
minute = m;
second = s;
p = new char[8];
strcpy(p, "现时");
}
Time::~Time()
{
delete[] p;
}
void Time::show() const
{
cout << hour << " : " << minute << " : " << second << endl;
cout << "count = " << count << endl<<endl;
}
作业:
1. 定义一个简单的Computer类,有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,cpu为CPU类的一个对象,ram为RAM类的一个对象,cdrom为CDROM类的一个对象,定义并实现这个类。
2. 编写一个CCircle类,并应用该类的成员和方法。具体要求如下:
1)、 CCircle类中要求有私有数据成员半径和面积;
2)、 方法成员有不带参数的构造函数、带一个参数的构造函数、拷贝构造函数、析构函数、计算面积函数和显示面积函数;
3)、 CCircle类结构定义部分放在CCircle.h文件中,类方法放在CCircle.cpp文件中;main()主程序放在MyMain.cpp文件中;
4)、 在主程序中,定义CCircle类的对象,并初始化这些对象,然后应用这些对象。
3. 设计一个栈类
栈(stack)是程序设计过程中经常遇到的一种数据结构形式,它对于数据的存放和操作有下面这样的特点:
1) 它只有一个对数据进行存入和取出的端口;
2) 后进者先出,即最后被存入的数据将首先被取出。其形式很象一种存储硬币的小容器,每次只可以从顶端压入一个硬币,而取出也只可以从顶端进行,即后进先出。
4. 设计一个Bank类,实现银行某账号的资金往来账目管理,包括建账号、存入、取出等。
编写一个程序,在已设置好若干个用户名/口令后,通过输入用户名,查找对应的口令,连续执行这一过程直到用户输入结束标记(“end“)为止。
5. 一个射击运动员打靶,靶一共有 10 环,连开 10 枪打中90 环 的可能性有多少种?请用递归算法编程实现。