当前位置: 首页 > news >正文

对类和对象的详细解析

目录

1.类的构成

2.this指针

 3.C++类里面的静态成员

 3.1 类里面的静态成员函数

3.2 类里面的静态成员变量

静态成员变量的特点

共享性

存储位置

生命周期

访问权限

如何初始化?

 构造函数


1.类的构成

 public修饰的成员在类外可以直接被访问

private和protected修饰的成员不能在类外被访问

如果变量没有被这三个修饰词来修饰的话,则默认为private

2.this指针

关于this指针的一个经典回答:

有一个房子类

当你进入这个房子的时候,你可以看见桌子、椅子等,但是房子的全貌你看不见了

对于一个类的实例来说,你可以看见他的成员函数、成员变量

但是实例本身呢?

this这个指针就时时刻刻指向这个实例本身

this指针是指向对象的指针,存放对象的首地址

#include <iostream>
using namespace std;
class Student
{
public:void Setage(int age){this->age = age;//this->age表示这个类所指向的age,单独的age是形参}void Showage(){cout << age << endl;}private:int age;
};
int main()
{Student s;s.Setage(18);s.Showage();return 0;
}
//输出结果为18
#include <iostream>
using namespace std;
class Student
{
public:void Setage(int age){age = age;//age=age,并不会改变类里面age的值}void Showage(){cout << age << endl;}private:int age;
};
int main()
{Student s;s.Setage(18);s.Showage();return 0;
}
//输出结果是0

this指针是编译器帮我们加上的,不占用空间

#include <iostream>
using namespace std;
class Student
{
private:int age;
};int main()
{cout << sizeof(Student) << endl;//结果为4return 0;
}

既然this指针指向类,那么类的地址和this的地址是一样的

#include <iostream>
using namespace std;
class Student
{
public:void test(){cout << this << endl;}private:int age;
};int main()
{Student s;s.test();cout << &s << endl;return 0;
}

输出的地址是一样的

 3.C++类里面的静态成员

静态成员是属于整个类的而不是某个对象,静态成员只存储一份供所有对象使用

