文章目录
1.类的三种访问限定符
public、protected、private
2.实例化类的对象与访问对象成员
定义一个TV类:
class TV{
public:
char name[20];
int type;
void changeVol();
void power();
}
从栈中实例化:
int main(void){
TV tv;
TV tv[20];
return 0;
}
从堆中实例化:
int main(void){
TV *p = new TV();
TV *q = new TV[20];
// todo(如果从堆中,一定要手动释放)
delete p;
delete []q;
p = nullptr;
q = nullptr;
return 0;
}
注意:要区分是栈还是堆也很简单,堆我们需要手动分配、释放内存(new、delete)。
访问对象成员(栈):
int main(void){
TV tv;
tv.type = 0;
return 0;
}
访问对象成员(堆):
int main(void){
TV *p = new TV();
p->type = 0;
p->changeVol();
delete p;
p = nullptr;
return 0;
}
访问对象成员(数组):
int main(void){
TV *p = new TV[5];
for(int i = 0; i < 5; i++){
p[i]->type = 0;
p[i]->changeVol();
}
delete []p;
p = nullptr;
return 0;
}
3.String字符串的使用
常用方法:
s.empty()
s.size()
s1+s2
字符串连接:
string s1 = "hello";
string s2("world");
string s3 = s1 + s2;
string s4 = "hello" + s2;
string s5 = "hello" + s2 + "world";
// 错误
string s6 = "hello" + "world";
注意: 字符串连接,只能用于“字符串+变量”,不能“字符串+字符串”
4.new和delete运算符-内存的创建与释放
new/delete 是C++的运算符;类似于malloc/free,程序运行(动态)得开辟内存空间(堆);
new 可以为内置类型的变量开辟空间,数组变量,类的对象开辟空间。
这是在堆上开辟内存,返回一个指向该内存空间的地址。
new/delete会调用类的构造函数和析构函数(malloc不会)。
5.在类的外部定义类函数
注意:类内定义的函数优先选择编译为内联函数(inline)。
class Car{
public:
void run();
void stop();
void changeSpeed();
};
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}
如果需要 分文件 进行定义:
car.h
// 存放类的声明
class Car{
public:
void run();
void stop();
void changeSpeed();
};
car.cpp
// 存放类的定义,切记要包含(include)头文件
void Car::run(){}
void Car::stop(){}
void Car::changeSpeed(){}
6.内存区块与代码之间的联系
区 | |
---|---|
栈区 | int x = 0; int *p = nullptr; |
堆区 | int *p = new int[20]; |
全局区 | 存储全局变量及静态变量 |
常量区 | string str = “hello”; |
代码区 | 存储逻辑代码的二进制 |
7.构造函数
构造函数一定是没有返回值的。在参数上,如果没有参数,我们都称之其为默认构造函数。
如果是默认构造函数(不带参数的构造函数),在实例化的时候,有如下几种写法:
// 在堆中创建
Coordinate *p1 = new Coordinate();
Coordinate *p2 = new Coordinate;
// 在栈中创建
Coordinate p2;
如果在堆中进行创建,那我们也需要手动进行释放,代码如下:
delete p1;
p1 = nullptr;// 不是必要,只是防止delete失败,所以这里再将指针置为安全状态
// 如果是类数组
delete[] p;
p1 = nullptr;
8.构造函数-初始化列表
初始化列表是连接到构造函数名后面的,初始化列表和构造函数一样能够初始化类中的属性,但是对于const修饰的常量,只能用 初始化列表 进行初始化。即如果需要初始化,必须写到 初始化列表 当中。
- 初始化列表先于构造函数执行
- 初始化列表,只能用于构造函数
- 初始化列表可以同时初始化多个数据成员
例子1,对const修饰的常量赋初值:
// 错误,采用构造函数
class Circle{
public:
Circle():{m_dPi=3.14}
private:
const double m_dPi;
};
// 正确,采用初始化列表
class Circle{
public:
Circle():m_dPi(3.14){}
private:
const double m_dPi;
};
例2,多参数赋值:
// ====== 类声明START ======
class Teacher{
public:
// 构造函数(没有返回值),这里设定了默认参数
Teacher(string name = "Jim",int age = 1,int max = 50);
// 操作类的属性
void setName(string name);
string getName() const;
void setAge(int age);
int getAge();
private:
// 属性
string m_strName; // 名字
int m_iAge; // 年龄
};
// ====== 类声明END ======
// ====== 类实现START ======
// 构造函数(在初始化列表中,初始化属性)
Teacher::Teacher(string name,int age,int max):m_strName(name),m_iAge(age){
cout << "This is the constructor!" << endl;
}
// 操作类的属性
void Teacher::setName(string name) {m_strName = name;}
string Teacher::getName() const {return m_strName;}
void Teacher::setAge(int age) {m_iAge = age;}
int Teacher::getAge() {return m_iAge;}
// ====== 类实现END ======
int main(int argc,char *argv[]) {
Teacher tea;
cout << "Name:" << tea.getName() << endl;
cout << "Age:" <<tea.getAge() << endl;
return 0;
}
初始化列表的成员初始化顺序:
C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
// 构造函数的声明
class CMyClass {
CMyClass(int x, int y);
int m_x;
int m_y;
};
// 构造函数的实现
CMyClass::CMyClass(int x, int y) : m_y(y), m_x(m_y){
}
你可能以为上面的代码将会首先做m_y=y
,然后做m_x=m_y
,最后m_x
、m_y
有相同的值。
但是编译器先初始化m_x
,然后是m_y
,因为它们是按这样的顺序 声明 的。
结果是m_x将有一个不可预测的值。
有两种方法避免它,一个是总是按照你希望它们被初始化的顺序声明成员;第二个是,如果你决定使用初始化列表,总是按照它们 声明的顺序 罗列这些成员。这将有助于消除混淆。
9.拷贝构造函数
格式:类名(const类名&变量名)
,变量名可写可不写。
拷贝构造函数和构造函数一样,也可以在函数名后面添加初始化列表,如果没有去实现它,系统也会自动生成一个默认的。
拷贝构造函数的参数是确定的,不能重载。
在下面的3中情况下,系统自动调用 拷贝构造函数 ,此时不会调用 构造函数 !
- 一个对象作为函数参数,以值传递的方式传入函数体;
- 一个对象作为函数返回值,以值传递的方式从函数返回;
- 一个对象用于给另外一个对象进行初始化(常称为赋值初始化);
例1,使用 赋值初始化 来创建新的对象 :
class stu{
public:
// 构造函数
stu(){
cout << "stu" << endl;
}
// 拷贝构造函数
stu(const stu& stu01){
cout << "stu01" << endl;
}
};
int main(int argc,char *argv[]){
stu stu01; // 调用构造函数
stu stu02 = stu01; // 调用拷贝构造函数
stu stu03(stu01); // 调用拷贝构造函数
return 0;
}
/* 输出:
stu
stu01
stu01
*/
这里创建了3个实例,但是实际上只有第一次调用了构造函数,之后两次调用的都是拷贝构造函数。
例2,当类作为 函数参数 传递时,将触发拷贝构造函数:
class stu{
public:
// 构造函数
stu(){
cout << "stu" << endl;
}
// 拷贝构造函数
stu(const stu&){
cout << "stu01" << endl;
}
};
// 当作为函数参数传递时
void test(stu t){
}
int main(int argc,char *argv[]){
stu stu01; // 调用构造函数
test(stu01); // 调用拷贝构造函数
return 0;
}
/* 输出:
stu
stu01
*/
例3,当对象作为 返回值 时,触发拷贝构造函数:
// 请看笔记中的封装篇(下)中的第3章:对象指针
// 其中有相关的实例代码
拷贝构造函数中,如何获取引用&的值、如何手写拷贝构造函数:
在本例中,部分类的属性操作方法的后面添加const
,将其变为const成员函数(常对象函数),以便我们在拷贝构造函数中使用 &
引用 来调用他们获取数据。
// ====== 类声明START ======
class Teacher{
public:
// 构造函数(没有返回值),这里设定了默认参数
Teacher(string name = "Jim",int max = 50);
// 拷贝构造函数
Teacher(const Teacher& tea);
// 操作类的属性
void setName(string name);
string getName() const;
int getMax() const;
private:
// 属性
string m_strName; // 名字
const int m_iMax; // 最多带的学生数
};
// ====== 类声明END ======
// ====== 类实现START ======
// 构造函数(在初始化列表中,初始化属性)
Teacher::Teacher(string name,int max):m_strName(name),m_iMax(max){
cout << "02" << endl;
}
// 拷贝构造函数(在初始化列表中,初始化属性)
Teacher::Teacher(const Teacher &teaRaw):m_strName(teaRaw.getName()),m_iMax(teaRaw.getMax()) {
cout << "03" << endl;
cout << "teaRaw.getName() = " << teaRaw.getName() << endl;
cout << "teaRaw.getMax() = " << teaRaw.getMax() << endl;
}
// 操作类的属性
// name
void Teacher::setName(string name) {m_strName = name;}
string Teacher::getName() const {return m_strName;}
// max
int Teacher::getMax() const {return m_iMax;}
// ====== 类实现END ======
int main(int argc,char *argv[]) {
// 将调用构造函数
Teacher tea;
cout << "Name:" << tea.getName() << endl;
cout << "Max:" << tea.getMax() << endl;
// 将调用拷贝构造函数(在这一步中,将展示如何获取引用&的值)
Teacher tea01 = tea;
cout << "Name:" << tea01.getName() << endl;
cout << "Max:" << tea01.getMax() << endl;
return 0;
}
/* 输出:
02
Name:Jim
Max:50
03
teaRaw.getName() = Jim
teaRaw.getMax() = 50
Name:Jim
Max:50
*/
这样我们就能够在 拷贝构造函数 中获取到tea
这个对象的值了。
这里需要再次强调,如果在类的属性由const修饰,意味着我们只能在 初始化 的时候为其赋值,其它的时刻任何赋值操作都会报错,所以我们不能在构造函数/拷贝构造函数的{ }
为其赋值,只能在 初始化列表 中赋值,且之后再无法修改。
注意: 关于const 成员函数的声明,const 关键字只能放在函数声明的尾部,看起来确实是怪怪的。
10.析构函数
格式:~类名()
,不能加任何参数。
成员函数除了析构函数之外,都可以有重载。
本篇为视频教程笔记,视频如下: