C++:类与对象
注意,这是笔记,不是学习手册!!!有可能不适合别人阅读,如果读者有什么问题欢迎在评论区提问!!
类与对象
不要把类看的多么高深莫测,通过C语言中类型和变量的方式来类比记忆。
- 类:类型
- 对象:变量
- 属性:数据
- 行为:操作(函数)
在C++中,类可以看作一种自定义的类型,和C的结构体很像。
对于这个类,其中包含的每一个类型就是一个成员(结构体中的类型)也叫做类的属性 ,对于每个成员,都有可以对其进行操作的函数,这个函数就叫做成员方法 ,这个成员方法也叫做类的行为。
例子:
类 | 对象 |
---|---|
Cat | garfield |
Dog | odie |
People | hug |
类是数据 和行为的集合。
定义类与使用:
cpp
class Cat{
};
class Dog{
};
class People{
};
Cat garfield;
Dog odie;
People hug;
例子:
cpp
class People{
// 属性
string name;
Day birthday;
double height;
double weight;
void say(string word);
void run(Location &loc);
}
cpp
/*************************************************************************
> File Name: class.cpp
> Author:Royi
> Mail:royi990001@gmail.com
> Created Time: Fri 23 Feb 2024 07:56:13 PM CST
> Describe:
************************************************************************/
#include <iostream>
#include <algorithm>
#include <list>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <ctype.h>
#include <cmath>
#include <string>
#include <sstream>
using namespace std;
class People {
public:
string name;
unsigned int age;
char sex;
void say() {
cout << "My name is " << name;
cout << ", I'm " << age << "years old";
if (sex == 'm') {
cout << "I'm a boy!" << endl;
} else {
cout << "I'm a girl" << endl;
}
}
};
int main() {
People hug;
hug.name = "Captain Hu";
hug.age = 40;
hug.sex = 'm';
hug.say();
return 0;
}
类的作用
在程序设计时我们通过在类中规定一个值的界限而确定该对象的是否执行了合理的操作:
cpp
#include <iostream>
#include <algorithm>
#include <list>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <ctype.h>
#include <cmath>
#include <string>
#include <sstream>
using namespace std;
class People {
public :
void say() {
cout << name << ", " << age << endl;
}
void set_name(string name) {
this->name = name;
return ;
}
void set_age(int age) { // 一个人的名字不能为负数
if (age <= 0) {
perror("Invalid parameters");
exit(1);
} else {
this->age = age;
return ;
}
}
private :
string name;
int age;
};
int main() {
People hug;
hug.set_name("Royi");
hug.set_age(40);
hug.say();
return 0;
}
访问权限
关键字 | 作用(类外对类内) |
---|---|
public | 公告访问权限(所有人) |
private | 私有访问权限(自己) |
protected | 受保护的访问权限(自己和儿子) |
friendly | 破坏私有(可以访问类内的类外方法) |
- 属性设置成
private
:只允许类内方法访问自己。
this
this是一个指针,这个指针指向当前对象。
构造/析构函数
构造/析构函数 | 使用方式 |
---|---|
默认构造函数 | People a; |
People(string name); | People a("hug"); |
People(const People &a) | 拷贝构造,与=不等价 |
~People(); | 无 |
类的定义与销毁
先运行对象的构造函数,执行完之后这个对象就算成立了。
当对象被销毁时,会调用析构函数。
在类中会有一个默认的构造函数,它没有任何参数(编译器添加)。
类只有在定义时才会调用构造函数。
有参构造和转换构造函数
- 有参构造:在基础的构造函数中加入参数,调用时传入参数
- 转换构造:只有一个参数的有参构造函数。给类提供了将其他类型的值隐式的转换为该类的方法。
显式的类型转换:强制类型转换
隐式的类型转换:程序中看不见,是自动的
cpp
// ...
// 这是一个转换构造函数
A(int x) {
cout << "transfer constructor" << endl;
this->x = x;
this->y = 0;
}
// 调用时
void func(A a) {
cout << "func :";
a.output();
return;
}
// 如果调用
func(6);
// 也能通过编译,这种情况下就使用了隐式的类型转换,下面的代码也是转换构造:
A a = 4;
// 将4赋值给a类对象,调用的是转换构造
func(6)
函数在调用时实际上发生了这样的操作:func(A a = 6)
拷贝构造函数
传入相同类型的一个对象的引用时,将传入对象的值拷贝给当前对象。
cpp
//下面是一个拷贝构造函数
A(const A &a) {
cout << "copy constructor";
this->x = a.x;
this->y = a.y;
}
int mian() {
A a; // 调用的a类的默认构造函数
A b = a, c; // 调用的b类的拷贝构造函数,调用的是c类的默认构造函数
c = a; // 没有调用构造函数,使用了c的赋值运算符符号,但是需要重载
}
注意:只有定义类时,才会调用构造函数
为什么拷贝构造函数一定要传入const类型的值?
假设我们有一个 const
类型的类 const A a
, 如果:
cpp
int mian() {
const A a;
A b = a; // 调用了拷贝构造,会报错
}
因为 const A
表示"不能变",但是 &
表示可以变,逻辑冲突。
为什么拷贝构造一定要传入引用?
假设有这样的一行代码:
cpp
A c = a;
我们期望调用 c
的拷贝构造函数,但是如果 c
的拷贝构造函数中传入的不是引用,将会是下面的结果:
cpp
// 相当于将外部的a对象拷贝给参数列表中的a对象
A(const A = a) {
...
}
这将会再次调用参数 A
对象的拷贝函数,这样会发生递归。
构造函数与析构函数的具体执行流程
第一原则
先构造的后析构。(先构造的对象有可能被后后构造的对象所依赖)
类中属性的构造顺序和当前类的构造顺序
初始化列表
故名思意,先给该类中的属性进行初始化。
cpp
class C{
public:
// 初始化列表
C(string n) : ch = 'a', a(n + ".a"), b(n + ".b") {
name[this] = n;
name.output(this, " class C constructor");
}
char ch;
A a, b;
~C() {
name.output(this, " class C destructor");
}
}
属性的构造顺序和当前类的构造顺序
属性先构造,本身对象再构造。(保证对属性的操作可执行)
属性的构造顺序与初始化列表的顺序无关。
属性的构造顺序只与属性的声明顺序有关。
使用初始化列表的原则
初始化列表的使用顺序与声明顺序应当相同。
代码:
展示了不同的方式定义的析构函数的用法:
constructor.cpp
cpp
#include <iostream>
#include <algorithm>
#include <list>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <ctype.h>
#include <cmath>
#include <string>
#include <sstream>
using namespace std;
class A;
class MyMap : public map<A *, string> {
public :
void output(A *p, string s) {
if (find(p) == end()) {
cout << "unknown name " << p;
} else {
cout << operator[](p);
}
cout << s << endl;
return ;
}
} name;
class A {
public :
A() {
name.output(this, " default constructor");
}
A(int x) {
name.output(this, " transfer constructor");
this->x = x;
this->y = 0;
}
A(const A &a) {
name.output(this, " copy constructor");
this->x = a.x;
this->y = a.y;
}
A(int x, int y) {
name.output(this, " 2 arguments constructor");
this->x = x;
this->y = y;
}
void operator=(const A &a) {
name.output(this, " operator=");
this->x = a.x;
this->y = a.y;
return ;
}
void output() {
cout << "(" << x << ", " << y << ")" << endl;
}
~A() {
name.output(this, " destructor");
}
private:
int x, y;
};
void func(A a) {
cout << "func : ";
a.output();
return ;
}
namespace copy_constructor {
int main() {
const A d;
A a;
A b = a, c = d;
name[&a] = "a";
name[&b] = "b";
name[&c] = "c";
cout << "c = a" << endl;
c = a;
cout << "a = " << &a << endl;
cout << "b = " << &b << endl;
cout << "b = " << &c << endl;
return 0;
}
}
namespace test1 {
int main() {
A a(3, 4), b(3), c = 4;
a.output();
b.output();
func(a);
func(b);
cout << "=========" << endl;
func(6);
return 0;
}
}
int main() {
//test1::main();
copy_constructor::main();
return 0;
}
下面这个代码解释了构建与释放顺序
constructer_order.cpp:
cpp
#include <iostream>
#include <algorithm>
#include <list>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <ctype.h>
#include <cmath>
#include <string>
#include <sstream>
using namespace std;
class MyMap : public map<void *, string> {
public :
void output(void *p, string s) {
if (find(p) == end()) {
cout << "unknown name " << p;
} else {
cout << operator[](p);
}
cout << s << endl;
return ;
}
} name;
class A {
public :
A(string n) {
name[this] = n;
name.output(this, "class A constructor");
}
~A() {
name.output(this, "class A distructor");
}
};
class B {
public :
B(string n) {
name[this] = n;
name.output(this, " class B constructor");
}
};
int main() {
A a("a"), b("b");
return 0;
}