 3.1 类里面的静态成员函数

静态成员函数没有隐藏的this指针

#include <iostream>
using namespace std;
class Student
{
public:void Set(int age){this->age = age;}void Showage(){cout << age << endl;}static void test(){cout<<this->age<<endl;//这个地方的age会报错,如果不加this也会报错//静态成员函数不能引用非静态成员,但是可以在类的非静态成员函数里面使用静态成员}
private:int age;
};int main()
{Student s;return 0;
}

这就说明了静态函数是在这个类创建出来之前就出现了,也就是说静态函数的创建时间比这个类早,所以在这个静态函数里面使用类里面的东西会报错

那么应该如何使用这个函数呢

首先要注意一点:这个静态函数不能使用类里面的东西

class Student
{static void test(){cout<<"i want to sleep"<<endl;}
};int main()
{Student s;Student::test();//这里记住不能通过类名调用非静态成员函数s.test();//上面两句说明类的对象可以使用静态和非静态成员函数return 0;
}

这两种方法都可以调用这个静态函数

3.2 类里面的静态成员变量

静态成员变量在类的所有实例之间共享,并且可以在不创建类的情况下访问(在类创建之前就创建好了

3.3静态成员变量的特点

共享性

静态成员变量在类所有的实例之间共享,这个类的每个对象的静态变量都是相同的

存储位置

静态成员变量存储在静态存储区,而不是每个对象的堆栈中。这使得他们可以在不创建类的实例的情况下访问

生命周期

与程序的生命周期一样。在程序启动的时候就创建了,在程序结束的时候就销毁

访问权限

静态成员变量可以通过类名来访问,也可以通过对象来访问。但是建议前者,这样子能强调他们的共享性

如何初始化?

class Student
{
private:static int a;
};int Student::a=0;

 4构造函数

构造函数的主要作用不是开辟空间创建对象,而是为了初始化对象

特征

他不是开空间创建对象,而是初始化对象

1. 无返回值(也不用写void)

2. 函数名与类名相同

3. 构造函数可以重载

#include <iostream>using namespace std;class Student
{
public:Student(int a,int b,int c)//构造函数{_a=a;_b=b;_c=c;}Student(int a,int b){_a=a;_b=b;}void Show(){printf("%d %d %d",_a,_b,_c);}
private:int _a,_b,_c;
};int main()
{Student s(1,2,3);//创建s对象的同时调用构造函数Student p(1,2);//函数重载s.Show();return 0;
}
//输出结果1 2 3
#include <iostream>using namespace std;class Student
{
public:void Show(){printf("%d %d %d",_a,_b,_c);}
private:int _a,_b,_c;
};int main()
{Student s;s.Show();return 0;
}
/*
输出结果32767 0 0
如果类里面没有构造函数,则C++编译器会自动调用一个无参的默认构造函数
*/
private:int _a=1,_b=1,_c=1;/*
这里对_a,_b,_c不是赋值不是初始化,而是给缺省值
原因:这里的变量都是声明,还没有创建出来
*/

5析构函数

作用与构造函数相反。在对象销毁时自动调用,完成对象中资源的清理工作

特征

析构函数名是在类名的签名加~

无参数无返回值

一个类只能有一个析构函数,如果自己没有定义,系统会调用默认的析构函数。析构函数不能重载

对象生命周期结束时调用

析构函数的执行在return之前

#include <iostream>
using namespace std;
class S
{
public:S(){cout << "构造" << endl;}~S(){cout << "析构" << endl;}
};
int main()
{S s;return 0;
}

先输出构造,然后再输出析构

 作用

当我们在类中声明了一些指针变量的时候,我们一般在析构函数里面释放这些指针变量所占有的空间,因为系统不会释放指针变量指向的空间,我们需要自己来delete

6.构造函数和析构函数的一些应用

6.1 定义为局部变量

先补充一个知识点:局部变量存储在栈区,全局变量和静态变量存储在静态区

#include <iostream>using namespace std;class Date
{
public:Date(int a){_a = a;cout << "Date()->" << _a << endl;}~Date(){cout << "~Date()->" << _a << endl;}private:int _a;
};int main()
{Date a(1);Date b(2);return 0;
}

输出结果

析构函数的时候为什么先输出~Date()->2呢?

首先我们要知道a和b变量都是局部变量,都存储在栈区,栈区遵循后进先出的原则

 6.2 局部变量和全局变量同时存在时

#include <iostream>using namespace std;class Date
{
public:Date(int a){_a = a;cout << "Date()->" << _a << endl;}~Date(){cout << "~Date()->" << _a << endl;}private:int _a;
};Date b(2);int main()
{Date a(1);return 0;
}

输出结果

全局变量先定义,在程序结束之后再销毁(在局部变量销毁之后再销毁)

 6.3 静态变量、局部变量、全局变量共存

#include <iostream>using namespace std;class Date
{
public:Date(int a){_a = a;cout << "Date()->" << _a << endl;}~Date(){cout << "~Date()->" << _a << endl;}private:int _a;
};Date b(2);int main()
{Date a(1);static Date c(3);return 0;
}

输出结果

创建变量按顺序来。首先是局部变量被销毁,静态区的变量(只考虑这个区域时)在销毁时也是类似“后进先出”一样来销毁

 6.4 静态变量在循环中

#include <iostream>using namespace std;class Date
{
public:Date(int a){_a = a;cout << "Date()->" << _a << endl;}~Date(){cout << "~Date()->" << _a << endl;}private:int _a;
};int main()
{for (int i = 0; i <= 1; i++){static Date a(1);Date b(2);}return 0;
}

输出结果

静态变量只会被创建一次,所以只有一次构造函数和一次析构函数

 7.拷贝构造函数

浅拷贝

拷贝成员变量的值

#include <iostream>using namespace std;class Student
{
public:Student(int a, int b, int c){_a = a;_b = b;_c = c;}/*当有Student b(a)这行代码的时候问题1:为什么要写成 Student &s呢?写成Student &s表示s是a的别名,不需要调用拷贝构造函数如果写成Student s的话,会无限递归原因:在调用拷贝构造函数的时候,
Student(Student s){this->_a = s._a;this->_b = s._b;this->_c = s._c;}
把a传给s也会调用拷贝够咱函数,也就是说,调用一个拷贝构造函数会使得其内部嵌套另一个拷贝构造函数
那这个嵌套的拷贝构造函数也会继续嵌套一个,依次无限嵌套下去而导致死循环;所以要加一个&问题2:那为什么加一个const呢?因为我们要a的值不变,将其内部的值赋给b(s),所以我们最好加一个const来修饰来让a的值不变
只要a里面的值变,就会报错
因为有可能出现以下情况
Student(const Student &s){a._a = this->_a;a._b = this->_b;a._c = this->_c;}
如果没有const,出现上述情况编译器是不会报错的*/Student(const Student &s){this->_a = s._a;this->_b = s._b;this->_c = s._c;}void Show(){printf("%d %d %d\n", _a, _b, _c);}private:int _a, _b, _c;
};int main()
{Student a(10, 20, 30);Student b(a);b.Show();return 0;
}

输出结果

 8.重载

关键字:operator

8.1 运算符重载

#include <iostream>
using namespace std;
class Student
{
public:Student(int a, int b, int c){_a = a;_b = b;_c = c;}bool operator==(const Student &b)//重载==运算符{return _a == b._a && _b == b._b && _c == b._c;//a和b相等的条件}
private:int _a, _b, _c;
};int main()
{Student a(10, 20, 30);Student b(10, 20, 30);cout << (a == b) << endl;//这里判断a和b是否相等只需要判断a和b里面的_a,_b,_c是否分别相等return 0;
}
#include <iostream>
using namespace std;
class Student
{
public:Student(int a, int b, int c){_a = a;_b = b;_c = c;}int operator-(const Student &b){return b._c - _c;}private:int _a, _b, _c;
};int main()
{Student a(10, 20, 30);Student b(10, 20, 50);cout << (b - a) << endl;return 0;
}
//输出结果是a._c - b._c  结果为-20

这里就有一个问题

void operator++是怎么区别前置++和后置++的呢?

int operator++();//前置++int operator++(int);//后置++
//规定:()里面无参数的为前置++,否则为后置++

注意点

#include <iostream>
using namespace std;
class A
{
public:A(int val){_val=val;}void operator+(const A& d){_val+=d._val;}void operator=(const A& d){_val=d._val;}void Print(){cout<<_val<<endl;}
private:int _val;
};
int main()
{A a(3),b(4),c(7);c=a=b;
/*
c=a=b会报错,但是a=b,不会报错
因为c=a=b这句话首先执行的是a=b,然后返回值是void
所以
void operator=(const A& d){_val=d._val;}
应该改成
A operator=(const A& d){_val=d._val;return *this;//这里返回值是*this的一个拷贝(拷贝的话可能调用其中的拷贝函数或者构造函数什么的)}
所以最好写成
A& operator=(const A& d){_val=d._val;return *this;//这里返回的是*this的别名,不会再去调用拷贝函数}
*/a.Print();return 0;
}

8.2 流重载

#include <iostream>using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void operator<<(ostream &out) // out是cout的别名{out << _year << '/' << _month << '/' << _day << endl;}void operator>>(istream &in){in>>_year>>_month>>_day;}private:int _year, _month, _day;
};int main()
{Date a(2023, 9, 16);a>>cin;//a是被操作的对象,cin传给ina<<cout;return 0;
}

const的参与

#include <iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){printf("%d %d %d\n", _year, _month, _day);}private:int _year, _month, _day;
};
int main()
{Date a(2023, 9, 17);const Date b(1,1,1);a.Print();b.Print();//这里会报错
/*
因为b是一个const Date类型的
b.Print()的原型是b.Print(&b),&b是一个const Date*类型的
void Print()的原型是void Print(Date* const this)
&b从const Date*变成Date*权限放大了
所以我们将void Print()写成void Print() const,这样他的原型变成了:
void Print(const Date* const this)是const Date*类型,后面那个const是修饰this
让this一直指向这个类且不能变
*/return 0;
}
#include <iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print()const{printf("%d %d %d\n", _year, _month, _day);}bool operator<(const Date& d){return _year<d._year;}private:int _year, _month, _day;
};
int main()
{Date a(2023, 9, 17);const Date b(1,1,1);a<b;//不会报错b<a;//这里会报错
/*
bool operator<(const Date& d)的原型是
bool operator<Date* const this,const Date& d)
所以写成b<a的时候(b是const Date*类型)
b由const Date*变成Date*,权限变大
可以写成
bool operator<(const Date& d)const
*/return 0;
}

