- 对于有Java基础的思想不过多记录,仅看语法
创建和封装对象
-
类似Java中的set操作,可以写一个赋值操作用于给实例化对象赋属性值
-
三种权限:public , protect ,private
Class和Struct区别
- Struct默认为public权限
- class默认为private权限,除非public:成员
私有成员属性
- 可以自己控制读写权限
- 先给每个属性加上private:
3. 对每个属性进行set和get方法,办法:给不期望被写的属性值,不写set方法即可
4.
- 对于写权限,可以检测数据有效性
分开放置类的声明和方法
以计算
该类的.h文件
- 写在头文件位置,命名:point.h
- 仅需要写出属性和函数声明
#pragma once //确保同一个头文件在同一个编译单元中只被包含一次
#include<iostream>
using namespace std;
class point {
//只保留函数和成员变量的声明
public:
void setx(int x);
int getx();
void sety(int y);
int gety();
private:
int m_x = 10;
int m_y = 20;
};
设计该类的方法
- 专门用一个文件,放置类的方法:point.cpp
- 每个方法都声明是在point的作用域下
#include "point.h" //自定义的头文件,使用" "而不是< >
//方法实现部分,用point::说明,在point作用域
//才能对point的属性值m_x和m_y进行赋值
void point::setx(int x) {
m_x = x;
}
int point::getx() {
return m_x;
}
void point::sety(int y) {
m_y = y;
}
int point::gety() {
return m_y;
}
用一个主函数调用该类
- 在circle类:仅写了个方法,其中调用point的类及其方法。
- 在main类中,也可以用到point类及其方法
#include<iostream>
using namespace std;
#include "point.h" //引入自定义头文件
class circle {
public:
void showcenter(point p) {
cout << p.getx() << "---" << p.gety() << endl; //虽然
}
};
int main() {
circle c1;
point p1;
p1.setx(10);
p1.sety(10);
c1.showcenter(p1);
system("pause");
}
拷贝构造函数
声明和使用
通过构造函数,将某个对象的属性完全拷贝到一个新对象。
#include<iostream>
using namespace std;
class person
{
public:
person() {
cout << "无参构造" << endl;
}
//拷贝构造函数
//参数:引用&获取拷贝的对象。用const,保证不改变原对象
//使用时传入一个person类即可,将参数位置的对象属性,拷贝到调用该方法的对象中
person(const person &p) {
age = p.age;
cout << "拷贝构造,年龄:"<<age << endl;
}
void setage(int age) {
this->age = age;
}
~person() {};
private:
int age;
};
int main()
{
person p1; //默认构造函数不加(),如果有括号,会认为是函数声明
p1.setage(20);
//拷贝构造函数
//person p2(p1); //拷贝构造,年龄:20
person p2 = person (p1); //这个也能实现拷贝构造 (括号里是被拷贝的对象)
// person p2 = p1; //也是一种拷贝构造写法
system("pause");
}
- 参数:引用&获取拷贝的对象。 用const为了保证不改变原对象
- 使用时传入一个person类即可,将参数位置的对象属性,拷贝到调用该方法的对象中(括号里是被拷贝的对象,返回值是拷贝得到的对象)
使用场景
- 克隆一个已有的对象:
- 值传递的形式,给函数传参
返回一个局部对象时
默认规则
- 即使不写拷贝函数,默认也会有一个拷贝函数,进行值拷贝
- 如果自己写了有参构造,并且没写无参构造,无参实例化一个变量,编译器会报错:没有构造函数。因为当你写了有参构造,编译器不再提供无参,加上你没写无参,那就是没有构造参数。
深浅拷贝
https://www.bilibili.com/video/BV1et411b73Z/?p=110
-
浅拷贝:简单赋值
-
深拷贝:堆中重新申请空间进行拷贝
-
浅拷贝:
进行如下:将p1拷贝至p2。会报错
因为:二者通过指针,同时使用堆区的内存。但是上面两行代码运行结束时。p2的析构函数先释放了堆区,等到p1想释放的时候,已经没有可以释放的,重复释放会出问题。
解决:编译器自带的拷贝是浅拷贝,不好用。自己写一个深拷贝
初始化列表
构造函数():属性1(值1),属性2(值2),属性3(值3) ... {}
或者写成
构造函数(int a,int b,int c):属性1(a),属性2(b),属性3(c)...{}
成员类和自己哪个先构造
- 先构造内部成员类对象,再构造外层对象。(先有胳膊腿,再有自身)
- 先析构外层对象,再析构内部成员对象。
静态变量
成员变量
- 可以视为:专属这个类的全局变量(静态变量也有private权限)
- 注意声明时候,不带static,并且Person作用域形式
p2对这个值进行修改,输出m_A确实变了
访问方式:
成员函数
-
因为无法确定调用静态函数func时候,所用的m_B是哪个对象的属性,所以静态(公用)函数只能处理静态(公用)变量
-
同时,静态成员函数也有权限,例如:private权限就不能被类外调用
调用方式:
成员.func()
或者类名::func()
成员变量和成员函数分开存储
- 一般把成员变量和成员函数分开存储
- 静态的变量/函数,都不属于类的对象上
this指针
-
指向被调用的成员函数所属的对象(是固定,不可修改的)
-
隐含在每个非静态成员函数内
-
用途1:形参和成员变量重名时,this所指是成员变量
-
用途2:希望某个函数的返回值,是对象本身,就return *this
注意:引用是地址传递。指针返回的地址通过引用地址传给自己,如果不用引用是传给克隆出来的对象,对象自己就不能链式递增了,而是不停的克隆本体,如下图所示:
-
返回值如果不加引用,会返回一个新的对象
-
加上引用,就是对当前对象进行类似覆盖的拷贝,可以继续使用原对象
空指针访问成员
通常增加一个判断增强鲁棒性:
const修饰
常函数
常对象
属性值不能改
友元
让其他特殊函数/类,访问本类的private权限内容,关键字是friend
全局函数友元
类友元
这样,GoodGay就可以防伪Building中的私有成员:BedRoom
成员函数做友元
运算符重载
对已有运算符进行重新定义,例如把两个自定义类型相加。
- 可以通过成员函数重载
- 也可以通过全局函数重载
- 因为函数可以重载,所以也可以写operator(Person &p1 , int num){…},实现给自定义对象内部增加num数字
- 不要滥用(防御性代码)
左移运算符重载(cout << )
希望能输出p内部的属性值,类似Java的show
- 通常使用全局函数重载
- 蓝色确保<<左边是cout,右边是Person类型
- 因为我们一般让类内属性值为private,所以需要把左移运算符变成友元
递增运算符重载
对自定义类型内部的数据进行递增
- 分为 前置 和 后置
- 前置递增写法
- 后置递增写法(官方设定用 int 区分)
- 用下面代码,检查两 种递增
赋值 运算符重载
类似于拷贝一个对象,但是现在是将一个对象的值覆盖。
- 直接赋值是使用浅拷贝,会出现重复释放的问题,所以要申请新空间进行深拷贝
-
所以需要修改为Person &类型
-
使用方法:
关系运算符重载
函数调用运算符重载( 括号() )
- 重载之后的方式非常像函数调用,因此称为仿函数
小知识点
-
空对象也占1字节内存,目的是为了区分空对象占内存的位置
-
箭头运算符(->)用于结构体/类指针变量访问成员。A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针
-
cout是输出流ostream的对象