C++复习笔记3——类与对象
C++三大特征
封装
继承
多态
类与对象
1.C++ struct和class的区别
/ | / | / |
---|---|---|
struct | 默认 | public |
class | 默认 | private |
2.对象和变量的区别
对象就是内存+资源
调用对象时做的事是开辟内存并初始化
变量就是内存
使用变量做的事仅仅是开辟了内存
3.面向过程和面向对象的区别
https://blog.csdn.net/abcdnml/article/details/75329520
OPP思想
封装
/ | / |
---|---|
public | 任意位置都可以访问 |
protected | 只有本类和子类可访问 |
privated | 只有本类可访问 |
【我想访问私有成员可以通过访问公有成员来访问】
【属性和实现细节被隐藏起来,对外来提供公有接口来调用】??
this指针
类成员方法的调用约定 _thiscall;
类中默认的函数
类中默认的函数(如果有写就会用写的,没有写就会用系统提供的):
构造函数
- 初始化对象所占用的内存 赋予资源
- 可以重载
- 不依赖对象调用
- 不用手动调用//因为调用的时候对象还没生成 如同鸡生蛋蛋生鸡那样
析构函数
- 释放对象所占用的资源
- 不可重载
- 手动调用 退化成普通函数的调用//系统本身会调用,如果使用自己写的析构函数还需要在main里自己手动调用一次 就像普通函数的调用一样
~String()
{
delete[] mptr;//释放mptr指向的空间
mptr = NULL;//置空指针
}
拷贝构造函数
- 拿已经存在的对象来生成相同类型的新对象
- 形参 一定要用引用的,因为要避免递归调用生成形参对象最终导致栈溢出程序崩溃。
【不写拷贝构造系统也可以自己纪念性拷贝构造,但会崩溃因为默认的拷贝构造函数是一个浅拷贝函数】
【浅拷贝:多个对象拥有一个资源 】
【深拷贝:每个对象都有自己的资源 】
如:
Student(const Student& rhs)//拷贝构造函数 形参要用引用
{
mid = rhs.mid;
mname = rhs.mname;
msex = rhs.msex;
mage = rhs.mage;
}//拷贝构造函数
int main()
{
Student stu1;
stu2=stu1;//拷贝构造函数
}
赋值运算重载
- 拿已经存在的对象给相同类型的已存在赋值
- 返回值为 类 类型的引用 (此处是因为这样做可以连续赋值)
- 赋值运算符重载函数是浅拷贝函数
- 该函数要做的事有:(如例2)
(1)自赋值
(2)释放旧资源
(3)开辟新资源(内存)
(4)赋值
- 例1(不安全的
Student& operator=(const Student& rhs)
{
if (this != &rhs)//这里进行自赋值判断是为了减少自赋值如果生成的是对象会占用资源
//所以不想浪费资源选择返回this指针。
{
mid = rhs.mid;
mname = rhs.mname;
msex = rhs.msex;
mage = rhs.mage;
}
return *this;//如果是自赋值返回this指针
}
//这是一种不安全的赋值运算符重载,但如果私有成员中没有指针是可以的
//如果私有成员中存在指针 该函数将会造成内存泄漏的严重情况
内存泄漏的情况:
String& operator=(const String& rhs)//赋值运算符重载函数
{
if (this != &rhs)
{
mptr=rhs.mptr;
}
return *this
}//例1的相同方法用在私有成员是指针的情况
int main()
{
String str1("hello");
String str2;
str2 = str1;//如果此时主函数这样调用 然而赋值函数用的是例一的方法 就会发生内存泄漏
return 0;
}
例2(相对安全的,也存在不安全的情况,这是一种私有成员内有指针的情况)
class String
{
public:
String()//构造函数
{
mptr = new char[1]();
}
String(char* ptr)
{
mptr = new char[strlen(ptr) + 1]();
strcpy_s(mptr, strlen(ptr) + 1, ptr);
}
String(const String& rhs)//拷贝构造函数
{
mptr = new char[strlen(rhs.mptr) + 1]();
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
}
String& operator=(const String& rhs)//赋值运算符重载函数
{
if (this != &rhs)
{
delete[] mptr;
mptr=nullptr;//释放mptr的内存空间,此时mptr为NULL
mptr = new char[strlen(rhs.mptr) + 1]();//给mtpr开辟新内存 大小为赋值源对象的长度+1
/*
此处上面两个函数会有一种不安全的情况:如果先释放mptr内存空间,
但是内存不足导致开辟内存失败了new char抛出异常
就会导致丢失了整个对象 mprt就是一个空指针,
非常容易导致程序崩溃,是一个很严重的问题。
*/
//这里应该先开辟内存再释放mptr空间,防止内存开辟失败导致丢失这个对象
//--->剑指offer面试题1(会写在例3中)
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
//c++认为strcpy不安全,使用strcpy_s(目的,字节长度,源)可以改变这个不安全的情况
}
return *this
/*
自赋值
释放旧的资源
开辟新的资源
赋值,
*/
}
private:
char* mptr;
}
例3(更加安全,考虑到异常的解法)
剑指offer面试题1:赋值运算符重载
String& operator=(const String& rhs)
{
if(this!=&rhs)
{
String strTemp(rhs);//构建一个临时的实例 生存周期仅为该函数结束
char*pTemp = strTemp.mptr;//建一个指针指向临时实例
strTemp.mptr=mptr;//把mptr给临时实例
mptr=pTemp;//最后
}
}
这个方法先创建一个临时实例,在交换临时实例和原来的实例,在这个函数中我们先创建一个临时实例strTemp,接着把strTemp.mptr和实例自身的mptr进行交换。由于strTemp.mptr是一个局部变量,但程序运行到if的外面时也就出了该变量的作用域就会被自动调用该实例的析构函数把strTemp.mptr指向的内存释放掉。由于strTemp.mptr指向的内存就是实力之前mptr的内存,这就相当于自己调用析构函数释放实例的内存。
取地址操作符的重载函数
const修饰的取地址操作符的重载函数
Static静态成员变量
- static修饰成员变量
1. 不属于对象 属于类作用域
2. 一定要在类外初始化
//类内:
static int mb;
//类外
int Test::mb=10;//(Test为类名)
- static修饰成员方法
1. _cdecl 没有thiscall(this指针) 不依赖对象调用
2. 不能调用普通的类成员(包括成员变量和成员方法)
3. 不能访问除静态成员变量之外的变量
临时量
【引用提升临时对象的生存周期 】
-
const static修饰成员
-
const 修饰成员变量
-
1.临时对象生成 构造函数
-
2.新对象生成 拷贝构造
-
3.临时对象销毁 析构函数
赋值
赋值先看类型匹配:
- 类型是否匹配,匹配就赋值不匹配往下走
- 隐式转换:看一下类中是否有像我一样的构造函数可以构造成同类型,有则可以生成临时对象来调用构造函数构造一个同类型的进行赋值
- 强制转化
- 报错(临时量分为有优化和没有优化)
临时量在赋值运算符重载中的应用
x’x’l’l’l’l’l’l’l’l’l’l’l’l’l’l’l’l’l
main()函数
- Test test1;
test1=30;
//30和test1不是同类型生成一个临时对象调用里面的构造函数然后构造一个临时对象然后就可以赋值了 - Test &rt2=Test(10);
引用提升了临时对象生存周期,临时对象生存周期由原本的到表达式结束变为到函数结束,生存周期延长。? - Test *pt3=&Test(10);
//构造函数+析构函数 - const Test&rt3=10;
//10为常量,常引用可以,10生成临时对象并起名为rt3。如果不加const是不对的。
示例代码:
class Test
{
public:
Test(int a = 0) :ma(a)
{
std::cout << "Test::Test(int)" << std::endl;
}
Test(const Test& rhs)
{
std::cout << "Test::Test(const Test&)" << std::endl;
ma = rhs.ma;
}
~Test()
{
std::cout << "Test::~Test()" << std::endl;
}
Test& operator = (const Test& rhs)
{
std::cout << "Test::operator=(const Test&)" << std::endl;
if (this != &rhs)
{
ma = rhs.ma;
}
return *this;
}
private:
int ma;
};
Test gtest1(10);
int main()
{
Test ltest1(10);//main函数结束//调用点到当前作用域结束
Test ltest2(ltest1); //拷贝构造函数
ltest1 = 20;//隐式生成临时对象调用构造函数
ltest2 = Test(20);//显示生成一个临时对象 临时对象无优化
ltest2 = (Test)(34, 34, 67, 67789, 654);//只赋值654逗号表达式654-》ltest2
Test* ptest1 = new Test();
delete ptest1;//在堆上不遵循先构造后析构 delete结束了new开辟的就结束了
Test* ptest2 = new Test[10];//?
delete[] ptest2;
Test& rt1 = ltest1;//给对象ltest起别名叫rt1
std::cout << "--------------------" << std::endl;
Test& rt2 = Test(10);//给显示生成的临时对象起别名为rt2
std::cout << "--------------------" << std::endl;
Test* ptest3 = &Test(10);//?????/???????
std::cout << "--------------------" << std::endl;
const Test& rt3 = 10;//常引用,10生成临时变量并起名为rt3????
return 0;
}
Test gtest2(20);
int main()
{
//const int& a = 10;
Test test1(20);//建立对象并传参初始化 构造函数+析构
test1 = 20;//20生成了一个临时对象调用了构造函数并赋值 构造+赋值+析构
test1 = Test(20);//显示生成了临时对象 构造函数+赋值+析构
//test1 = 30;// Test <= int //Test <== Test
//std::cout << "--------------------" << std::endl;
//Test test2 = 20;// Test <== int //Test <== Test
//std::cout << "--------------------" << std::endl;
/*
1.临时对象生成 构造函数
2.新对象生成 拷贝构造
3.临时对象销毁 析构函数
*/
//int a = 10.1;//int = double
return 0;
}
学习代码(仅用来举例学习本博客内容)
#include<iostream>
class String
{
public:
String()//构造函数
{
mptr = new char[1]();
}
String(char* ptr)
{
mptr = new char[strlen(ptr) + 1]();
strcpy_s(mptr, strlen(ptr) + 1, ptr);
}
String(const String& rhs)//拷贝构造函数
{
mptr = new char[strlen(rhs.mptr) + 1]();
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
}
String& operator=(const String& rhs)//赋值运算符重载函数
{
if (this != &rhs)
{
mptr = new char[strlen(rhs.mptr) + 1]();//先开辟内存后释放空间
delete[] mptr;//这里应该先开辟内存再释放mptr空间,防止内存开辟失败就会丢失这个对象
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);//c++认为strcpy不安全,使用strcpy_s(目的,字节长度,源)可以改变这个不安全的情况
}
return *this
/*
自赋值
释放旧的资源
开辟新的资源
赋值,
*/
}
~String()
{
delete[] mptr;//释放mptr指向的空间
mptr = NULL;//置空指针
}
private:
char* mptr;
int main()
{
String str1("hello");
String str2;
str2 = str1;
return 0;
}
class Student
{
public:
Student(){}
Student(const std::string& id, const std::string& name, bool sex, int age)
:mid(id), mname(name), msex(sex), mage(age)
{}
Student(const Student& rhs)
{
mid = rhs.mid;
mname = rhs.mname;
msex = rhs.msex;
mage = rhs.mage;
}
Student& operator=(const Student& rhs)
{
if (this != &rhs)
{
mid = rhs.mid;
mname = rhs.mname;
msex = rhs.msex;
mage = rhs.mage;
}
return *this;
}
~Student()
{
std::cout << "Student::~Student()" << std::endl;
}
void eat(const std::string& meal)//Student* const this
{
std::cout << this->mname << " is eatting " << meal << std::endl;
}
void sleep(const std::string& time)
{
std::cout << mname << " is sleeping at " << time << std::endl;
}
void solve(const std::string& math)
{
std::cout << mname << " is doing this " << math << std::endl;
}
private:
std::string mid;
std::string mname;
bool msex;
int mage;
};
int main()
{
//int a = 10;
//int b = 20;
//a = b;
Student stu1("001", "zhangsan", true, 19);
Student stu2;
stu2 = stu1;
return 0;
}
int main()
{
Student stu1("001", "zhangsan", true, 19);
Student stu2 = stu1;
int a = 10;
int b = a;
return 0;
}
int main()
{
Student stu1;
stu1.eat("肉");
stu1.~Student();
return 0;
}
#endif