如果每个人都能理解你,那你得普通成什么样子。
这是我自己学习C++的第二篇博客总结。后期我会继续把C++学习笔记开源至博客上。 上一期笔记是关于C++的入门基础知识,没看的同学可以过去看看: 关于C++入门基础知识的笔记
https://blog.csdn.net/hsy1603914691/article/details/142715432?spm=1001.2014.3001.5502
1. class为定义类的关键字。比如说:定义一个栈结构,class为定义类的关键字,Stack为类的名字,{ }中为类的主体,注意类定义结束时后面的 ; 不能省略。 2. 类的主体中的内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。 3. 为了区分成员变量,成员变量会加⼀个特殊标识,如成员变量前面或者后面加上 _ ,又或者可与以 m 开头。 4. C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐用class定义类。 5. 定义在类面的成员函数默认为inline,运行效率更高。
class Stack
{
//类的主体
};
1. C++⼀种实现封装的方式,就是用类将对象的属性(变量)与方法(函数)结合在⼀块,让对象更加完善,通过访问权限,选择性的将其接口提供给外部的用户使用。 2. public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private本质作用是⼀样的。 3. 访问权限作用域从该访问限定符出现的位置开始,直到下⼀个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } ,即类结束。 4. 一般来说,对象的属性(变量)是私有的,对象的方法(函数)是共有的。 5. class定义成员没有被访问限定符修饰时默认为private,struct默认为public。 6. 一般来说,我们通过类的函数来改变类的变量。
#include <iostream>
using namespace std;
class Stack
{
public:
// 成员函数
void Init(int n = 4)
void Push(int x)
int Top()
void Destroy()
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
int main()
{
Stack st;//创建一个栈类型的变量,命名为st
st.Init();//初始化st
st.Push(1);//尾插st
st.Push(2);//尾插st
cout << st.Top() << endl;//打印st的首元素
st.Destroy();//销毁st
return 0;
}
1. 类定义了⼀个新的作用域,类的所有成员都在类的作用域中,在类体外使用成员时,需要使用 :: (作用域操作符)指明成员属于哪个类域。 2. 当类域中的函数定义过长时候,一般在类域中存放函数声明,在类域外面定义函数。 3. 类域影响的是编译的查找规则,下面程序中 Init() 函数如果不指定类域Stack,那么编译器就把 Init() 函数当成全局函数,那么编译时,找不到函数中成员的声明、定义在哪里,就会报错。 4. 指定类域Stack,就是知道 Init() 函数是成员函数,当前域找不到函数中那些成员,就会到类域中去查找。
#include<iostream>
using namespace std;
class Stack
{
public:
// 成员函数
void Init(int n = 4);
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
// 声明和定义分离,需要指定类域
void Stack::Init(int n)//指定类域Stack::Init
{
//实现函数
}
int main()
{
Stack st;
st.Init();
return 0;
}
1. 用类这种类型在物理内存中创建对象的过程,称为类实例化出对象。 2. 类是对象的⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间。 3. ⼀个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。 4. 类实例化出对象就像使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多 少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房子才能住人。同样类就像设计图⼀样,不能存储数据,实例化出的对象才能分配物理内存,存储数据。
对象大小
1. 类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量。但是成员函数并没有保存在每个对象里面。 2. 所以计算类的大小时候,只计算成员变量占用的内存,并不计算成员函数占用的内存。 3. C++规定类实例化的对象也要符合内存对齐的规则。 4. 如果是没有成员变量的类对象,则占一个字节,表示占位,但不存储有效数据。
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
class B
{
public:
void Print()
{
//...
}
};
class C
{};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl;//8
cout << sizeof(b) << endl;//1
cout << sizeof(c) << endl;//1
return 0;
}
1. 编译器编译后,类的成员函数都会默认在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。 2. 类的成员函数访问成员变量,本质都是通过this指针访问的。 3. C++规定不能在实参和形参的位置的写this指针(编译时编译器会处理),但是可以在函数体内使用this指针。
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year,int month,int day)//本质上是void Init(Date* this,int year,int month,int day)
void Print()//本质上是void Print(Date* this)
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2024, 11, 3);//本质上是d1.Init(&d1, 2024, 11, 3)
d1.Print();//本质上是d1.Print(&d1)
Date d2;
d2.Init(2024, 11, 4);//本质上是d2.Init(&d1, 2024, 11, 4)
d2.Print();//本质上是d1.Print(&d2)
}
1. C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的⼀种体现,这个是最重要的变化。这里的封装的本质是⼀种更严格规范的管理,避免出现乱访问修改的问题。 2. C++中有⼀些相对方便的语法,比如 Init() 函数给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了很多,使用类型不再需要 typedef ,用类名就很方便。
1. 默认成员函数,就是即使用户没有写代码去实现,编译器也会自动生成的成员函数。 2. 对于⼀个类,一般情况下,编译器会生成六个默认成员函数。我们主要学习前面四个默认成员函数,对于后面两个默认成员函数只需了解即可。
1. 构造函数是一个特殊的成员函数,其主要任务并不是开空间创建对象,而是对象实例化时初始化对象。 2. 构造函数的本质是要替代我们以前写的 Init() 函数的功能,构造函数自动调用的特点就完美的替代的了Init() 函数。
1. 构造函数的函数名与类名相同。 2. 构造函数无返回值。 (如果需要自己写构造函数,那么该函数不用返回任何值,也不需要在最前面写 void ) 3. 构造函数可以重载。(无参数,有参数,缺省参数) 4. 对象实例化时,系统会自动调用对应的构造函数。 5. 如果类中没有自己定义构造函数,则C++编译器会自动生成⼀个无参的默认构造函数,⼀旦用户自己写代码实现构造函数,那么定义编译器将不再生成默认构造函数。 6. C++把类型分成内置类型和自定义类型。内置类型就是语言提供的原生数据类型,如:int、char、double、指针等,自定义类型就是我们使用class、struct等关键字自己定义的类型。 7. 对内置类型成员变量的初始化没有要求,也就是说:是否初始化是不确定的,看编译器,保险起见,此时就需要我们自己去写构造函数。 8. 对于自定义类型成员变量,就一定要求调用这个成员变量的默认构造函数初始化。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//在实例化对象d1的时候,会自动调用构造函数。
d1.Print();
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:
//Date()
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
//Date(int year = 1, int month = 1, int day = 1)
//{
// _year = year;
// _month = month;
// _day = day;
//}
Date(int year , int month , int day )
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1,1,1);//在实例化对象d1的时候,会自动调用构造函数。
d1.Print();
return 0;
}
1. 析构函数与构造函数功能相反,构造函数不是用来创建对象,析构函数也不是用来完成对象本身的销毁。 2. C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。
1. 析构函数的函数名就是在类名前加上字符 ~。 2. 析构函数无参数,无返回值。 (这里跟构造函数类似,也不需要在最前面加 void ) 3. ⼀个类只能有⼀个析构函数。若用户没有自己写代码实现,则系统会自动生成默认的析构函数。 4. 对象生命周期结束时,系统会自动调用析构函数。 5. 跟构造函数类似,编译器自动生成的析构函数,对内置类型成员不做处理,对自定类型成员会调用系统的析构函数。 6. 即使我们自己写析构函数,对于自定义类型成员也会调用系统的析构函数,也就是说自定义类型成员无论什么情况都会自动调用析构函数。 7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要自己写析构函数,如MyQueue;但是除此之外,有资源申请时,⼀定要自己写析构函数,否则会造成资源泄漏,如Stack。 8. ⼀个局部域的多个对象,C++规定后定义的对象先析构。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:
//Date()
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
//Date(int year = 1, int month = 1, int day = 1)
//{
// _year = year;
// _month = month;
// _day = day;
//}
Date(int year , int month , int day )
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
~Date()
{
cout << '1' << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1,1,1);//在实例化对象d1的时候,直接调用无参的默认构造函数。
d1.Print();
return 0;//d1的生命周期结束时,会自动销毁,此时会自动调用析构函数,清理内存资源空间
}
1. 如果⼀个构造函数的第⼀个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是⼀个特殊的构造函数。
1. 拷贝构造函数是构造函数的⼀个重载。 2. 拷贝构造函数的第⼀个参数必须是自身类类型对象的引用,使用传值传参方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。 3. 拷贝构造函数也可以多个参数,但是第⼀个参数必须是自身类类型对象的引用,后面的参数必须有缺省值。 4. C++规定自定义类型对象进行拷贝行为时,必须调用拷贝构造函数,所以如果自定义类型出现传值传参和传值返回,则都会调用拷贝构造完成。 5. 若用户没有自己写代码定义拷贝构造函数,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝、浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造函数。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:
//Date()
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
//Date(int year = 1, int month = 1, int day = 1)
//{
// _year = year;
// _month = month;
// _day = day;
//}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
~Date()
{
cout << '1' << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1, 1, 1);//在实例化对象d1的时候,直接调用无参的默认构造函数。
//d1.Print();
Date d2(d1);//构造函数存在重载,由于参数不同,故系统通过参数来判需要自动断调用的是哪一个构造函数。
Date d3 = d1;//拷贝构造函数的另一种调用形式。
d2.Print();
return 0;//d1的生命周期结束时,会自动销毁,此时会自动调用析构函数,清理内存资源空间
}
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!