c++中的类详解
知识点1【封装】
- 把变量和函数合成一个整体,封装在一个类中
- 对变量和函数进行访问控制(公有、私有、保护)
访问属性 | 属性 | 对象内部 | 对象外部 |
---|---|---|---|
public | 公有 | 可访问 | 可访问 |
protected | 保护 | 可访问 | 不可访问 |
private | 私有 | 可访问 | 不可访问 |
在没有涉及继承与派生时,protected与private 是同等级的,外部不允许访问。
知识点2【类的初识】
#include <iostream>
#include<cstring>
using namespace std;
class Person{
private:
char name[20];//定义类的时候,不要给成员初始化
int age;
public:
void setNmae(char* n){
strcpy(name,n);
}
char* getName(){
return name;
}
void setAge(int a){
age = a;
}
int getAge(){
return age;
}
};
int main(int argc, char *argv[])
{
Person person;
char name[20] = "tony";
person.setNmae(name);
person.setAge(15);
cout << person.getName() << person.getAge() << endl;
return 0;
}
实例:设计一个点和圆的类
#include <iostream>
using namespace std;
class Point{//设计一个点的类
private:
int m_x;
int m_y;
public:
void setX(int x){
m_x = x;
}
int getX(){
return m_x;
}
void setY(int y){
m_y = y;
}
int getY(){
return m_y;
}
};
class Circle{//设计一个圆的类
private:
Point point;
int r;
public:
void setPoint(int x,int y){
point.setX(x);
point.setY(y);
}
void setR(int x){
r = x;
}
void isPointOnCircle(Point &ob){
//判断点与圆的关系
int distance = (point.getX()-ob.getX()) *(point.getX()-ob.getX()) + (point.getY() - ob.getY()) * (point.getY() - ob.getY());
if(distance > r*r){
cout << "在圆外" << endl;
}else if(distance == r*r){
cout << "在圆上" << endl;
}else{
cout << "在圆内" << endl;
}
}
};
int main(int argc, char *argv[])
{
//实例化一个点对象
Point p1;
p1.setX(5);
p1.setY(5);
//实例化一个圆对象
Circle circle;
circle.setR(3);
circle.setPoint(3,3);
//判断点point与圆的关系
circle.isPointOnCircle(p1);
return 0;
}
知识点3【类的空间大小】
成员数据占类的空间大小,成员函数不占类空间的大小。
using namespace std;
class Data
{
private:
//成员数据 占类的空间大小
int num;//4B
public:
//成员函数 不占类的空间大小
void setNum(int data)
{
num = data;
}
int getNum(void)
{
return num;
}
};
void test01()
{
printf("%d\n",sizeof(Data));//4B
}
知识点4【在类内声明 类外定义 成员函数】
class data{
private:
int num;
public:
void setNum(int x);
int getNum();
};
void Data::setNum(int x){
num = x;
}
int Data::getNum(){
return num;
}
void test01(){
Data data;
data.setNum(100);
cout << data.getNum() << endl;
}
int main(int argc, char *argv[]){
test01();
return 0;
}
知识点5【类的定义在头文件 成员函数 在cpp文件中实现】
data1.h
#ifndef DATA1_H
#define DATA1_H
class Data1
{
private:
int num;
public:
void setNum(int x);
int getNum();
};
#endif // DATA1_H
data1.cpp
#include "data1.h"
void Data1::setNum(int x){
num = x;
}
int Data1::getNum(){
return num;
}
main.cpp
#include <iostream>
#include"data1.h"
using namespace std;
void test01(){
Data1 data;
data.setNum(1000);
cout << data.getNum() << endl;
}
int main(int argc, char *argv[]){
test01();
return 0;
}
知识点6【构造和析构函数的概述】
构造函数和析构函数都会被编译器自动调用,构造函数完成对象的初始化动作,析构函数在对象结束的时候完成清理工作。
注意:初始化和清理工作是编译器强制我们做的事情,即使程序并没有提供初始化操作和清理操作,编译器也会给程序增加默认的操作,但是默认操作并不会做任何事。
构造函数:实例化对象的时候系统自动调用
析构函数:对象释放的时候系统自动调用
知识点7【构造和析构函数的定义】
构造函数的语法:
构造函数名与类名相同,没有返回类型,连void都不可以,但可以有参数,可以重载。
析构函数的语法:
析构函数的名字是在类名前面加“ ~ ”组成,没有返回类型,连void都不可以,不能有参数,不能重载。
案例:
#include <iostream>
using namespace std;
class Data
{
public:
Data() {
cout << "无参构造函数" << endl;
}
Data(int num){
num = 100;
cout << "有参构造函数" << endl;
}
~Data(){
cout << "析构函数" << endl;
}
};
int main(int argc, char *argv[])
{
cout << "-----------01----------" << endl;
Data ob;
cout << "-----------02----------" << endl;
return 0;
}
知识点8【构造函数的分类以及调用】
1、构造函数的分类
按参数类型分类: 分为无参构造函数和有参构造函数
按类型分类:普通构造函数和拷贝构造函数(复制构造函数)
2、构造函数的调用
#include <iostream>
using namespace std;
class Data
{
private:
int num;
public:
Data() {
num = 0;
cout << "无参构造函数" << endl;
}
Data(int num1){
num = num1;
cout << "有参构造函数" << num << endl;
}
~Data(){
cout << "析构函数" << num << endl;
}
};
void test01(){
//调用无参或默认构造函数
Data ob;
//调用有参构造函数(显式调用)
Data ob1 = Data(100);
//调用有参构造函数(隐式调用)
Data ob2(1000);
//隐式转换的方式 调用有参构造(针对于 只有一个数据成员)(尽量别用)
Data ob3 = 10000;//转化成Data ob5(30)
//匿名对象(当前语句结束 匿名对象立即释放)
Data(40);
cout << "---------------------" << endl;
//千万不要 用以下方式调用 无参构造
//Data ob4();//不会被认为时实例化对象,会被看作函数ob4的声明
}
int main(int argc, char *argv[])
{
cout << "-----------01----------" << endl;
test01();
cout << "-----------02----------" << endl;
return 0;
}
注意:在同一作用域,构造和析构的顺序相反(类似于栈,先进后出)
3、拷贝构造函数(系统提供一个拷贝构造函数 赋值操作)
#include <iostream>
using namespace std;
class Data
{
public:
int num;
public:
Data() {
num = 0;
cout << "无参构造函数" << endl;
}
Data(int num1){
num = num1;
cout << "有参构造函数" << num << endl;
}
//拷贝构造函数
Data(const Data &ob){//Data &ob = ob5;
num = ob.num;
cout << "拷贝构造函数" <<endl;
}
~Data(){
cout << "析构函数" << num << endl;
}
};
void test01(){
//调用有参构造函数(隐式调用)
Data ob3 = Data(100);
cout<<"ob3.num = "<<ob3.num<<endl;
//显式调用拷贝构造函数
Data ob5 = Data(ob3);
cout<<"ob5.num = "<<ob5.num<<endl;
//隐式调用拷贝构造函数
Data ob6(ob3);
cout<<"ob6.num = "<<ob6.num<<endl;
//隐式转换调用拷贝构造函数
Data ob7 = ob3;
cout<<"ob6.num = "<<ob6.num<<endl;
}
int main(int argc, char *argv[])
{
cout << "-----------01----------" << endl;
test01();
cout << "-----------02----------" << endl;
return 0;
}
记住一句话:旧对象初始化新对象才会调用拷贝构造函数
Data ob1(10);
Data ob2(ob1);//拷贝构造
Data ob3 = Data(ob1);//拷贝构造
Data ob4 = ob1;//拷贝构造函数
注意:下方就不会调用拷贝构造
Data ob1(10);
Data ob2;
ob2 = ob1;//不会调用拷贝构造 单纯对象 赋值操作
案例:
void test02(){
Data ob1 = Data(100);
Data ob2;
ob2 = ob1;
cout<<"ob1.num = "<<ob1.num<<endl;
cout<<"ob2.num = "<<ob2.num<<endl;
}
int main(int argc, char *argv[])
{
cout << "-----------01----------" << endl;
test02();
cout << "-----------02----------" << endl;
return 0;
}
4、拷贝函数的注意事项
(1)不能调用拷贝构造函数区初始化匿名对象
(2) 对象作为函数的参数 如果实参与形参都是普通对象 那么就会调用拷贝构造
(3)函数返回局部对象,在qt中会被优化,从而调用不了拷贝构造
知识点9【构造函数的调用规则】
系统默认会对任何一个类提供三个函数成员函数:
默认构造函数(空) 默认析构函数(空) 默认拷贝构造函数(浅拷贝)
1、如果用户提供了有参构造,将会屏蔽系统的默认构造函数
2、如果用户提供了有参构造,不会屏蔽系统的默认拷贝构造函数
3、如果用户提供了拷贝构造函数,将会屏蔽系统的默认构造函数和默认拷贝构造函数
知识点10【深拷贝与浅拷贝】
1、浅拷贝
浅拷贝只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存
2、深拷贝
深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。
浅拷贝:
#include <iostream>
#include<string.h>
using namespace std;
class Person{
private:
char *m_name;
int num;
public:
Person(int a, char *name){
num = a;
m_name = new char[strlen(name)+1];
strcpy(m_name,name);
cout << "有参构造" <<endl;
}
~Person(){
delete [] m_name;
cout << "空间已被释放" << endl;
cout << "析构函数" << endl;
}
void setNum(int a){
num = a;
}
void setString(char* str){
strcpy(m_name,str);
}
void showPerson(){
cout << m_name << " "<< num << endl;
}
};
int main(int argc, char *argv[])
{
Person person = Person(100,"lucy");
Person bob1 = person;
cout<<"------person输出值-------"<<endl;
person.showPerson();
person.setNum(1000);
person.setString("lucy2");//修改person中m_num的值
cout<<"------person输出值-------"<<endl;
person.showPerson();
cout<<"------bob1输出值-------"<<endl;
bob1.showPerson();
return 0;
}
运行结果:
使用person去初始化bob1调用拷贝构造函数,我们的目的是将person中的内容拷贝到bob1的空间中,但实际上,使用浅拷贝的方式只是改变了bob1的指向,让bob1指向person所指向的空间(person和bob1的字符串m_name指向堆区同一块空间),因此,当使用函数person.setString(“lucy2”)对字符串进行修改后,bob字符串的值也会被修改,;这会使得调用析构函数时,同一空间被释放两次,造成错误;
深拷贝:
#include <iostream>
#include<string.h>
using namespace std;
class Person{
private:
char *m_name;
int num;
public:
Person(int a, char *name){
num = a;
m_name = new char[strlen(name)+1];
strcpy(m_name,name);
cout << "有参构造" <<endl;
}
Person(const Person &ob){
num = ob.num;
m_name = new char[strlen(ob.m_name)+1];
strcpy(m_name,ob.m_name);
cout << "有参构造" <<endl;
}
~Person(){
delete [] m_name;
cout << "空间已被释放" << endl;
cout << "析构函数" << endl;
}
void setNum(int a){
num = a;
}
void setString(char* str){
strcpy(m_name,str);
}
void showPerson(){
cout << m_name << " "<< num << endl;
}
};
int main(int argc, char *argv[])
{
Person person = Person(100,"lucy");
//Person bob = Person(person);
Person bob1 = person;
cout<<"------person输出值-------"<<endl;
person.showPerson();
person.setNum(1000);
person.setString("lucy2");
//bob.showPerson();
cout<<"------person输出值-------"<<endl;
person.showPerson();
cout<<"------bob1输出值-------"<<endl;
bob1.showPerson();
return 0;
}
运行结果:
从代码运行结果来看,我们可以通过重新构建拷贝构造函数的方式,完成深拷贝的工作,就算person对象对值进行修改,也不会影响bob的值。
注意:如果类中的成员指向了堆区空间,一定要记得在析构函数中释放空间。如果用户不实现拷贝构造,系统就会提供默认拷贝构造值,而默认拷贝构造只是单纯的赋值,如果赋值为指针的话,则两个对象会指向同一块堆区空间,容易造成浅拷贝的问题。用户记得要实现:无参构造(初始化数据)、有参构造(赋参数)、拷贝构造(深拷贝)、析构函数(释放空间)。
知识点11【初始化列表】
注意:初始化成员列表(参数列表)只在构造函数使用
#include <iostream>
using namespace std;
class Data{
public:
int m_a;
int m_b;
int m_c;
Data(int a,int b,int c):m_a(a),m_b(b),m_c(c){
cout<<"有参构造函数"<<endl;
}
~Data(){
cout<<"析构函数"<<endl;
}
void showData(){
cout << m_a << " " << m_b << " " << m_c << endl;
}
};
int main(int argc, char *argv[])
{
Data data = Data(10,100,1000);
data.showData();
return 0;
}
知识点12【类对象作为另一个类的成员】
#include <iostream>
using namespace std;
class A
{
private:
int m_a;
public:
A()
{
cout<<"A无参构造函数"<<endl;
}
A(int a)
{
m_a = a;
cout<<"A有参构造函数"<<endl;
}
~A()
{
cout<<"A析构函数"<<endl;
}
};
class B
{
private:
int m_b;
public:
B()
{
cout<<"B无参构造函数"<<endl;
}
B(int b)
{
m_b = b;
cout<<"B有参构造函数"<<endl;
}
~B()
{
cout<<"B析构函数"<<endl;
}
};
class Data
{
private:
A oba;//对象成员
B obb;//对象成员
int data;//基本类型成员
public:
Data()
{
cout<<"Data无参构造"<<endl;
}
//初始化列表:对象名+() 显示调用 调用对象成员的构造函数
Data(int a, int b, int c):oba(a),obb(b),data(c)
{
//data =c;
cout<<"Data有参构造"<<endl;
}
~Data()
{
cout<<"Data析构函数"<<endl;
}
};
void test01()
{
//先调用 对象成员的构造‐‐>自己的构造函数‐‐>析构自己‐‐‐>析构对象成员
//Data ob1;
//系统默认调用的是 对象成员的无参构造
//必须在Data的构造函数中 使用初始化列表 使其对象成员 调用有参构造
Data ob2 = Data(10,20,30);
}
int main(int argc, char *argv[])
{
test01();
return 0;
}
注意:类中的成员变量中如果有对象实例化的对象,则在对该类进行实例化时,应该先满足实例化的对象的构造函数条件。