C++ class
1.C++的默认权限是私有private
#include <iostream>
using namespace std;
class hahaha {
int m_a;//默认权限是私有
};
struct haha
{
int m_a;
};
int main()
{
//在C++中 struct和class的区别是默认权限不同
//struct的默认权限是公共 public
//class的默认权限是私有 private
//共有和私有的区别是 在类外能否访问
hahaha K;
K.m_a = 39;//报错,说明hahaha内m_a是私有对象 在类外不能访问
haha O;
O.m_a = 89;//
return 0;
}
2.成员属性设置私有的好处
可以自己控制权限
对于写的东西可以检测数据有效性
3.成员属性设置为私有的做法
#include <iostream>
using namespace std;
#include <string>
class xixi
{
private:
string m_name;//姓名 只读 不可以访问和改变
int m_age;//年龄 只读
//若要可以改写私有对象,可以在public里完成
public:
void setname(string name)
{
m_name = name;
}//可写
string getname()
{
return m_name;
}//可读
};
int main()
{
xixi haha;
haha.setname("skanha");
haha.getname();
cout<<"姓名"<<haha.getname()<<endl;
return 0;
}
3.在一个类中可以引用另外一个类
4.表明一个对象的作用域,利用::来实现
比如一个函数add是在class A内起作用,则可以这样做
int A::add(int a,int b)
{
}
5.构造函数和析构函数
编译器会自动调用这两个函数,如果没有调用,系统会自动调用
5.1构造函数 初始化 类名(){}
1.构造函数没有返回值也不用写void
2.函数名称和类名相同
3.可以有参数,可能发生重载
4.程序在调用对象的时候会自动调用构造,无需手动调用,且只调用一次
5.1.1构造函数分类及调用
分类:
按参数:有参和无参(默认)
按类型:普通和拷贝
//拷贝函数
类名(const &具体的类 比如xixi a)//把xixi a 拷贝
调用方式:
括号、显示、隐式转换法
a.拷贝构造函数的调用时机
使用一个已经创建完毕的对象来初始化一个新对象
值传递方式给函数参数传值
值方式返回局部对象
#include <iostream>
using namespace std;
#include <string>
class xixi
{
private:
string m_name;//姓名 只读
//若要可以改写私有对象,可以在public里完成
public:
//构造函数
xixi()
{
cout<<"默认构造"<<endl;
}
xixi(int a)
{
cout<<"参数调用构造函数"<<endl;
m_age = a;
}
xixi(const xixi &p)
{
cout<<"拷贝调用构造函数"<<endl;
m_age = p.m_age;
}
//析构函数
~xixi()//对象被销毁前会调用
{
cout<<"析构"<<endl;
}
int m_age;//年龄
};
//使用一个已经创建完毕的对象来初始化一个新对象
void test ()
{
xixi p1(20);
xixi p2(p1);
cout<<"p2 年龄 "<<p2.m_age<<endl;
}
//值传递方式给函数参数传值
void dowork(xixi p)
{
p.m_age = 4;
}
void test2()
{
xixi p;
dowork(p);
cout<<"p的值" <<p.m_age <<endl;
}
//值方式返回局部对象
xixi dowork2()
{
xixi p1;
return p1;
}
void test3()
{
xixi p = dowork2();
}
int main()
{
test();
test2();
test3();
system("pause");
return 0;
}
*深拷贝与浅拷贝
浅拷贝:简单的赋值操作,c++自己创造的拷贝构造函数就是一个浅拷贝
深拷贝:在堆区重新申请空间,进行拷贝操作,如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
#include <iostream>
using namespace std;
class xixi
{
public:
int m_age;
int* m_height;
xixi(int age,int height)
{
m_age = age;
m_height = new int(height);
}
xixi(const xixi& p)
{
cout << "kaobei" << endl;
m_age = p.m_age;
//m_height = int p.m_height; 浅拷贝
m_height = new int (*p.m_height);//深拷贝
}
~xixi()
{
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
}
};
void test01()
{
xixi p1(18, 30);
xixi p2(p1);
cout << "年龄和身高分别为" << p2.m_age << " " << *p2.m_height << endl;
}
int main()
{
test01();
return 0;
}
5.2析构函数 清理 ~类名(){}
1.析构函数没有返回值也不用写void
2.函数名称和类名相同,前面加~
3.不可以有参数,不可以发生重载
4.程序在调用对象的时候会自动调用析构,无需手动调用,且只调用一次
#include <iostream>
using namespace std;
#include <string>
class xixi
{
private:
string m_name;//姓名 只读
int m_age;//年龄 只读
//若要可以改写私有对象,可以在public里完成
public:
void setname(string name)
{
m_name = name;
}
string getname()
{
return m_name;
}
//构造函数
xixi()
{
cout<<"构造"<<endl;
}
//析构函数
~xixi()//对象被销毁前会调用
{
cout<<"析构"<<endl;
}
};
int main()
{
xixi haha;
haha.setname("skanha");
haha.getname();
cout<<"姓名"<<haha.getname()<<endl;
return 0;
}
运行结果如下
初始化列表
class Person
{
public:
int m_a, m_b, m_c;
//传统初始化
/*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)
{
}
};
void test01()
{
Person p(39,39,10);
cout << "m_a=" << p.m_a << endl;
cout << "m_b=" << p.m_b << endl;
cout << "m_c=" << p.m_c << endl;
}
int main()
{
test01();
return 0;
}
类对象作为类成员
当其他类对象作为本类成员,构造时先构造类对象,再构造自身。其他类对象可以看作一个人的器官,而要先有器官,才能组成完整的人,可以类比构造顺序。
而析构的顺序与构造相反。
静态成员
1.静态成员变量
a.类内声明,类外初始化
b.静态成员变量,不属于某个对象,所以对象都共享一份数据
访问方式:通过对象访问,通过类名访问。
c.同时静态成员变量也是有访问权限的,当成员变量为private时,私有权限类外访问不到。
class A
{
public:
static int m_a;
};
int A::m_a = 6;
int main()
{
A p;
A p2;
p2.m_a = 8;
//通过对象访问
cout << p.m_a << endl;
//通过类名进行访问
cout << A::m_a << endl;
return 0;
}
2.静态成员函数
a.所有对象共享同一个函数。
b.静态成员函数只能访问静态成员变量。
c.有访问权限,在private的静态成员函数在类外不能调用。
class haha
{
public:
//静态成员函数
static void func()
{
m_a = 100;//静态成员函数可以访问静态成员变量
//m_b = 200;//会报错,这说明静态成员函数不能访问非静态成员变量,无法区分到底时哪个对象的m_b
cout << "static 调用" << endl;
}
static int m_a;//静态成员变量
//int m_b;
};
void test1()
{
//通过对象访问
haha p;
p.func();
//通过类名访问
haha::func();
}
成员变量和成员函数分开存储
只有非静态的成员变量才属于类的对象上。静态成员变量,非静态成员函数,静态成员函数都不属于类对象上。
空对象占有内存空间为1,每个空对象也应该有独一无二的内存你地址,分配一个字节的空间,是为了区分每个空对象占内存的地址
this指针
this指针指向被调用的成员函数所属的对象
this指针隐含在每一个非静态成员函数内的一种指针,且不需要被定义。
可以解决名称冲突,返回对象
this指针本质是指针常量,指针的指向不可以修改
class A
{
public:
A(int age)
{
//this 指针指向被调用成员函数所属的对象
//当形参和成员变量同名时,可以用this指针区分
this->age = age;
}
A& ADD(A &p)
{
this->age += p.age;
//返回对象自己用*this 返回对象本身
return *this;
}
int age;
};
void test1()
{
//1.解决名称冲突
A p(5);
cout << p.age << endl;
}
//2.返回对象本身用*this
void test2()
{
A p1(34);
A p2(10);
//链式编程思想
p2.ADD(p1).ADD(p1);
cout << p2.age << endl;
}
int main()
{
test1();
test2();
return 0;
}
const修饰成员函数
1.常函数 在函数后加const 比如 void test01()const {}
a.成员函数后加const后称这个函数为常函数
b.常函数内不可以修改成员属性
c.成员属性声明时加关键字mutable后,在常函数中依然可以修改
2.常对象
a.声明对象前加const称该对象为常对象
b.常对象只能调用常函数
友元
目的:让一个函数或者类访问另一个类中私有成员
关键字:friend
三种实现:全局函数做友元 类做友元 成员函数做友元
//全局函数做友元
class Building
{
//告诉编译器,func是Building类的好朋友,在类外可以访问私有成员
friend void func();
};
void func()
{
}
类与成员函数做友元的方法与全局函数做友元类似。
成员函数做友元是要注意表明是什么类下的成员函数(运用::)。
运算符重载
定义:对已有运算重新定义,赋予其另外一种功能。
1.加号运算符重载
两种方法
class Person
{
public:
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;
}
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 = p2.m_b + p2.m_a;
return temp;
}
//实现Person p3 = p1+p2
int main()
{
Person p1;
p1.m_a = 10;
p1.m_b = 20;
Person p2;
p2.m_a = 20;
p2.m_b = 30;
Person p3 = p1+p2;//成员函数方式 Person p3 = p1.operator+(p2);
Person p4 = p1+p2;//全局变量方式 Person p4 = operator+(p1, p2);
cout << p3.m_a << endl;
cout << p4.m_a << endl;
return 0;
}
2.左移运算符重载(<<)
主要是cout函数。
3.其余的运算符重载写法类似加号运算符重载,可以依葫芦画瓢。
继承
继承是面向对象三大特性之一
1.好处:减少重复代码
2.语法:class 子类 : 继承方式 父类
子类也被称为派生类,包括基类继承过来和自己特有的成员
父类也被称为基类
3.继承方式有三种:公共继承 保护继承 私有继承
父类中的私有内容无论是哪个继承方式都无法访问,而三种继承的区别是,公共继承不会改变从父类中继承过来的成员变量的属性(public仍然是public,protected仍然是protected),而保护继承公共权限和保护权限都变为保护权限,私有继承公共权限和保护权限都变为私有权限。
4.继承中的对象模型
class Base
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son :public Base
{
public:
int m_d;
};
void test1()
{
//16
//父类中所有静态成员属性都会被子类继承下来,包括私有成员属性
//父类中私有成员属性,被编译器隐藏,访问不到,但是确实被继承
cout << "size of Son = " << sizeof(Son) << endl;
}
int main()
{
test1();
return 0;
}
5.继承中的构造和析构顺序
先调用父类构造,再调用子类构造,析构顺序与构造顺序相反。
6.继承同名成员处理方式
a.调用自己的,直接 对象.成员名
b.调用父类,对象.作用域(即父类class名称)::成员名
c.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类所有同名成员函数,如果要访问父类的成员,需要加作用域。
7.继承同名静态成员处理方式
与非静态同名成员类似。
a.访问子类,直接访问
b.访问父类,加作用域
c.同名静态成员处理和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)。
8.多继承语法
语法
class 子类: 继承方式 父类1,继承方式 父类2,...
多继承中当父类中出现同名成员,子类使用时要加作用域。
9.菱形继承
概念:两个son继承同一个father,grandson继承两个son。
问题:子类继承两份相同的数据,导致资源浪费。
解决办法:利用虚继承(可以上csdn搜索了解)可以解决菱形继承问题。
多态
好处:
a.组织结构清晰。
b.可读性强。
c.利于前后期的扩展和维护性高。
1.动态多态
满足条件:有继承关系,子类重写父类的虚函数。
使用:父类的指针或者引用指向子类对象。(Base *base = new Son)
(重写概念:函数返回值类型 函数名 参数列表完全一致称为重写)
2.纯虚函数和抽象类
a.纯虚函数语法
virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也被称为抽象类
b.抽象类特点:
无法实例化对象,子类必须重写抽象类的纯虚函数,否则也属于抽象类
class Base
{
public:
//纯虚函数
virtual void func() = 0;
int m_a;
int m_b;
};
class Son:public Base
{
public:
};
void test1()
{
Son s;//会报错,报错如上面的图片
s.func();
}
若想不报错,则需重写纯虚函数
class Base
{
public:
//纯虚函数
virtual void func() = 0;
int m_a;
int m_b;
};
class Son:public Base
{
public:
virtual void func() {};//重写纯虚函数
};
void test1()
{
Son s;
s.func();//不会报错
}
3.虚析构和纯虚析构
问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决:将父类中的析构函数改为虚析构和纯虚析构。
共性:
a.解决父类指针释放子类对象。
b.都需要具体函数实现。
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法
virtual ~类名(){}
纯虚析构语法
virtual ~类名() = 0;//类内声明
类名::类名(){}//类外实现
重写抽象类的纯虚函数,否则也属于抽象类
[外链图片转存中…(img-nZqPZLyV-1708327608132)]
class Base
{
public:
//纯虚函数
virtual void func() = 0;
int m_a;
int m_b;
};
class Son:public Base
{
public:
};
void test1()
{
Son s;//会报错,报错如上面的图片
s.func();
}
若想不报错,则需重写纯虚函数
class Base
{
public:
//纯虚函数
virtual void func() = 0;
int m_a;
int m_b;
};
class Son:public Base
{
public:
virtual void func() {};//重写纯虚函数
};
void test1()
{
Son s;
s.func();//不会报错
}
3.虚析构和纯虚析构
问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决:将父类中的析构函数改为虚析构和纯虚析构。
共性:
a.解决父类指针释放子类对象。
b.都需要具体函数实现。
区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象。
虚析构语法
virtual ~类名(){}
纯虚析构语法
virtual ~类名() = 0;//类内声明
类名::类名(){}//类外实现
如果子类中没有堆区数据,可以不写虚析构和纯虚析构。