C++基础-类与对象(1)
C++类与对象(1)
类的设计:可以把属性和行为放在不同的权限下
struct和class区别在于某人的访问权限不同
- struct:默认共有
- class:默认私有
对象的初始化和清理
如果我们不写,系统会自己给我没写
- 构造函数的语法 类名(){}
没有返回值,也不写void
函数名和类型相同
可以有参,也可以无参
在调用对象会自动调用函数,无需手动调用,只调用一次
- 析构函数语法 ~类名(){}
同上,不过是无参(不可以重载),销毁时自动调用函数,只调用一次
构造函数的分类与调用
分类
- 有参和无参(默认)
- 普通和拷贝
拷贝函数
函数名(const 函数名 &p(对象)){
age = p.age;
}
#include<iostream> //构造函数和析构函数
using namespace std;
class person{
public:
int age;
public:
//构造函数
//普通
person(){
cout << "Person无参构造函数的调用" << endl;
}
person(int a){
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝
person(const person &p){
age = p.age;
cout << "Person拷贝构造函数的调用" << endl;
}
//析构函数
~person(){
cout << "Person析构函数的调用" << endl;
}
};
void test01(){
//调用
//1.括号法
//person p1;
//person p2(10);
//person p3(p2);
//注:调用默认构造函数时,不要加()(系统会以为是一个函数的声明)
//cout << "p2的年龄:" << p2.age << endl;
//cout << "p3的年龄:" << p3.age << endl;
//2.显示法
person p4;
person p5 = person(10);//右值:匿名对象,当前行结束,系统收回匿名对象
person p6 = person(p5);
//注:不要利用拷贝函数初始化匿名对象
//3.隐式转换法
person p7 = 10;//相当于person p5 = person(10);
person p8 = p7;
}
int main(){
test01();
return 0;
}*/
拷贝函构造函数的调用时机
c++中调用拷贝函数一般三种请况
- 使用一个已经创建完毕的对象来初始化一个对象
- 值传递的方法给函数参数传值
- 以值方式返回局部对象
#include<iostream> //拷贝时机
using namespace std;
class Person{
private:
int m_Age;
public:
Person(){
cout << "person的默认无参构造函数调用" << endl;
}
Person(int age){
cout << "Person有参构造函数调用" << endl;
m_Age = age;
}
Person(const Person &p){
cout << "Person的拷贝调用" << endl;
m_Age =p.m_Age;
}
~Person(){
cout << "person的析构函数调用" << endl;
}
};
void test01(){
Person p1 = Person(20);
Person p2(p1);
}
void dowork(Person p){ }
void test02(){
Person p;
dowork(p);
}
Person dowork2(){
Person p1;
cout << (int*)&p1 << endl; //输出地址
return p1;
}
void test03(){
Person p = dowork2();
cout << (int*)&p << endl;
}
int main(){
test01();
cout << endl;
test02();
cout << endl;
test03();
return 0;
}
构造函数的调用规则
默认情况下,C++会至少给一个类添加3个函数
- 默认构造函数无参,函数体为空
- 默认析构函数无参,函数体为空
- 默认拷贝构造函数,对属性进行拷贝(所有的属性都进行赋值操作)
规则如下:
- 如果用户定义了有参构造函数,C++不在提供默认无参构造,但会提供默认的拷贝
- 如果用户定义了拷贝函数,C++不h会提供其他构造函数
注意:若只定义了拷贝(只有参同理),则
Person p1;
//和
Person p1(20);
//均是错误的(因为系统不会提供)
深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作(如果对其进行释放,则堆区的内存会重复释放,出现错误)
深拷贝:在堆区重新申请空间,进行拷贝操作
m_height = new int(*P.m_height); //new返回的值是地址
private:
int * m_height; //地址(指针)类型
初始化列表
作用:用来初始化属性
语法:
构造函数():属性1(值1),属性2(值2)...{}
来个浅例吧
#include <iostream>
using namespace std;
class Person{
public:
//传统初始化
// Person(int a,int b,int c){
// m_A = a;
// m_B = b;
// m_C = c;
// }
//初始化列表
Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){} //参数a,b,c可以自由的改变所赋的值
int m_A;
int m_B;
int m_C;
};
void test01(){
Person p(30,20,10);
//Person p;
cout << "m_A:" << p.m_A << endl;
cout << "m_B:" << p.m_B << endl;
cout << "m_C:" << p.m_C << endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果是30 20 10
类对象作为类的成员
C++类中的成员可以是另一个类的对象,我们成该成员为对象成员
例如:
class A{};
class B{
A a;
}
//敲黑板(好老的梗...)
//先构造A的对象(即先构造其他类的对象),再构造B的对象
//析构的顺序是相反的,先析构本类,再析构其他类
代码的简单例子
#include <iostream>
using namespace std;
#include<cstring> //要用字符串呢
class Phone{ //类一
public:
//品牌名字
string m_Pname;
Phone(string name){
m_Pname = name;
}
};
class Person{ //类二
public:
//姓名
string m_Name;
//手机
Phone m_Phone; //类一作为类二的成员
Person(string Name,string Pname):m_Name(Name),m_Phone(Pname){}
//相当于Phone m_Phone = Pname = Phone(Pname)(隐式转化)
};
void test01(){
Person p("张三","华为");
cout << p.m_Phone.m_Pname;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果:华为
静态成员函数
静态成员变量就是加上const
静态成员分成:
- 静态成员变量
- 所有对象共享一份数据
- 再编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量(函数体内无法区分普通变量是那个对象的成员)
- 也是有访问权限的,private下在类外就访问不到
#include <iostream>
using namespace std;
class Person{
public:
//静态成员函数
void static func(){
m_a = 100;//静态成员函数访问静态成员变量
cout << "func的调用" << m_a << endl;
}
static int m_a;//类内声明类外初始化
};
int Person::m_a = 0;
//两种访问方式
void test01(){
//通过对象访问
Person p;
p.func();
//通过类名访问
Person::func();
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果
对象模型和this指针
成员变量和成员函数分开存储
-
只有非静态成员变量才属于类的对象上面
-
空对象占用一个字节
C++编译器会给每个空对象分配一个字节的空间(独一无二的内存地址),防止区分空对象占内存的位置
#include <iostream>
using namespace std;
class Person1{};
class Person2{
int m_a;//非静态成员变量,属于类的对象上
static int m_b;//静态成员变量,不属于类的对象上
void test01(){}//非静态成员函数,属于类的对象上
static void test02(){}//静态成员函数,不属于类的对象上
};
int Person2::m_b = 0;
void test01(){//空对象所占用的内存
Person1 p1;
cout << "p1 sizeof of p is " << sizeof(p1) << endl;
}
void test02(){//非空对象占用的内存
Person2 p2;
cout << "p2 sizeof of p is " << sizeof(p2) << endl;
}
int main(int argc, char** argv) {
test01();
test02();
return 0;
}
this指针
引子:在上面我们知道,非静态的成员函数只会生成一份函数实例,也是是说多个同类的对象会公用一块代码(一个函数),那么:这一块代码是如何区分是那个对象调用自己呢?
通过this指针来解决上面的问题,this指针指向被调用的成员函数所属的对象(eg:p1调用就指向p1...)
- this指针是隐含在每一个非静态成员函数内的一种指针,不用定义,直接使用
用途
- 当形参和成员变量重名时,可用this来区分
- 在类的非静态成员函数返回对象本身(return *this;)
#include <iostream>
using namespace std;
class Person1{//名称冲突
public:
Person1(int age,int age1){
this->age = age;
age1 = age1;
}
int age;
int age1;
Person1 & Add(Person1 &p){
this->age += p.age;
//this是一个指向p3的指针,*this就是对象p3的本体
return *this;
}
};
void test01(){
Person1 p1(18,18);
cout << "p1的年龄是" << p1.age << endl;
cout << "p1的年龄是" << p1.age1 << endl;
}
void test02(){//把p2的年龄加到p3上
Person1 p2(10,10);
Person1 p3(10,10);
p3.Add(p2).Add(p2);//链式编程思想
cout << "p3的年龄是" << p3.age << endl;
}
int main(int argc, char** argv) {
test01();
test02();
return 0;
}
结果
空指针访问成员函数
C++中允许空指针调用成员函数的,但是也要注意有没有用到this地址
如果用到this指针,则需要加以判断确保代码的健壮性
if(this == NULL) return;
看个小例子吧
#include <iostream>
using namespace std;
class Person{
public:
void show(){
cout << "show的调用" << endl;
}
int m_age;
void get(){
if(this == NULL) return;
cout << "age=" << m_age << endl;
//默认this->m_age
}
};
void test01(){
Person * p = NULL;
//空指针可以访问成员
p->show();
p->get();
}
int main(int argc, char** argv) {
test01();
return 0;
}
const修饰成员函数
常函数
- 不可以修改成员属性
- 成员属性声明时+mutable关键字,在常函数中就可以修改了
class Person{
public:
//this本质 指针常量 Person * const this 指向不可以改变
//在成员函数后面+const <=>const Person * const this,让指针指向的值不可以改变
void show() const{
m_a =100;//所以会报错哦
//其实是this->m_a = 100;
}
int m_a;
};
常对象
- 常对象只能调用常函数
const Person p;
p.show();
友元
在程序里,有些私有的属性想让类外的特殊的一些函数或者类进行调用,就需要友元技术
作用(目的):让一个函数或是类访问另一个类中的私有成员
友元关键字:friend(友元:不是类的成员,不受访问限制)
友元的三种实现
- 全局函数友元
- 类做友元
- 成员函数做友元
全局函数做友元
#include <iostream>
using namespace std;
#include<cstring>
class Building{
//goodFriend是Building类的好朋友,可以访问啦
friend void goodFriend(Building &building);
public:
Building(){
SittingRoom = "客厅";
BedRoom = "卧室";
}
public:
string SittingRoom;//客厅
private:
string BedRoom;//卧室
};
//全局函数
void goodFriend(Building &building){
cout << "友元全局函数 正在访问:" << building.SittingRoom << endl;
cout << "友元全局函数 正在访问:" << building.BedRoom << endl;
}
void test01(){
Building building;
goodFriend(building);
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果
类做友元
大致流程:
- 先创建GoodFriend类的对象GF
- 调用本类下的构造函数:创建一个Building(同时调用Building的构造函数)
- 访问visit()函数,就可以访问building下的成员啦
#include <iostream>
using namespace std;
#include<cstring>
class Building{
//GoodFriend类是Building类的好朋友
friend class GoodFriend;
...//和上面一样
};
class GoodFriend{
public:
GoodFriend(){
//创建对象
building = new Building;
}
void visit(){//参观函数 访问Building中的属性
cout << "友元正在访问:" << building->SittingRoom << endl;
cout << "友元正在访问:" << building->BedRoom << endl;
}
Building * building;
};
void test01(){
GoodFriend GF;
GF.visit();
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果
成员函数做友元
流程与上面的几乎一样
#include <iostream>
using namespace std;
#include<cstring>
class Building;//防止在未创建BUilding类是报错
class GoodFriend{
public:
Building * building;
GoodFriend();
void visit();//参观函数 访问Building中的私有成员
};
class Building{
//visit()做为BUilding类的好朋友
friend void GoodFriend::visit();
public:
string SittingRoom;//客厅
private:
string BedRoom;//卧室
public:
Building();
};
//类外声明
Building::Building(){
SittingRoom = "客厅";
BedRoom = "卧室";
}
GoodFriend::GoodFriend(){
building = new Building;
}
void GoodFriend::visit(){//参观函数 访问Building中的私有成员
cout << "友元正在访问:" << building->SittingRoom << endl;
cout << "友元正在访问:" << building->BedRoom << endl;
}
void test01(){ //测试函数
GoodFriend GF;
GF.visit();
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果:
运算符的重载
概念:对已有运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
对于内置的数据类型,系统知道如何进行运算
加号运算符重载(其他同理)
- 成员函数重载+号
本质:Person p3 = p1.operator+(p2);
#include <iostream>
using namespace std;
class Person{
public:
int m_A;
int m_B;
/*======================================================*/
Person operator+(Person &p){
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
/*======================================================*/
};
void test01(){
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
cout << p3.m_A <<endl;
cout << p3.m_B <<endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果是两个20(相加成功)
- 全局函数重载+号
本质:Person p3 = operator+(p1,p2);
#include <iostream>
using namespace std;
class Person{
public:
int m_A;
int m_B;
};
/*======================================================*/
Person operator+(Person &p1,Person &p2){
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
/*======================================================*/
int main(int argc, char** argv) { //函数和上面的一样
test01();
return 0;
}
结果也是两个20
注意:- 运算符的重载也可以发生函数重载(名字相同,参数不同)
- 不可以改变内置运算符
左移运算符的重载(<<)
只能利用全局函数重载左移运算符
作用:输出自定义的数据类型
本质:operator<<(cout,p) => cout << p
#include <iostream>
using namespace std;
class Person{
public:
int m_A;
int m_B;
};
/*======================================================*/
ostream & operator<<(ostream &out,Person &p){
out << "m_A:" << p.m_A << '\t' << "m_B:" << p.m_B << endl;
return out;
}
/*======================================================*/
void test01(){
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p << endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果:m_A:10 m_B:10
若类的成员变成私有:用友元
class Person{
friend ostream & operator<<(ostream &out,Person &p);
public:
int m_A;
int m_B;
};
递增运算符重载(++)
前置返回引用,后置返回值
- 前置递增
#include <iostream>
using namespace std;
class MyInteger{//自定义的整型
friend ostream & operator<<(ostream &out,MyInteger &p);
public:
MyInteger(){
m_Num = 0;
}
/*======================================================*/
MyInteger & operator++(){//返回引用是为了一直对一个数据操作
m_Num++;
return *this;
}
/*======================================================*/
private:
int m_Num;
};
ostream & operator<<(ostream &out,MyInteger &p){
out << p.m_Num;
return out;
}
void test01(){
MyInteger myint;
cout << "myint:" << ++(++myint) << endl;
cout << "myint:" << myint << endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果1:myint:2(换行)myint:2
- 后置递增
#include <iostream>
using namespace std;
class MyInteger{//自定义的整型
friend ostream & operator<<(ostream &out,const MyInteger &p);
public:
MyInteger(){
m_Num = 0;
}
/*======================================================*/
MyInteger operator++(int){//int 代表占位参数,可以用于区分前置和后置递增
//先记录
MyInteger temp = *this;
//后递增
m_Num++;
//再返回
return temp;
}
/*======================================================*/
private:
int m_Num;
};
ostream & operator<<(ostream &out, const MyInteger &p){//这里加了const,否则在test02()的输出会有问题
out << p.m_Num;
return out;
}
void test02(){
MyInteger myint;
cout << "myint:" << myint++ << endl;
cout << "myint:" << myint << endl;
}
int main(int argc, char** argv) {
test02();
return 0;
}
结果2:myint:0(换行)myint:1
赋值运算符重载
补充构造函数调用规则,一个类至少4个函数
- 第四个:赋值运算符operator=,对属性进行拷贝
p2 = p1的问题:堆区重复释放,和浅拷贝的问题是一样的
#include <iostream>
using namespace std;
class Person{
public:
int *m_age;//开辟到堆区
Person(int age){
m_age = new int(age);
}
~Person(){
if(m_age != NULL){
delete m_age;
m_age = NULL;
}
}
/*======================================================*/
Person & operator=(Person &p){//和深拷贝几乎是一样的,返回值是引用是要满足连等
//先判断左值是否有属性在堆区,如果有,先释放干净,再深拷贝
if(m_age != NULL){
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);
//返回对象本身
return *this;
}
};
/*======================================================*/
void test01(){
Person p1(10);
Person p2(20);
Person p3(30);
p3 = p2 = p1;//赋值操作
cout << "p1的年龄是:" << *p1.m_age << endl;
cout << "p2的年龄是:" << *p2.m_age << endl;
cout << "p3的年龄是:" << *p3.m_age << endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果:p1,p2,p3都是10
关系运算符的重载(>/<...)
- ==的重载(!=同理)
#include <iostream>
using namespace std;
class Person{
public:
string m_name;
int m_age;
Person(string name,int age){
m_name = name;
m_age = age;
}
~Person(){}
/*======================================================*/
bool operator==(Person &p){
if(this->m_name == p.m_name&&this->m_age == p.m_age){
return true;
}else{
return false;
}
}
/*======================================================*/
};
void test01(){
Person p1("Tom",18);
Person p2("Tom",18);
if(p1 == p2) cout << "p1和p2相等" << endl;
else cout << "p1和p2不相等" << endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果:p1和p2相等
函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此也称谓仿函数
- 仿函数没有固定的写法,很灵活
#include <iostream>//写了两个...
using namespace std;
#include<cstring>
class Myprint{//打印类
public:
/*======================================================*/
void operator()(string test){
cout << test <<endl;
}
/*======================================================*/
};
class MyAdd{//加法类
public:
/*======================================================*/
int operator()(int a,int b){
return a+b;
}
/*======================================================*/
};
void test01(){
Myprint myPrint;
myPrint("hello word");
MyAdd myAdd;
int c = myAdd(1,2);
cout << c << endl;
//匿名对象
cout << MyAdd()(1,1) << endl;
}
int main(int argc, char** argv) {
test01();
return 0;
}
结果:hello word(换行)3(换行)2