 9.初始化列表

初始化列表可以认为是成员变量定义的地方

(private里面是声明)

9.1格式

#include <iostream>
using namespace std;
class Date
{
public:
//格式如下Date(int a): _a(a),_b(a){}void Print(){cout << _a << endl;}private:int _a;int &_b;
};
int main()
{Date a(6);a.Print();return 0;
}

9.2例子

每个成员变量在初始化列表中只能出现一次

类中包含以下成员,必须放在初始化列表位置进行初始化

1.引用成员变量

2.const成员变量

3.自定义类型成员

class A
{
public:A(int a): _a(a){}void Print(){cout << _a << endl;}private:int _a;
};class B
{
public:B(int a, int b): _b(b),_a(a),_n(10),_r(b){}void Print(){_a.Print();cout << _b << endl;}private:int _b;A _a;const int _n;int& _r;
};

 成员变量在类中声明的次序就是其在初始化列表里面初始化的次序,与其在初始话列表里面的先后顺序无关

例子:

#include <iostream>using namespace std;class A
{
public:A(int a):_a(a),_b(_a){}void Print(){cout<<_a<<endl;cout<<_b<<endl;}
private:int _b;int _a;
};int main()
{A a(3);a.Print();return 0;
}
/*
输出结果是 3 随机值
因为先是把_a赋值给_b,再把a赋值给_a
*/

9.3explicit关键字

用explicit修饰的构造函数,会禁止单参构造函数的隐式转换

首先搞明白什么是隐式转换

class A
{
public:A(int a)//单参构造函数: _a(a){}private:int _a;
};//定义一个类对象
A a(10);
/*
此时_a的值为10,如果进行以下操作,a=20,这样子会把_a的值变为20
编译器会用20(将20赋值给无名对象的_a)构造一个无名对象
最后用无名对象赋值给对象a,这种转换就叫隐式转换
*/

隐式转换其实还有很多,比如

int a=2;

double b=a;

编译器会先引入一个中间变量,类型为double,然后再将这个值赋值给b

#include <iostream>using namespace std;class A
{
public:A(int a)
/*
如果这里改成explicit A(int a)的话
a=5那行代码会报错
因为explicit会禁止单参构造函数的隐式转换
*/: _a(a){}void Print(){cout << _a << endl;}private:int _a;
};int main()
{A a(3);a = 5;a.Print();return 0;
}

