类和对象
类和对象:
类型与变量
类型 = 类型数据 + 类型操作
class People {
public:
void say(string world);
void run(Location &loc);//成员操作
private:
string __name;
Day __birthday;
double __height;
double __weight; //成员属性
};
访问权限:所用的属性和方法对外的访问权限
public: 公共访问权限 : 类内类外都可以访问
private:私有访问权限:类内可以访问,类外不可以访问
protected:受保护的访问权限:外不可访问,但是继承对象可以访问
friendly:友元函数可以访问类内的私有的访问权限
this指针:只有在成员方法可以访问this指针,用来指向当前对象,存储的是当前对象的地址
#include<iostream>
#include <string>
using std::string;
using std::cout;
using std::endl;
namespace haizei {//命名空间
class Cat {};
class Dog {};
class People {//类的默认访问权限是private
public:
string name;
int age;
double height;
double weight;
void say(string name) {//声明成员函数方式1,声明定义放在一起,适用写小程序
cout << "my name is " << this->name << name << endl;
return ;
}
void run();
};
}
int main() {
haizei::People hug;
hug.name = "Captain hu";
hug.say("hahaha");
return 0;
}
实现简单cout
cout << 变量
所以cout 是一个对象
代码演示:
#include<iostream>
#include <cstdio>
using namespace std;
#define BEGINS(x) namespace x {
#define ENDS(x) } // namespace x
BEGINS(haizei)
class ostream {
public:
ostream &operator<<(int x);
ostream &operator<<(const char* );
};
ostream &ostream::operator<<(int x) {
printf("%d", x);
return *this;
}
ostream &ostream::operator<<(const char *x) {
printf("%s", x);
return *this;
}
ostream cout;
ENDS(haizei)
int main() {
int n = 123, m = 456;
std::cout << n << " " << m; std::cout << std::endl;
haizei::cout << n << " " << m; std::cout << std::endl;
return 0;
}
构造函数,析构函数
构造函数:相关对象的初始化
析构函数:相关对象的销毁
任何对象在产生的时候会运行对象的构造函数,经过使用,然后运行析构函数
构造函数,析构函数:
-
默认构造函数: people a;
编译器自动生成的, 不传任何参数的构造函数 -
有参构造:People a(“hug”); 只有一个参数叫做转换构造
-
拷贝构造:People(const People &a) ,与=不等价
-
~People():析构函数,与构造函数一起使用的,(用来析构动态数组的空间,相关资源的申请和释放)
工程开发中一般不在构造函数中申请大规模空间,会设置一个伪构造函数,和伪析构函数 -
还有一个移动构造,涉及对于右值的理解,在后续补充。。。。。。
-
一旦有了有参构造函数,那么编译器的默认构造函数就没了
#include<iostream>
using namespace std;
class A {
public:
A() {
cout << this << " : constructor" << endl;
}//默认构造函数
A(int x) {
cout << this << " : transform constructor" << endl;
}
A(const A &a){
cout << this << " : copy constructor" << ;
}
const A &operator=(const A &a) const{
cout << this << " : operator=" << endl;
return *this;
}
~A() {
cout << this << " : destructor" << endl;
}
};
int main() {
A a;
A d;
A b = a;//拷贝构造
A c = 3;//这里是转换:需要的是转换构造函数
a = 123;
cout << "end of main" << endl;
return 0;
}
问1:为什么的先构造却后销毁?内存尾号为93的对象先构造,但是最后销毁
内存尾号为94的对象的构造,在逻辑上有可以依赖93的信息,所以94对象的析构有可能依靠93对象的析构,所以在逻辑上讲,先析构94,再析构93
所以一个对象,相对于另一个对象先构造,就晚于后一个对象析构
问2:为什么在析构中,尾号为97的先于end of main 析构?
在代码中有一行为a = 123; 在这一行程序的运行背后,首先利用赋值运算符,然后通过调用A的构造函数,将123转换成A的临时匿名对象,赋值完成析构 临时匿名对象。
问3:在构造函数中A(const A &a) 为什么不用A(A a)?
在该构造函数中,需要把a中的值,拷贝一份,然后赋值给this,但是在拷贝的过程中,生成了另一个临时变量,这里姑且称它为a*, 所以产生了将a*赋值给this,于是出现了一个死循环。所以用引用模式,相当于直接将a赋给this ,而不是a的拷贝。
问4:代码执行到多少行完成的构造,完成的是实际上的构造,还是逻辑上的构造
构造函数结束是逻辑上的构造
进入构造函数时时编译器层面的构造
实际构造行为,经历完初始化列表
初始化列表的顺序与成员声明顺序相同的
new 与 malloc 区别
#include<iostream>
#include <stdlib.h>
using namespace std;
class A {
public:
A() {
cout << "default construtor" << endl;
}
};
int main() {
int n = 10;
cout << "malloc int" << endl;
int *date1 = (int *)malloc(sizeof(int) * n);
cout << "new int" << endl;
int *date2 = new int[n];
cout << "malloc A" << endl;
A *Adate1 = (A *)malloc(sizeof(A) * n);
cout << "new A" << endl;
A *Adate2 = new A[n];
return 0;
}
new 不仅可以开存储区,还会初始化,调用构造函数
new 和 malloc 可以结合起来尽心原地构造,在深拷贝的时候居多
类属性,类方法,成员属性,成员方法
在成员属性,成员对象前加static 就会变成类属性,类方法
类属性:所有对象共有的,不与成员对象绑定,类属性内部不可以使用this指针的
类方法:不单独属于某一对象的方法
const 方法:
为了配合const 对象使用的
普通的成员方法可以改变成员属性值
const方法,保证了不对const对象内部成员属性修改
对象与引用
SomeClass a;
SomeClass &b = a;
引用在定义的时候就需要初始化,引用是一个绑定的过程/
C++中的结构体与类
struct 访问权限默认为public : 黑名单策略,
class 访问权限默认为private : 白名单策略
C++中struct 的底层实现与class相同都是类
问1 :C++都有class了为什么还要保留struct关键字?
兼容C语言,增加C++的推广度。
返回值优化
#include<iostream>
using namespace std;
class A{
public:
A() {
cout << "default constructor" << endl;
}
A(int x) : x(x) {
cout << "transform constructor" << endl;
}
A(const A &a) {
cout << "copy constructor" << endl;
}
int x;
};
A func() {
A temp(69);
return temp;
}
int main() {
A a = func();
return 0;
}
分析该段代码返回值
第一想法:transform constructor + copy constructor
但是输出结果为
只调用了一个transform constructor
若输出temp 和 a的地址
会发现a的地址和temp的地址一模一样
temp 更像是一种引用
在该代码,会把func的返回值拷贝给a,而func返回值是局部变量,也就是说对temp做的所有的操作,都会返回到a的身上,出于系统优化,编译器将temp调用过程中的this指针,全部替换成a的地址,所以在该代码的运行过程中,只进行了一份转换构造。该优化叫做返回值优化。
对象初始化:
- 开辟对象存储区
- 匹配构造函数
- 完成构造
分析下面代码
People func {
People temp_a("temp name");
return temp_a;
}
int main() {
People a = func();
return 0;
}
- 开辟a对象数据区
- 调用函数func
- 开辟对象temp_a数据区
- 调用temp_a 对象的构造函数
- 使用temp_a调用临时匿名变量的拷贝构造函数
- 销毁temp_a对象
- 使用临时匿名变量调用a的拷贝构造函数
- 销毁临时匿名变量
- 销毁a对象
过程中会出现两个临时匿名变量,且没有提供任何价值,所以编译器想出了第一套优方案
一次有参构造(“temp name”), 一次拷贝行为,A a = func()
又因为temp_a为临时变量,编译器又将temp_a作为a的别名,操作temp_a相当于操作a,这种情况下,就没有任何拷贝行为。
(关闭返回值优化的运行结果)
编译器在拷贝的操作上会做优化,意味着,在自己的环境中,究竟调用了多少次拷贝构造是不确定的,意味着在工程设计时,不可以随便改变拷贝构造的语义。
tips:当使用拷贝构造,每一个属性都需要拷贝过去
static_const
- 类变量随类而改变,在声明类变量的时候,需要在类变量前加static
- 类变量需要在全局定义,在类里面只是声明
- 类方法,可不需变量,在类下输出,如下People::say_count()
- const 限定的变量,所使用的方法,只能使const 方法,以防,方法改变变量成员值
- 与类主要信息无关量,可以使用mutable 限定修饰,使他不受const约束
- const限定的方法,若想引用其他方法,其他方法必须为const方法,这里可以利用函数重载
- 参考代码如下
#include<iostream>
using namespace std;
class People {
public:
People() : say_cnt(0) {
People::total_num += 1;
}
static void say_count() {
cout << People::total_num << endl;
}
void say() const{ //const方法只能调用const方法
cout << "hahaha, funny!" << endl;
output();// const output 方法
say_cnt += 1;
}
void output() const {
cout << "const output function" << endl;
}
void output() {
cout << "non-const output function" << endl;
}
~People() {
People::total_num -= 1;
}
private:
mutable int say_cnt;//当前属性可变可不变,不在const之内的管辖
static int total_num;//声明
};
int People::total_num = 0;//定义
int main() {
People hug, xiaohug;
People::say_count();
const People xiaobo;
xiaobo.say();
return 0;
}
delete_default
在构造函数中,在不同的情况添加不同的规则,我们总得记得C++给我们类添加了什么样的行为,这种行为是我们看不到的,反应不到代码上,通常情况下,在大型的工程中为了避免人为的疏忽和潜在规则间可能会触发的潜在bug,C++增加两个关键字**delete**, **default**,
#include<iostream>
using namespace std;
class A {
public :
A() = default;//删除了A的默认构造
//A(const A &) = default;//使用编译器默认规则,帮助避免错误
private:
/*功能需求1:希望某个类的对象是不可以拷贝的*/
A(const A&) = delete;
A &operator=(A &a);
const A &operator=(const A &a) const;
/*将拷贝构造,赋值运算符都放在类里面*/
};
int main() {
A a;
A b;
return 0;
}
重载
函数重载:
如果一个作用域内几个函数名字相同但是参数列表不同,称为函数重载,与返回值没关系!
重载的意义:
- 通过函数名对函数功能进行提示
- 通过函数参数列表对函数的用法进行提示
- 扩展已有的功能
#include<iostream>
using namespace std;
/*int func(int x) {
return 2 * x;
}*/
int func(int x, int y = 2) {
return x * y;
}//与上冲突
double func(double x) {
return x * x;
}
int main() {
cout << func(2) << endl;//func(1)
cout << func(2.3) << endl;//func(2)
cout << func(2, 5) << endl;//func(3);
return 0;
}
运算符重载
只有4种运算符无法被重载
::(域作用符)
.*(成员指针运算符)
.(引用运算符)
?:(三元运算符)
sizeof也无法重载,不过通常不认为他是运算符,不过实际上他是运算符
new 和 delete 也是运算符,也可以进行重载
#include<iostream>
using namespace std;
class Point {
public:
Point();
Point(int x,int y);
Point operator+(const Point &a);
Point &operator+=(int);
private:
friend Point operator+(const Point &a, const Point &b);
friend ostream &operator<<(ostream &out, const Point &a);
int x, y;
};
Point::Point() : Point(0, 0) {}//委托构造函数
Point::Point(int x, int y) : x(x), y(y) {}
Point Point::operator+(const Point &a) {
Point c(x + a.x, y + a.y);
return c;
}
Point &Point::operator+=(int n) {
x += n, y += n;
return *this;
}
Point operator+(const Point &a, const Point &b) {
Point c(a.x + b.x, a.y + b.y);
return c;
}//类外重载
ostream &operator<<(ostream &out, const Point &a) {
out << "(" << a.x << " , " << a. y << ")";
return out;
}
int main() {
Point a(3, 4);
Point b(7, 9);
Point c = a + b;
cout << a << endl;
cout << b << endl;
cout << c << endl;
a += 2;
cout << a << endl;
return 0;
}
类内重载的优先级高于类外重载的优先级
友元
若类外的一个函数,想要访问类内部成员,需要将改函数声明成类的一个友元函数
以做运算符为例
#include<iostream>
using namespace std;
class Date {
public:
Date(int x, int y) : x(x), y(y) {}
friend ostream &operator<<(ostream &out, const Date &d);
private:
int x, y;
};
class A {
public:
A() :d(3, 4) ,c (3, 4){
cout << this << " : constructor" << endl;
}//默认构造函数
A(int x) :d (x, x), c(3, 4) {
cout << this << " : transform constructor" << endl;
}
A(const A &a) : d(a. d), c(a.c){
cout << this << " : copy constructor" << endl;
}
const A &operator=(const A &a) const{
cout << this << " : operator=" << endl;
return *this;
}
~A() {
cout << this << " : destructor" << endl;
}
Date c, d;
};
ostream &operator<<(ostream &out, const Date &d) {
out << d.x << " " << d.y;//无法访问d的内部
return out;
}
HomeWork:实现一个复数类
#include<iostream>
using namespace std;
class Complex {
public:
Complex() : real_part(0.0), imaginary_part(0.0) {}
Complex(double r, double i) : real_part(r), imaginary_part(i) {}
Complex(double x) : real_part(x), imaginary_part(0.0) {}
Complex(int x) : real_part(x) {}
Complex(const Complex &c);
~Complex() = default;
Complex &operator+=(Complex &c);
Complex &operator-=(Complex &c);
Complex &operator*=(Complex &c);
Complex &operator/=(Complex &c);
private:
friend istream &operator>>(istream &in, Complex &c);
friend ostream &operator<<(ostream &out, const Complex &c);
friend Complex &operator+(Complex &a, Complex &b);
friend Complex &operator-(Complex &a, Complex &b);
friend Complex &operator*(Complex &a, Complex &b);
friend Complex &operator/(Complex &a, Complex &b);
double real_part, imaginary_part;
};
Complex::Complex(const Complex &c) {
this->real_part = c.real_part;
this->imaginary_part = c.imaginary_part;
}
Complex &Complex::operator+=(Complex &c) {
this->real_part += c.real_part;
this->imaginary_part += c.imaginary_part;
return *this;
}
Complex &Complex::operator-=(Complex &c) {
this->real_part -= c.real_part;
this->imaginary_part -= c.imaginary_part;
return *this;
}
Complex &Complex::operator*=(Complex &c) {
this->real_part = this->real_part * c.real_part - this->imaginary_part * c.imaginary_part;
this->imaginary_part = this->real_part * c.imaginary_part + this->imaginary_part * c.real_part;
return *this;
}
Complex &Complex::operator/=(Complex &c) {
if (c.real_part == 0 && c.imaginary_part == 0) {
this->real_part = 0;
this->imaginary_part = 0;
cout << "denominator is 0" << endl;
return *this;
};
double a = this->real_part;
double b = this->imaginary_part;
double c1 = c.real_part;
double d1 = c.imaginary_part;
this->real_part = 1.0 * (a * c1 + b * d1) / (c1 * c1 + d1 * d1);
this->imaginary_part = 1.0 * (b * c1 - a * d1) / (c1 * c1 + d1 * d1);
return *this;
}
istream &operator>>(istream &in, Complex &c) {
in >> c.real_part >> c.imaginary_part;
return in;
}
ostream &operator<<(ostream &out, const Complex &c) {
if (c.imaginary_part >= 0) cout << c.real_part << "+" <<c.imaginary_part << "i";
else cout << c.real_part << c.imaginary_part << "i";
return out;
}
Complex &operator+(Complex &a, Complex &b) {
double r = a.real_part + b.real_part;
double i = a.imaginary_part + b.imaginary_part;
Complex *c = new Complex(r, i);
return *c;
}
Complex &operator-(Complex &a, Complex &b) {
double r = a.real_part - b.real_part;
double i = a.imaginary_part - b.imaginary_part;
Complex *c = new Complex(r, i);
return *c;
}
Complex &operator*(Complex &a, Complex &b) {
double r = a.real_part * b.real_part - a.imaginary_part * b.imaginary_part;
double i = a.real_part * b.imaginary_part + a.imaginary_part * b.real_part;
Complex *c = new Complex(r, i);
return *c;
}
Complex &operator/(Complex &c1, Complex &c2) {
double a = c1.real_part;
double b = c1.imaginary_part;
double m = c2.real_part;
double d = c2.imaginary_part;
double real = 1.0 * (a * m + b * d) / (m * m + m * m);
double imaginary_part = 1.0 * (b * m - a * d) / (m * m + d * d);
Complex *c = new Complex(real, imaginary_part);
return *c;
}
int main() {
Complex a, b;
cout << "Init : a : " << a << endl;
cin >> a;
cin >> b;
Complex c = a + b;
cout << " a : " << a << " : b : " << b << endl;
cout << " a + b : " << a + b << endl;
cout << " a - b : " << a - b << endl;
cout << " a * b : " << a * b << endl;
cout << " a / b : " << a / b << endl;
return 0;
}