什么是类(class)?
类(class)是类型(type),是用户自定义的类型。为什么不叫它type,因为借用Simula语言中的class关键字。
为什么要有类?
基于便利性的考虑,现实世界中物(object)通常被分为几种独立的分类。
class 类名{
成员变量成员函数声明
};
class
定义最后的;
一定不要忘记。
实践中,成员变量多数情况使用
private
或者protected
,成员函数多数情况使用public
。通常,通过成员函数改变对象的成员变量。
#pragma once
或者#ifnde...#endif
#include <>
(标准库函数)/#include ""
(自定义/第三方函数)在C++书籍中为了方便
.h
与.cpp
不做分离,但是项目开发中,需要分开。
强化练习:账单Bill(名称、数量、单价、小计)
vector与string
class
与struct
区别
struct_class.cpp
#include
using std::cout;
using std::endl;
struct SPos {
int x,y,z;
};
class CPos {
int x,y,z;
};
int main(){
#ifdef STRUCT
SPos spos = {
1,1,1};
cout << "(" << spos.x << "," << spos.y << "," << spos.z << ")" << endl;
#else
CPos cpos = {
1,1,1};
cout << "(" << cpos.x << "," << cpos.y << "," << cpos.z << ")" << endl;
#endif
}
C++class
与struct
区别是:
struct
是public
,class
是private
struct
可以使用花括号内的初始值列表{...}
初始化,class
不可以(C++98不可以,C++11可以)。注意:
- C++的
struct
可以有成员函数,而C不可以。- C++的
struct
可以使用访问控制关键字(public private protected
),而C不可以。- C++的
struct
SPos spos; // C++
struct SPos spos; // C/C++
成员变量默认初始化为随机值(主要影响指针)。
直接创建 – 类作为类型定义变量 – 栈上创建
基本类型
int main(){
int a = 10;
int b(10);
cout << a << " " << b << endl;
}
基本类型的初始化新增语法:
int a(0);// 等价 int a = 0;
const float b(1.0);// 等价 const float b = 1.0;
类类型
// 定义类
class Demo{
};
// 创建对象
int main(){
Demo d; // 变量(命名对象)
Demo(); // 匿名对象
}
类名 对象名; // 调用默认构造函数
类名(); // 创建匿名对象
动态创建 – 堆上创建
基本类型
int* p = new int;
delete p;
p = NULL;
类类型
// 定义类
class Demo{
};
// 创建对象
int main(){
Demo* d = new Demo;
delete d;
d = NULL;
}
类名* 对象指针 = new 类名;// 调用默认构造函数
delete 对象指针;
对象指针new
可以为对象设置初始值,例如下面代码
int* p = new int(100);
cout << *p << endl;
动态创建数组 – 堆上创建
基本类型
int* pa = new int[10];
delete pa;// 只释放p[0]
delete [] pa;// 释放全部数组
类类型
// 定义类
class Demo{
};
// 创建对象
int main(){
Demo* d = new Demo[10];
delete [] d;
d = NULL;
}
对象数组指针new
不可以为对象设置初始值。
int* pa = new int[10](100); // error: array 'new' cannot have initialization arguments
注意:C++除了特殊情况,很少直接使用
malloc()/free()
申请释放内存,取而代之的是new/delete
。
强化练习:银行与账户
空结构体与空类的大小(
sizeof
)为1
,主要在于初始化/实例化时,编译器给变量/对象分配内存(地址),内存最小单位为1个字节。
通常,sizeof(类型) == sizeof(变量)
。
this
指针如果成员函数形参与成员变量同名,使用
this->
做为前缀区分。
语法
类名(参数){
函数体
}
特点
调用时机
new
动态创建默认构造函数
类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数,默认构造函数没有参数。
构造函数的三个作用
初始化列表
类名(参数):成员变量(参数){
函数体
}
作用
初始化非静态成员变量
说明
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
必须使用初始化列表的情况
能使用初始化列表的时候尽量使用初始化列表
强化练习:银行管理多个账户
成员变量的初始化顺序
#include
using std::cout;
using std::endl;
class Member1{
public:
Member1(){
cout << "Member1 Init" <<endl;
}
};
class Member2{
public:
Member2(){
cout << "Member2 Init" <<endl;
}
};
class Member3{
public:
Member3(){
cout << "Member3 Init" <<endl;
}
};
class Test{
public:
Test():m3(),m2(),m1(){
};
private:
Member1 m1;
Member2 m2;
Member3 m3;
};
int main(){
Test test;
}
成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
C++的函数可以增加默认参数。
写法:
- 默认参数必须写在函数声明中,不能写在函数实现的参数列表中。
- 默认参数必须写在所有非默认参数的后面。
- 默认参数可以写任意多个。
使用:
- 默认参数可以不用传递值,此时,使用默认值。
- 默认参数可以传值,此时,使用实参值。
扩展阅读
- <
> 条款4:确定对象被使用前已被初始化
问题:银行管理多个账户的内存泄露及解决方案
语法:
~类名(){
函数体
}
~
调用时机
默认析构函数
类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数
作用
释放对象所申请占有的资源
RAII(资源的取得就是初始化,Resource Acquisition Is Initialization)
C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。 – 百度百科
分析下面程序执行结果:
#include
using namespace std;
class Test{
public:
Test(){
cout << "Test Construct" <<endl;
}
~Test(){
cout << "Test Deconstruct" << endl;
}
};
int main(){
// 局部对象
cout << "Before {" << endl;
{
cout << "After {" << endl;
Test t;
cout << "Before }" << endl;
}
cout << "After }" << endl;
// 动态对象
cout << "Before {" << endl;
{
cout << "After {" << endl;
Test* pt = new Test;
delete pt;
pt = NULL;
cout << "Before }" << endl;
}
cout << "After }" << endl;
return 0;
}
问题:new
/delete
与malloc()
与free()
的区别?
new与malloc的区别
案例:MyString
语法
声明:const 类型名& 对象名
/类型名& 对象名
使用:与对象变量、基本类型变量一样
例如:
int a = 1
int& b = a;
cout << "&a:" << &a << endl;
cout << "&b:" << &b << endl;
b = 13;
cout << a << endl;
引用其实就是一个别名,a
与b
代表的是相同的对象。
何处使用引用
为何使用引用
类型:
- 基本类型(bool、char、int、short、float)
- 复合类型(指针、数组、引用)
- 自定义类型(struct、union、class)
引用作用:取代指针
void Func(int* n){
*n = 2;
}
void Func(int& n){
n = 3;
cout << &n << endl;
}
int main(){
int a =1;
cout << &a << endl;
Func(&a);
cout << a << endl;
Func(a);
cout << a << endl;
}
函数三种传参方式
- 传值
void Func(int n)
- 传地址/指针
void Func(int* n)
- 传引用
void Func(int& n)
引用与指针的区别
引用通常用于三种情形
:成员变量和函数的参数列表以及函数返回值。
语法:
类名(类名& 形参){
函数体
}
或者
类名(const 类名& 形参){
函数体
}
调用时机
手动调用
类名 对象名; // 调用默认构造函数
类名 对象2 = 对象1; // 调用复制构造函数
类名 对象3(对象1); // 调用复制构造函数
自动调用
实践说明:
- gcc/clang自动RVO/NRVO优化,不执行拷贝构造函数。可以在编译命令添加选项禁止
-fno-elide-constructors
;- VC在Debug环境下返回值执行拷贝构造函数,在Release环境下实施RVO/NRVO优化。
问题
- 一个类可以多个拷贝构造函数吗?
- 拷贝构造函数的参数可以不是引用吗?例如
Test(Test t)
如何禁用默认拷贝构造函数
?在类定义,添加私有的复制构造函数的声明,但不实现。
下列程序的执行结果
#include
#include
using namespace std;
class Test {
public:
Test() {
}
Test(Test &t) {
cout << "Test(Test &t)" << endl; }
Test(Test const &t) {
cout << "Test(Test const &t)" << endl; }
};
int main() {
Test t1;
Test t2 = t1;
const Test t3;
Test t4 = t3;
}
语法
类名& operater=(const 类名& 形参){
// 赋值操作
return *this;
}
调用时机
如何禁用默认赋值运算符重载函数
?在类定义,添加私有的赋值运算符重载函数的声明,但不实现。
拷贝构造函数与赋值操作符的区别
问题下面那些是拷贝构造函数与赋值操作符?各有几次?
Demo a; //默认构造
Demo b; //默认构造
b = a; / 赋值运算符重载
Demo c = a; // 拷贝构造
Demo d = Demo(a); // 两次拷贝构造,其中含有一次匿名对象
扩展阅读
- <
>
- 条款10:令operator=返回一个reference to *this
- 条款11:在operator=中处理“自我赋值”
- 条款12:复制对象时勿忘其每一个成分
问题
一个类中存在指针类型的成员变量。并且类构造和析构管理指针的申请与释放。
class Demo{
public:
Demo(int n){
p = new int(n);
}
~Demo(){
delete p;
p = NULL;
}
private:
int* p;
};
调用拷贝构造函数会出现什么情况?
Demo a(10);
Demo b = a;
解决
赋值运算符重载是否会有相同情况?
区别
最佳实践
三大定律
(Rule of three / the Law of The Big Three / The Big Three)
如果类中明确定义下列其中一个成员函数,那么必须连同其他二个成员函数编写至类内,即下列三个成员函数缺一不可
:
扩展阅读
- <
>
- 条款5:了解C++默默编写并调用那些函数
- 条款6:若不想使用编译器自动生成的函数,就该明确拒绝
作用
特点
const 类型 变量 = 初始值;
const 类型 对象;
例如:
const int size = 4;
现在比较前卫写法
类型 const 变量 = 初始值;
类型 const 对象;
例如:
int const size = 4;
const
变量默认作用域是定义所在文件const
对象只能调用const
成员函数const
与宏定义#define
的区别const
与指针No.2 是使用最频繁的方式。
const
与引用类型 const &变量 = 初始值;
与const 类型& 变量 = 初始值;
都是引用对象不能改变。
const
与函数的参数和返回值const
成员变量
const
数据成员(C++11可以)const
成员变量只能在类构造函数的初始化列表中初始化class 类名{
public:
类名(类型 形参):成员变量(形参){
}
private:
const 类型 成员变量;
}
注意:
- 使用const成员变量不能省略构造函数(引用类型的成员变量相同)
- 使用const成员变量不能使用赋值运算符重载函数
const
成员函数class 类名{
public:
返回值类型 函数名(形参列表)const;
}
返回值类型 函数名(形参列表)const;
#include
using namespace std;
class Test{
public:
void Print() const{
cout << "Test" << endl;
}
};
void Func(const Test& t){
t.Print();
}
int main(){
Test t;
Func(t);// 变量
Func(Test());// 匿名对象
const Test t2;
Func(t2); // 只读对象
t2.Print();
}
必须在成员函数的声明和定义后都加上
const
只要能够使用
const
,尽量使用const
。
static
限定符本质:
语法
class 类名{
static 返回类型 函数(形参列表);
};
返回类型 类名::函数(形参列表){
函数体;
}
类名::函数(实参列表);
对象.函数(实参列表);
规则
禁忌
因为静态成员函数是属于类而不是某个对象。
volatile是一个不常用的关键字,作用是改善编译器的优化能力。
静态成员变量
语法:
因为静态成员变量是属于类而不是某个对象。静态成员变量所有类的对象/实例
共享
。
单例模式:使用静态成员变量和静态成员函数。
示例:
统计某类实例个数
习题:
下面代码输出为()[美团点评2019秋招]
#include
using namespace std;
class Test{
public:
static char x;
};
char Test::x='a';
int main(){
Test exp1,exp2;
cout<<exp1.x<<" ";
exp1.x+=5;
cout<<exp2.x <<endl;
}
A. f a
B. a f
C. a a
D. f f
选择b
静态成员变量是共享的
const static
限定符#include
using std::cout;
using std::endl;
class StaticConstTest{
public:
void print(){
cout << test1 << " " << test2 << endl;
}
private:
static const int test1 = 1;
static const int test2;
};
/* static */ const int StaticConstTest::test2 = 2;
int main(){
StaticConstTest sct;
sct.print();
}
注意:
static const
/const static
成员变量在类初始化必须是数字类型。
inline
– 宏定义的接班人
语法
慎用内联
本质
内联函数的代码直接替换函数调用,省去函数调用的开销
案例:实现复数类
语法
运算符重载主要有两种方式实现:
返回值类型 operator 运算符(参数){
函数体
}
friend 返回值类型 operator 运算符(形参列表) {
函数体
}
分类
void *operator new(size_t size);
void *operator new[](size_t size);
void operator delete(void*p);
void operator delete [](void* p);
void *operator new(类名,size_t size);
void *operator new[](类名&,size_t size);
void operator delete(类名&,void*p);
void operator delete [](类名&,void* p);
2.流运算符
流运算符只能使用友元函数实现。
inline ostream &operator << (ostream&, 类名&)
inline istream &operator >> (istream&, 类名&)
3.类型转换符
这些运算符只能使用成员函数实现。
operator char* () const;
operator int ();
operator const char () const;
operator short int () const;
operator long long () const;
4.其他运算符重载
这些运算符只能使用成员函数实现。
类名& operator = (const 类名& );
char operator [] (int i);//返回值不能作为左值
const char* operator () ();
T operator -> ();
规则
本质
函数重载
问题
#include
#include
#include
using namespace std;
string operator+(int n,string const& str){
ostringstream oss;
oss << n << str;
return oss.str();
}
string operator+(string const& str,int n){
ostringstream oss;
oss << str << n;
return oss.str();
}
string operator+(float n,string const& str){
ostringstream oss;
oss << n << str;
return oss.str();
}
string operator+(string const& str,float n){
ostringstream oss;
oss << str << n;
return oss.str();
}
string operator+(double n,string const& str){
ostringstream oss;
oss << n << str;
return oss.str();
}
string operator+(string const& str,double n){
ostringstream oss;
oss << str << n;
return oss.str();
}
// 注意重载中的类型自动转换
int main(){
string a = " test ";
string res1 = "str" + a;
cout << res1 << endl;
auto res2 = 123 + a;
cout << res2 << endl;
auto res3 = a + 123;
cout << res3 << endl;
auto res4 = 123.456f + a;
cout << res4 << endl;
auto res5 = a + 123.456f;
cout << res5 << endl;
auto res4 = 123.456 + a;
cout << res4 << endl;
auto res5 = a + 123.456;
cout << res5 << endl;
}
有如下类模板定义:[美团点评2019秋招]
class A{
int n;
public:
A(int i):n(i) {
}
A operator+(A b){
return A(n + b.n);
}
};
己知a, b是A的两个对象,则下列表达式中错误的是()
A. 3+a
B. a.operator+(b)
C. a+b
D. a+1
选择A
(int + a)int调用的是默认的+,默认的+不能与对象a相加运算