目录
一、面向对象
要论C和C++最本质的区别,那就是C是面向过程的语言,而C++是面向对象的语言了吧,虽然我现在不敢说完全掌握面向对象,不过可以说是略懂一二,所以说一下我的理解吧:我们把现实世界的物质运动进行抽象,得到了物理学,物理学中有质量、速度等概念,但现实世界中并没有质量、速度的实体,这是我们人类为了认识世界抽象出来的东西,同样,我们也可以把计算机世界的对象进行抽象,于是我们就得到了类,类中有各种成员,我们对类进行实例化,就可以在计算机世界得到对象!
二、类的大小
空类的大小为1,成员函数不参与计算,类的大小遵循内存对齐。
#include <iostream>
using namespace std;
//空类
class Empty
{};
//只有成员函数的类
class OnlyFunc
{
public:
void Print1()
{
cout << "hello class" << endl;
}
void Print2()
{
cout << "hello violet" << endl;
}
};
//既有成员变量,也有成员函数
class All
{
public:
void Print()
{
cout << "hello class" << endl;
}
private:
char c;
int i;
};
int main()
{
cout << "sizeof(Empty) = " << sizeof(Empty) << endl;
cout << "sizeof(OnlyFunc) = " << sizeof(OnlyFunc) << endl;
cout << "sizeof(All) = " << sizeof(All) << endl;
//输出为:
//sizeof(Empty) = 1
//sizeof(OnlyFunc) = 1
//sizeof(All) = 8
return 0;
}
三、特殊成员函数
四、构造函数
1.内置类型问题
内置类型(char、int、各类指针...)的成员变量不会调用编译器自动生成的构造函数,自定义类型(struct、class...)的成员变量才会调用编译器自动生成的构造函数。
#include <iostream>
using namespace std;
class X
{
public:
X(int x = 0)
{
_x = x;
cout << _x << endl;
}
private:
int _x;
};
class Y
{
public:
void Print()
{
cout << _y << endl;
}
private:
int _y;//内置类型
X _x;//自定义类型
//C++11
//int _y = 0;
};
int main()
{
Y y;
y.Print();
return 0;
}
y.Print();会输出一个不能确定的值,因为y._y没有调用编译器自动生成的构造函数,也就没有初始化。为了解决这个问题,C++11中可以在内置类型的成员变量声明的时候给定一个默认值。
2.必须使用初始化列表初始化的成员变量
在不考虑C++11给定默认值的情况下,必须使用初始化列表初始化的成员变量有:const修饰的成员变量、引用成员变量。这些变量的特点是它们只有一次初始化的机会,那就是在它们定义的时候。
#include <iostream>
using namespace std;
class X
{
public:
X(const char c, int& x)
//初始化列表,以:开始,之后每个变量用,隔开
:_c(c)
, _x(x)
{
cout << _c << endl;
cout << &_x << endl;
}
private:
const char _c;//const修饰的成员变量
int& _x;//引用成员变量
};
int main()
{
int temp = 0;
X x('!', temp);
cout << &temp << endl;
return 0;
}
3.初始化列表的执行顺序
谁先声明谁先初始化,与初始化列表的顺序无关。
#include <iostream>
using namespace std;
class X
{
public:
X()
//谁先声明谁先初始化,与初始化列表的顺序无关
//这里先初始化_a,再初始化_b
//而初始化_a的时候_b还没被初始化,所以导致_a的值不能确定
:_b(1)
, _a(_b)
{
cout << _a << endl;
cout << _b << endl;
}
private:
int _a;
int _b;
};
int main()
{
X x;
return 0;
}
4.explicit禁止隐式类型转换
explicit:指定构造函数为显式,即它不能用于隐式转换和复制初始化。
#include <iostream>
using namespace std;
class X
{
public:
//用explicit修饰构造函数即可禁止隐式类型转换
//explicit X(int x = 0)
X(int x = 0)
:_x(x)
{
cout << "构造函数" << endl;
}
X(const X& x)
{
this->_x = x._x;
cout << "拷贝构造" << endl;
}
private:
int _x;
};
int main()
{
//构造函数
X x1(1);
//隐式类型转换,会先生成一个X类型的临时变量,再调用拷贝构造
//经过编译器优化,会直接调用构造函数
X x2 = 1;
//证明临时变量存在
//X& rx = 1;//会报错,因为临时变量具有常性
const X& rx = 1;//不会报错,且这个临时变量会调用构造函数
return 0;
}
C++官方文档对explicit的解释(explicit 说明符 - cppreference.com):
五、析构函数
1.调用析构函数的时间点
作用域结束和使用delete表达式会调用析构函数,同一作用域,后实例化的对象先调用析构函数。
#include <iostream>
struct A
{
int i;
A(int num) : i(num)
{
std::cout << "构造 a" << i << '\n';
}
~A()
{
std::cout << "析构 a" << i << '\n';
}
};
A a0(0);
int main()
{
A a1(1);
A a2(2);
A* p;
{ // 嵌套的作用域
A a3(3);
p = new A(4);
} // a2 离开作用域
delete p; // 调用 a3 的析构函数
return 0;
}
//输出为:
//构造 a0
//构造 a1
//构造 a2
//构造 a3
//构造 a4
//析构 a3
//析构 a4
//析构 a2
//析构 a1
//析构 a0
2.内存泄漏
如果类中有申请内存,则一定要写析构函数,否则会造成内存泄漏,比如Stack类:
class Stack
{
public:
Stack(int capacity = 8)
{
_arr = new int[capacity];
_size = 0;
_capacity = capacity;
}
~Stack()
{
delete[] _arr;
_arr = nullptr;
_size = 0;
_capacity = 0;
}
private:
int* _arr;
int _size;
int _capacity;
};
int main()
{
Stack s;
return 0;
}
六、复制构造函数(拷贝构造函数)
深拷贝与浅拷贝
编译器默认生成的复制构造函数只会进行浅拷贝,而像Stack类需要把堆上的空间也拷贝下来,则需要深拷贝。
#include <string.h>
class Stack
{
public:
Stack(int capacity = 8)
{
_arr = new int[capacity];
_size = 0;
_capacity = capacity;
}
~Stack()
{
delete[] _arr;
_arr = nullptr;
_size = 0;
_capacity = 0;
}
//复制构造函数
Stack(const Stack& stack)
{
//深拷贝
_arr = new int[stack._capacity];
memmove(_arr, stack._arr, stack._size * sizeof(int));
//浅拷贝
_size = stack._size;
_capacity = stack._capacity;
}
private:
int* _arr;
int _size;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
七、复制赋值运算符(赋值运算符重载)
#include <iostream>
#include <string.h>
using namespace std;
class Stack
{
public:
Stack(int capacity = 8)
{
_arr = new int[capacity];
_size = 0;
_capacity = capacity;
}
~Stack()
{
delete[] _arr;
_arr = nullptr;
_size = 0;
_capacity = 0;
}
Stack(const Stack& stack)
{
//深拷贝
_arr = new int[stack._capacity];
memmove(_arr, stack._arr, stack._size * sizeof(int));
//浅拷贝
_size = stack._size;
_capacity = stack._capacity;
}
void Push(int x)
{
//CheckCapacity();
_arr[_size] = x;
++_size;
}
void Print()
{
for (int i = 0; i < _size; ++i)
{
cout << _arr[i];
}
cout << endl;
}
Stack& operator=(const Stack& stack);
private:
int* _arr;
int _size;
int _capacity;
};
Stack& Stack::operator=(const Stack& stack)
{
if (this != &stack)//非自赋值
{
if (_capacity < stack._capacity)
{
cout << "须扩容" << endl;
}
else
{
memmove(_arr, stack._arr, stack._size * sizeof(int));
_size = stack._size;
_capacity = stack._capacity;
}
}
return *this;
}
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Print();
Stack s2;
s2 = s1;
s2.Print();
return 0;
}
八、运算符重载
1.限制
2.前置++与后置++
#include <iostream>
using namespace std;
class X
{
public:
X(int x = 0) : _x(x) {};
//前置++
X& operator++()
{
++_x;
return *this;
}
//后置++
//int仅仅是为了和前置++区分,无实际意义
X operator++(int)
{
X temp(*this);
++_x;
return temp;
}
void Print()
{
cout << _x << endl;
}
private:
int _x;
};
int main()
{
X x1;
++x1;
x1.Print();
X x2 = x1++;
x1.Print();
x2.Print();
return 0;
}
//输出为:
//1
//2
//1
3.流插入与流提取
流插入与流提取运算符往往声明为非成员友元。
#include <iostream>
using namespace std;
class Date
{
//友元
friend ostream& operator<<(ostream& out, const Date& date);
friend istream& operator>>(istream& in, Date& date);
public:
Date(int year = 1, int month = 1, int day = 1) : _year(year), _month(month), _day(day)
{}
private:
int _year;
int _month;
int _day;
};
inline ostream& operator<<(ostream& out, const Date& date)
{
out << date._year << "年" << date._month << "月" << date._day << "日" << endl;
return out;
}
inline istream& operator>>(istream& in, Date& date)
{
in >> date._year >> date._month >> date._day;
return in;
}
int main()
{
Date date(2023, 3, 9);
cout << date;
cin >> date;
cout << date;
return 0;
}
C++官方文档:cppreference.com
最后,分享一张好看的图片(´・ ᴗ ・`)