C++ 自学
前言
C++ 来源于生活,每个对象都会有初始设置以及销毁前的清除数据操作
对象的初始化和清理也是二个非常重要的安全问题
一个对象或变量没有初始状态,对其使用结果是未知的;使用完变量或对象后,没有及时清理也会造成一定的安全隐患
构造函数和析构函数
C++ 利用构造函数和析构函数解决了上述问题,这两个函数将会被编译器自动调用,完成对象的初始化和清理工作
构造函数
构造函数:主要作用在于创建对象时为对象的属性赋值(构造函数由编译器自动调用)
构造函数语法:类名() { 代码体 }
构造函数特点:没有返回值也不用写 void;函数名称与类名称一致;构造函数可以有参数,因此可以发生重载;程序在调用对象时会自动执行一次构造函数的调用
析构函数
析构函数:主要作用在于对象销毁前系统调用,执行一些清理操作
析构函数语法:~类名() { 代码体 }
构造函数特点:没有返回值也不用写 void;函数名称与类名称一致,还需要在前面加上 ~;构造函数不可以有参数,因此不可以发生重载;程序会在对象销毁前调用一次析构函数
类中构造函数和析构函数的实际调用过程
#include <iostream>
using namespace std;
class Student {
public:
Student() {
cout << "构造函数的调用" << endl;
}
~Student() {
cout << "析构函数的调用" << endl;
}
};
void func() {
// 局部变量,函数执行完后自动释放内存
Student s1;
}
int main() {
func();
// output:构造函数的调用
// 析构函数的调用
system("pause");
return 0;
}
构造函数的分类以及调用
构造函数的分类:有参构造函数和无参构造函数(按参数)普通构造函数和拷贝构造函数(按类型)
重点:理解拷贝构造函数的参数;如下构造函数的调用属于括号法
#include <iostream>
using namespace std;
class Student {
public:
// 无参构造函数
Student() {
cout << "无参构造函数的调用" << endl;
}
// 有参构造函数
Student(int a) {
cout << "有参构造函数的调用" << endl;
}
// 拷贝构造函数
Student(const Student &s) {
cout << "拷贝构造函数的调用" << endl;
}
};
int main() {
Student s1; // 无参构造函数的调用
Student s2(2); // 有参构造函数的调用
Student s3(s1); // 拷贝构造函数的调用
system("pause");
return 0;
}
构造函数的调用方式:括号法;显示法;隐式转换法
#include <iostream>
using namespace std;
class Student {
public:
// 无参构造函数
Student() {
cout << "无参构造函数的调用" << endl;
}
// 有参构造函数
Student(int a) {
cout << "有参构造函数的调用" << endl;
}
// 拷贝构造函数
Student(const Student &s) {
cout << "拷贝构造函数的调用" << endl;
}
};
int main() {
// 1. 括号法
Student s1; // 无参构造函数的调用
Student s2(2); // 有参构造函数的调用
Student s3(s1); // 拷贝构造函数的调用
// 2. 显示法
Student s4; // 无参构造函数的调用
Student s5 = Student(2); // 有参构造函数的调用
Student s6 = Student(s4); // 拷贝构造函数的调用
// 3. 隐式转换法
Student s7; // 无参构造函数的调用
Student s8 = 2; // 有参构造函数的调用
Student s9 = s7; // 拷贝构造函数的调用
system("pause");
return 0;
}
拷贝构造函数调用时机
C++ 中拷贝构造函数调用时机通常有三种情况(如下)
第一种:使用一个已经初始化完毕的对象来初始化一个新对象
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数
// 无参构造函数
Student() {
cout << "无参构造函数的调用" << endl;
}
// 有参构造函数
Student(int a) {
cout << "有参构造函数的调用" << endl;
}
// 拷贝构造函数
Student(const Student &s) {
cout << "拷贝构造函数的调用" << endl;
}
// 析构函数
~Student() {
cout << "析构函数的调用" << endl;
}
};
void func() {
// 实例化对象 s1
Student s1;
// 第一种情况:使用已经初始化好的对象来初始化新对象(此过程调用拷贝构造函数)
Student s2(s1);
}
int main() {
func();
// outputs:无参构造函数的调用
// 拷贝构造函数的调用
// 析构函数的调用
// 析构函数的调用
system("pause");
return 0;
}
第二种:值传递的方式给函数参数传值
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数
// 无参构造函数
Student() {
cout << "无参构造函数的调用" << endl;
}
// 有参构造函数
Student(int id) {
cout << "有参构造函数的调用" << endl;
}
// 拷贝构造函数
Student(const Student &s) {
m_id = s.m_id;
cout << "拷贝构造函数的调用" << endl;
}
// 析构函数
~Student() {
cout << "析构函数的调用" << endl;
}
public:
// 设置 id 的函数
void setId(int id) {
m_id = id;
}
// 打印 id 的函数
int getId() {
return m_id;
}
private:
int m_id;
};
void work(Student s) {
// 打印 s 的 id
cout << s.getId() << endl;
}
void func() {
// 实例化对象 s1
Student s1;
s1.setId(1); // id 为 1
cout << s1.getId() << endl;
// 将对象 s1 作为值给函数 work 传参
work(s1);
}
int main() {
func();
// outputs:有参构造函数的调用
// 1
// 拷贝构造函数的调用
// 1
// 析构函数的调用
// 析构函数的调用
system("pause");
return 0;
}
第三种:以值方式返回局部对象
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数
// 无参构造函数
Student() {
cout << "无参构造函数的调用" << endl;
}
// 有参构造函数
Student(int id) {
cout << "有参构造函数的调用" << endl;
}
// 拷贝构造函数
Student(const Student &s) {
m_id = s.m_id;
cout << "拷贝构造函数的调用" << endl;
}
// 析构函数
~Student() {
cout << "析构函数的调用" << endl;
}
public:
// 设置 id 的函数
void setId(int id) {
m_id = id;
}
// 打印 id 的函数
int getId() {
return m_id;
}
private:
int m_id;
};
// 以值方式返回局部对象
Student work() {
Student s;
s.setId(2); // id 为 2
cout << s.getId() << endl;
return s;
}
void func() {
Student s1 = work(); // 调用拷贝构造函数
cout << s1.getId() << endl;
}
int main() {
func();
// outputs:无参构造函数的调用
// 2
// 拷贝构造函数的调用
// 析构函数的调用
// 2
// 析构函数的调用
system("pause");
return 0;
}
构造函数调用规则
默认情况下,C++ 编译器至少给一个类添加 3 个函数
第一种:默认构造函数(无参且函数体为空)
class Student {
public:
Student() {
pass
}
第二种:默认析构函数(无参且函数体为空)
class Student {
public:
~Student() {
pass
}
第三种:默认拷贝构造函数(对属性进行浅拷贝)
class Student {
public:
Student(const Student &s) {
属性值 = s.属性值;
}
构造函数调用规则如下:
规则 1:如果用户自己定义了有参构造函数,C++ 不再提供默认无参构造函数,但是会提供默认拷贝函数
#include <iostream>
using namespace std;
class Student {
public:
// 有参构造函数
Student(int id) {
m_id = id;
cout << "有参构造函数的调用" << endl;
}
public:
// 打印 id 的函数
int getId() {
return m_id;
}
private:
int m_id;
};
void func() {
// 调用有参构造函数成功!
Student s1(1); // id 为 1
// 调用无参构造函数失败!
// Student s2; // Error!
// 调用拷贝构造函数成功!
Student s3(s1);
cout << s3.getId() << endl;
}
int main() {
func();
// outputs:有参构造函数的调用
// 1
system("pause");
return 0;
}
规则 2:如果用户定义了拷贝构造函数,C++ 不会再提供其它构造函数
#include <iostream>
using namespace std;
class Student {
public:
// 拷贝构造函数
Student(const Student &s) {
m_id = s.getId();
cout << "有参构造函数的调用" << endl;
}
public:
// 设置 id 的函数
void setId(int id) {
m_id = id;
}
// 打印 id 的函数
int getId() {
return m_id;
}
private:
int m_id;
};
void func() {
// 由于没有无参构造和有参构造函数,故下面代码出错!
// Student s1; // Error!
// Student s2(1); // Error!
}
int main() {
func();
// outputs:有参构造函数的调用
// 1
system("pause");
return 0;
}
深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数(默认有无参和拷贝 -- 简单的值拷贝)
// 析构函数
~Student() {
// 将堆区开辟的内存清空
if (m_height != NULL) {
delete m_height;
m_height = NULL;
}
}
public:
// 设置 id,height 的函数
void setIdAndHeight(int id, int height) {
m_height = new int(height); // 从堆区开辟内存,返回值为指针
m_id = id;
}
// 获取 m_height
int* getHeight() {
return m_height;
}
private:
int m_id;
int *m_height;
};
// 在函数调用结束后,s1 和 s2 都调用了析构函数,都进行了 delete m_height;
// 然而对同一段内存空间进行二次清空是非法的
void func() {
Student s1;
s1.setIdAndHeight(1, 178);
cout << *s1.getHeight() << endl; // 178
// 拷贝构造函数创建一个新对象
// Student s2(s1); // 简单的拷贝 -- m_height 的地址也是一样的
}
int main() {
func();
// outputs:有参构造函数的调用
// 1
system("pause");
return 0;
}
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数(默认有无参和拷贝 -- 深拷贝)
// 重写拷贝构造函数
Student() {
cout << "无参构造函数调用" << endl;
}
Student(const Student &s) {
m_id = s.m_id;
m_height = new int(*s.m_height); // 重新申请内存空间
}
// 析构函数
~Student() {
// 将堆区开辟的内存清空
if (m_height != NULL) {
delete m_height;
m_height = NULL;
}
}
public:
// 设置 id,height 的函数
void setIdAndHeight(int id, int height) {
m_height = new int(height); // 从堆区开辟内存,返回值为指针
m_id = id;
}
// 获取 m_height
int* getHeight() {
return m_height;
}
private:
int m_id;
int *m_height;
};
// 在函数调用结束后,s1 和 s2 都调用了析构函数,都进行了 delete m_height;
// 然而对同一段内存空间进行二次清空是非法的
void func() {
Student s1;
s1.setIdAndHeight(1, 178);
cout << (int)s1.getHeight() << endl;
// 拷贝构造函数创建一个新对象
Student s2(s1); // 深拷贝 -- 内存空间不一样
cout << (int)s2.getHeight() << endl;
}
int main() {
func();
// outputs:无参构造函数的调用
// 7734624
// 7793792
system("pause");
return 0;
}
初始化列表
语法:构造函数() :属性 1(值 1), 属性 2(值 2)… { }
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数用于初始化列表
Student(int id, int score) :m_id(id), m_score(score) {
cout << "有参构造函数调用" << endl;
}
// 析构函数
~Student() {
cout << "析构函数调用" << endl;
}
public:
void printMessage() {
cout << "学号:" << m_id << " " << "分数:" << m_score << endl;
}
private:
int m_id;
int m_score;
};
void func() {
Student s1(1,100);
s1.printMessage();
}
int main() {
func();
// outputs:无参构造函数调用
// 学号:1 分数:100
// 析构函数调用
system("pause");
return 0;
}
在本质上和如下初始化方法一致
#include <iostream>
using namespace std;
class Student {
public:
// 构造函数用于初始化列表
Student(int id, int score) {
m_id = id;
m_score = score;
cout << "有参构造函数调用" << endl;
}
// 析构函数
~Student() {
cout << "析构函数调用" << endl;
}
public:
void printMessage() {
cout << "学号:" << m_id << " " << "分数:" << m_score << endl;
}
private:
int m_id;
int m_score;
};
void func() {
Student s1(1,100);
s1.printMessage();
}
int main() {
func();
// outputs:无参构造函数调用
// 学号:1 分数:100
// 析构函数调用
system("pause");
return 0;
}
类对象作为类成员
C++ 类中的成员可以是另一个类的的对象,我们称为对象成员
class A {}
class B {
A a;
}
B 类中有 a 作为成员,a 为对象成员
实例:学生类和手机类(手机被学生所拥有)
#include <iostream>
using namespace std;
#include <string>
class Phone {
public:
Phone(string name){
p_name = name;
}
// 获取手机信息
string getPhone() {
return p_name;
}
private:
string p_name;
};
class Student {
public:
// 构造函数用于初始化列表
Student(string name, string phone) :m_name(name), m_phone(phone) {
cout << "有参构造函数调用" << endl;
}
// 析构函数
~Student() {
cout << "析构函数调用" << endl;
}
// 打印学生的信息
void printMessage() {
cout << m_name << "同学有" << m_phone.getPhone() << endl;
}
private:
string m_name;
Phone m_phone;
};
void func() {
// 初始化学生对象
Student s("苏苏", "HuaWei");
s.printMessage();
}
int main() {
func();
// outputs:有参构造函数调用
// 苏苏同学有HuaWei
// 析构函数
system("pause");
return 0;
}
静态成员和静态成员函数
静态成员就是在成员变量和成员函数前加一个 static
静态成员变量特点:所有对象共享同一份数据;在编译阶段分配数据;类内声明,类外初始化
静态成员函数特点:所有对象共享同一份数据;静态成员函数只能访问静态成员变量
重点 1:验证所有对象共享静态成员变量
#include <iostream>
using namespace std;
#include <string>
class Student {
public:
// 类内声明
static string m_name;
};
// 类外初始化
string Student::m_name = "苏苏";
void func() {
Student s1;
cout << s1.m_name << endl;
// 创建一个新对象 s2,来验证所有的对象都共享这个数据
Student s2;
s2.m_name = "娜娜";
cout << s1.m_name << endl;
}
int main() {
func();
// outputs:苏苏
// 娜娜
system("pause");
return 0;
}
重点 2:验证静态成员函数只能访问静态成员变量
#include <iostream>
using namespace std;
#include <string>
class Student {
public:
// 类内声明
static string m_name;
// 静态成员函数
static void changeMessage() {
string name;
cin >> name;
m_name = name;
}
};
// 类外初始化
string Student::m_name = "苏苏";
void func() {
Student s1;
cout << s1.m_name << endl;
s1.changeMessage();
cout << s1.m_name << endl;
}
int main() {
func();
// outputs:苏苏
// 柯柯
system("pause");
return 0;
}
重点 3:静态成员变量和函数都可以由类名直接访问
#include <iostream>
using namespace std;
#include <string>
class Student {
public:
// 类内声明
static string m_name;
// 静态成员函数
static void changeMessage() {
string name;
cin >> name;
m_name = name;
}
};
// 类外初始化
string Student::m_name = "苏苏";
void func() {
cout << Student::m_name << endl;
Student::changeMessage();
cout << Student::m_name << endl;
}
int main() {
func();
// outputs:苏苏
// 柯柯
system("pause");
return 0;
}
重点 4:类外访问不到私有静态成员变量(同理也访问不到静态成员函数)
#include <iostream>
using namespace std;
#include <string>
class Student {
public:
// 类内声明
static string m_name;
// 静态成员函数
static void changeMessage() {
string name;
cin >> name;
m_name = name;
}
private:
int m_id;
};
// 类外初始化
string Student::m_name = "苏苏";
int Student::m_id = 1;
void func() {
cout << Student::m_name << endl;
Student::changeMessage();
cout << Student::m_name << endl;
// 访问私有静态成员变量
// cout << Student::m_id << endl; // Error!
}
int main() {
func();
// outputs:苏苏
// 柯柯
system("pause");
return 0;
}
成员变量和成员函数存储位置
在 C++ 中类内的成员变量和成员函数是分开存储的(只有非静态成员变量才是类的对象)
重点问题:空对象占用内存为 1 的原因是为了区分空对象所占内存的位置
#include <iostream>
using namespace std;
#include <string>
// 空类
class Student1 {
};
// 非静态成员变量
class Student2 {
int m_id;
};
// 静态成员变量
class Student3 {
static int m_id;
};
// 静态成员函数
class Student4 {
static int func() {
}
};
// 非静态成员函数
class Student5 {
int func() {
}
};
void func() {
cout << "空 Student1 占用空间为:" << sizeof(Student1) << endl;
cout << "Student2 占用空间为:" << sizeof(Student2) << endl;
cout << "Student3 占用空间为:" << sizeof(Student3) << endl;
cout << "Student4 占用空间为:" << sizeof(Student4) << endl;
cout << "Student5 占用空间为:" << sizeof(Student5) << endl;
}
int main() {
func();
// outputs:空 Student1 占用空间为:1
// 空 Student1 占用空间为:4
// 空 Student1 占用空间为:1
// 空 Student1 占用空间为:1
// 空 Student1 占用空间为:1
system("pause");
return 0;
}
this 指针概念
用途:当形参和成员变量名同名时,可用 this 指针来区分;在类的非静态成员函数中返回对象本身可用 ( 星 ) this
案例 1:区分形参和成员变量名
#include <iostream>
using namespace std;
#include <string>
// 空类
class Student {
public:
void setId(int m_id) {
this->m_id = m_id; // this->m_id 表示调用该函数的对象,也就是 s1->m_id = id;
}
int getId() {
return m_id;
}
private:
int m_id;
};
void func() {
Student s1;
s1.setId(1);
cout << s1.getId() << endl;
}
int main() {
func();
// output:1
system("pause");
return 0;
}
案例 2:对象年龄的加法问题
#include <iostream>
using namespace std;
#include <string>
// 空类
class Student {
public:
void setYear(int m_age) {
this->m_age = m_age;
}
int getYear() {
return m_age;
}
Student& addyear(Student &s) {
this->m_age += s.getYear();
return *this;
}
private:
int m_age;
};
void func() {
Student s1, s2;
s1.setYear(10);
cout << s1.getYear() << endl; // 10
s2.setYear(5);
s1.addyear(s2);
cout << s1.getYear() << endl; // 15
s1.addyear(s2).addyear(s2).addyear(s2);
cout << s1.getYear() << endl; // 30
}
int main() {
func();
system("pause");
return 0;
}
空指针访问成员函数
C++ 中的空指针也可以调用成员函数,但是也得注意函数体内部是否用到了 this 指针
#include <iostream>
using namespace std;
class Student {
public:
void printMessage() {
cout << "你是猪" << endl;
}
void changeMessage() {
this->m_id = 1; // 很明显 this->m_id 即 s->m_id 而 s 指向的为空
}
private:
int m_id;
};
void func() {
// 初始化学生对象,使其指向空内存
Student *s = NULL;
s->printMessage(); // 你是猪
// s->changeMessage(); // Error!
}
int main() {
func();
system("pause");
return 0;
}
const 修饰成员函数
常函数
成员函数后加 const 我们称之为常函数
常函数内不可修改成员属性
成员属性声明时若加了关键字 mutable,在常函数中就可以修改了
#include <iostream>
using namespace std;
class Student {
public:
Student(int id) {
m_id = id;
}
// 常函数不能修改成员属性
//void changeId() const {
// int id;
// cin >> id;
// m_id = id;
//}
// 常函数不能修改成员属性,但是如果在属性前面加上关键字 mutable,那么就可以在常函数中修改了
void changeScore() const {
int score;
cin >> score;
m_score = score;
}
void printMessage() {
cout << "学号" << m_id << "的成绩为:" << m_score << endl;
}
private:
int m_id;
mutable int m_score;
};
void func() {
Student s1(1); // id 为 1
// s1.changeId(); // Error!
s1.changeScore();
s1.printMessage(); // 学号1的成绩为:100
}
int main() {
func();
system("pause");
return 0;
}
常对象
声明对象前加 const 我们称之为常对象
常对象只能调用常函数和用关键字 mutable 修饰的成员属性
#include <iostream>
using namespace std;
class Student {
public:
Student(int id) {
m_id = id;
}
void changeScore() const {
int score;
cin >> score;
m_score = score;
}
void printMessage() {
cout << "学号" << m_id << "的成绩为:" << m_score << endl;
}
private:
int m_id;
mutable int m_score;
};
void func() {
// 常对象调用常成员函数
const Student s1(1); // id 为 1
s1.changeScore();
// 常对象调用非常成员函数
// s1.printMessage(); // Error!
}
int main() {
func();
system("pause");
return 0;
}