一、类和对象上
1、面向过程和面向对象
C语言是面向过程的,关注的是过程。
C++是面向对象的,关注的是对象。
2、类的引入
3、类的定义
C++中把struct升级成了类,同时引入了新的定义类的关键字class。
class 类名
{
类体 ,由成员变量和成员函数构成;
};
4、类的访问限定符及其封装
4.1、访问限定符
】
这里需要注意以下几点:
public修饰的成员在类外面可以被访问;
相反的protected和private修饰的成员在类外面不可以被访问;
其作用域从该访问限定符开始到下一个访问限定符结束;
class默认的访问限定符为privacy,struct为public;
4.2、封装
5、类的作用域
类定义了一个新的作用域,类中的所有成员都在这个作用域中。
6、类的实例化
这里需要注意的是类在没有进行实例化的时候,这个类相当于一个模型,并没有分配实际的空间来存储它。
类在没有实例化之前相当于一个建筑图纸并没有开辟相应的空间。
7、类对象模型
7.1、类对象的大小
类中只保留成员变量,而成员函数则存放在公共代码区;
7.2、类对象的存储方式
遵循内存对齐的原则;
对于仅有成员函数的类来说,需要1byte,是为了占位,表示对象存在;
8、this指针
this指针是隐藏传递的,this不能在形参和实参显示传递,但是可以在函数内部显示使用;
this指针存放的位置,this是形参,所有this指针跟普通参数一样存放在函数的栈里面。
看下面两个例子:判断其运行结果
左一:p调用Print,不会发生解引用,因为Printf地址不在对象中,p会作为实参传递给this指针;
this指针是空的,但是函数内没有对this指针进行解引用,因此正常运行。
右一:p调用Print,不会发生解引用,因为Printf地址不在对象中,p会作为实参传递给this指针;
this指针是空的,但函数体内访问_a,本质是this->a,因此运行崩溃。
二、类和对象中
1、类的6个默认成员函数
下面主要介绍四个(构造函数,析构函数,拷贝构造函数,赋值重载);我们不写编译器会自己生成。
2、构造函数
2.1、概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象(自定义类型)时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
2.2、特征
构造函数是特殊的成员函数,这里并非开辟空间,而是初始化对象;
(1)、函数名与类名相同;
(2)、无返回值;
(3)、对象实例化时编译器自动调用相应的构造函数;
(4)、构造函数可以重载;
(5)、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
(6)、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。
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;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明
// 这里给的是默认的缺省值,给编译器生成默认构造函数用
int _year = 1;
int _month = 1;
int _day = 1;
// 自定义类型
//Stack _st;
};
// 1、一般情况下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
// 2、全部都是自定义类型成员,可以考虑让编译器自己生成
int main()
{
// 构造函数的调用跟普通函数也不一样
Date d1;
//Date d1(); // 不可以这样写,会跟函数声明有点冲突,编译器不好识别
Date d2(2023, 11, 11);
d1.Print();
d2.Print();
Date d3(2000);
d3.Print();
//Date d1;
//d1.Date();
//Date d2;
//d2.Date(2023, 1, 1);
return 0;
}
构造函数的一些注意点:
(1)、一般情况下构造函数都需要我们自己写;
(2)不需要我们写的几个例子;
a、内置类型成员都有缺省值,且符合我们的要求;
b、全是自定义类型的构造,且这些类型都有定义默认构造;
3、析构函数
3.1、概念
与构造函数功能相反,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
3.2、特征
(1)、 析构函数名是在类名前加上字符 ~;
(2)、无参数无返回值类型;
(3)、一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载;
(4)、对象生命周期结束时,C++编译系统系统自动调用析构函数;
析构函数一些注意点:
a、内置类型成员不做处理;
b、自定义成员会去调用它的析构函数;
4、拷贝构造函数
4.1、概念
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。
4.2、特征
(1)、拷贝构造函数是构造函数的一个重载形式;
(2)、拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用;
(3)、若未显式定义,编译器会生成默认的拷贝构造函数;
a、内置类型成员完成值拷贝/浅拷贝;
b、自定义类型成员会调用它的拷贝构造;
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack()" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_top = 0;
}
// st2(st1)
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a = nullptr;
int _top = 0;
int _capacity;
};
5、赋值运算符重载
5.1、运算符重载
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
需要注意的点:
不能通过连接其他符号来创建新的操作符:比如operator@;
重载操作符必须有一个类类型参数;
用于内置类型的运算符,其含义不能改变,例如:内置的整型+;
不能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this;
.* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现;
Date d1(2023, 4, 26);
Date d2(2023, 6, 21);
d1 < d2; // 转换成operator<(d1, d2);
operator<(d1, d2);
d1 < d2; // 转换成d1.operator<(d2);
d1.operator<(d2);
// 用一个已经存在的对象初始化另一个对象 -- 构造函数
Date d4 = d2; // 等价于 Date d4(d2);
//已经存在的两个对象之间复制拷贝 -- 运算符重载函数
d4 = d1;
5.2、赋值运算符重载
(1)、赋值运算符只能重载成类的成员函数不能重载成全局函数;
(2)、用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝;
需要注意的是默认生成赋值重载跟拷贝构造行为一样:
a、内置类型成员——浅拷贝/值拷贝
b、自定义类型会去调用它的赋值重载
这块需要注意一个点:
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
// 流插入不能写成成员函数?
// 因为Date对象默认占用第一个参数,就是做了左操作数
// 写出来就一定是下面这样子,不符合使用习惯
//d1 << cout; // d1.operator<<(cout);
//void operator<<(ostream& out);
5.3、前置++,后置++
// 前置++
Date& Date::operator++()
{
*this += 1;
return *this;
}
// 后置++
// 增加这个int参数不是为了接收具体的值,仅仅是占位,跟前置++构成重载
Date Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
6、const成员
7、取地址及const取地址操作符重载
class Date
{
public:
Date* operator&()
{
cout << "Date* operator&()" << endl;
//return this;
return nullptr;
}
const Date* operator&() const
{
cout << "const Date* operator&() const" << endl;
return this;
}
private:
int _year = 1; // 年
int _month = 1; // 月
int _day = 1; // 日
};
int main()
{
Date d1;
const Date d2;
cout << &d1 << endl;
cout << &d2 << endl;
return 0;
}
三、类和对象下
1、再谈构造函数
1.1、构造函数体赋值
1.2、初始化列表
class A
{
public:
/*A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}*/
A(int a)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
private:
int _a;
};
class B
{
public:
// 初始化列表:对象的成员定义的位置
B(int a, int& ref)
:_ref(ref)
, _n(1)
, _x(2)
, _aobj(a)
{
//_n = 0;
//_ref = ref;
}
private:
// 声明
A _aobj; // 没有默认构造函数
// 特征:必须在定义的时候初始化
int& _ref; // 引用
const int _n; // const
int _x = 1; // 这里1是缺省值,缺省值是给初始化列表的
};
需要注意的是:
(1)、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次);
(2)、类中包含以下成员,必须放在初始化列表位置进行初始化;
a、引用成员变量;
b、const成员变量;
c、自定义类型成员(且该类没有默认构造函数时);
(3)、成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关;
1.3、explicit关键字
class A
{
public:
/*explicit A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}*/
A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2 = 2; // 隐式类型转换,整形转换成自定义类型]
// 2构造一个A的临时对象,临时对象再拷贝构造aa2 -->优化用2直接构造
// error C2440: “初始化”: 无法从“int”转换为“A &”
//A& aa3 = 2;
const A& aa3 = 2;
int i = 10;
double d = i;
return 0;
}
explicit所修饰的构造函数,禁止类型转换。
2、static成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
void Func2()
{
++_a1;
}
// 没有this指针,指定类域和访问限定符就可以访问
static int GetACount()
{
// 静态能否调用非静态:不可以。非静态的成员函数调用需要this指针,我没有this
// Func2();
//_a1++;
return _scount;
}
private:
// 成员变量 -- 属于每个一个类对象,存储对象里面
int _a1 = 1;
int _a2 = 2;
//public:
// 静态成员变量 -- 属于类,属于类的每个对象共享,存储在静态区
static int _scount;
};
// 全局位置,类外面定义
int A::_scount = 0;
这里由于静态成员没有隐藏的this指针,所以可以用 类名::静态成员 或者 对象.静态成员 来访问。
3、友元
3.1、友元函数
友元函数可访问类的私有和保护成员,但不是类的成员函数;
友元函数不能用const修饰;
友元函数可以在类定义的任何地方声明,不受类访问限定符限制;
一个函数可以是多个类的友元函数;
友元函数的调用与普通函数的调用原理相同;
3.2、友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
需要注意:
友元关系是单向的,不具有交换性;
友元关系不能传递 ;
友元关系不能继承;
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
3.3、内部类
class A
{
private:
static int k;
int h;
class B // 内部类是外部类的天生友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
private:
int b;
};
};
5、匿名对象
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
cout << "Sum_Solution" << endl;
//...
return n;
}
};
void push_back(const string& s)
{
cout << "push_back:" << s << endl;
}
int main()
{
A aa(1); // 有名对象 -- 生命周期在当前函数局部域
A(2); // 匿名对象 -- 生命周期在当前行
Solution sl;
sl.Sum_Solution(10);
Solution().Sum_Solution(20);
//A& ra = A(1); // 匿名对象具有常性
const A& ra = A(1); // const引用延长匿名对象的生命周期,生命周期在当前函数局部域
A(10);
Solution().Sum_Solution(20);
string str("11111");
push_back(str);
push_back(string("222222"));
push_back("222222");
return 0;
}