 9.4 匿名对象

生命周期只有它所在的那一行

class A
{
public:A(int a):_a=a{}
private:int _a;
}; A a(10);a = A(20);a.Print();如果不使用匿名对象,那就需要:
A a(10);
A b(20);
a=b;
这样子来操作
/*
A(20)表示创建一个匿名对象,他的_a值为20
这个匿名对象创建的时候也会调用构造函数和析构函数
*/

10.友元类

如果类A是类B的友元类,那么B可以调用A里面的private成员和protected成员

但是这样子做会破坏类的封装性

注意点

1. 友元关系不能被继承

2. 友元关系是单向的,A是B的友元,但是B不一定是A的友元

3. 友元不具有传递性,比如A是B的友元,B是C的友元,但是A不是C的友元

#include <iostream>using namespace std;class A
{
public:friend class B;//代表A是B的友元类,B可以访问A里面的所有成员A(int a){_a=a;}
private:int _a;
};class B
{
public:B(int b){_b=b;}void Show_A_a(A a){cout<<a._a<<endl;//这里就体现了可以访问A定义的对象a里面的私有成员}
private:int _b;
};int main()
{A a(666);B b(555);b.Show_A_a(a);return 0;
}

10. 2内部类

定义:如果一个类定义在另一个类的里面,那这个里面的类就叫做内部类

内部类是外部内的友元类,但是外部类不是内部类的友元类

比如A是外部类,B是外部类

那么定义一个类的时候应该A::B b;定义一个对象b,b能调用A和B里面的成员,但是如果A a,那a不能调用B里面的成员

http://www.lryc.cn/news/170768.html

相关文章:

  • matlab 间接平差法拟合二维圆
  • pgzrun 拼图游戏制作过程详解(10)
  • glog与pugi::xml使用方法
  • windows下MySQL服务不见,服务无法启动,服务闪退,提示“本地计算机上的MySQL服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止”
  • 剑指offer(C++)-JZ67:把字符串转换成整数atoi(算法-模拟)
  • 嵌入式笔试面试刷题(day15)
  • 【Docker】Dockerfile构建镜像
  • fota升级,可卸载apk也进行更新
  • ASP.NET dotnet 3.5 实验室信息管理系统LIMS源码
  • 2023!6招玩转 Appium 自动化测试
  • WireShark抓包分析TCP三次握手过程,TCP报文解析
  • 【C语言】指针和数组笔试题解析
  • Vue的模板语法(下)
  • Zookeeper客户端——I0Itec-zkClient
  • 火山引擎 ByteHouse:ClickHouse 如何保证海量数据一致性
  • hashmap使用
  • Centos7配置国内yum源
  • C#中async/await的线程ID变化情况
  • 网络安全—黑客技术—自学笔记
  • 功夫再高也怕菜刀。多年经验,会独立开发的机器视觉工程师,技术太强,但是找工作能力差劲
  • numpy的多项式函数: `poly1d`
  • Python灰帽编程——错误异常处理和面向对象
  • 【20230919】win11无法删除Chrome注册表项
  • TCP/IP客户端和服务器端建立通信过程
  • Python ---使用Fake库向clickhouse造数据小案例
  • 09MyBatisX插件
  • 使用 Messenger 跨进程通信
  • Spring Cloud Gateway
  • JVM 优化技术
  • 【MySQL系列】- MySQL自动备份详解