概念 | 比喻 |
---|---|
对象/实例 | 楼房 |
实例化 | 建造 |
类 | 建筑图纸 |
* 面向对象四大特征
特征 | 说明 | 类比 |
---|---|---|
抽象 | 抽出具体事物的普遍性的本质 | 分门别类:鸟类、哺乳类、鱼类 |
封装 | 把数据与处理(函数)包在一起 | 通信录(增加、删除) |
继承 | 数据与处理(函数)的传承 | 财富与绝技、混血儿(肤色/头发、 两种语言 |
多态 | 同一个事物(函数)的多种形态 | 手机键盘数字与字母、 电脑键盘功能键 |
struct
相似(C++)class 类名{
成员变量成员函数声明
};
class
定义最后的;
一定不要忘记。
构成 | 作用 |
---|---|
数据成员(data member)/成员变量/属性 | 对象内部数据和状态,只能在类定义中声明,可以在成员函数中直接调用。 |
成员函数/方法 | 对象相关的操作,可以在类内实现或类外实现。 |
* 实例:复数Complex
* 作用域运算符::
– 函数归属
* 访问限定符
限定符 | 作用 |
---|---|
private [默认] |
私有 |
public |
公开 |
protected |
保护 |
实践中,成员变量多数情况使用
private
或者protected
,成员函数多数情况使用public
。通常,通过成员函数改变对象的成员变量。
#pragma once
或者#ifnde...#endif
#include <>
(标准库函数)/#include ""
(自定义/第三方函数)在C++书籍中为了方便
.h
与.cpp
不做分离,但是项目开发中,需要分开。
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
}
class
与struct
区别是:
1. 默认的访问控制不同
struct
是public
,class
是private
2. struct
可以使用花括号内的初始值列表{...}
初始化,class
不可以(C++98不可以,C++11可以)。
注意:
* C++的struct
可以有成员函数,而C不可以。
* C++的struct
可以使用访问控制关键字(public
private
protected
),而C不可以。
成员变量默认初始化为随机值(主要影响指针)。
// 定义类
class Demo{};
// 创建对象
int main(){
Demo d;
Demo();
}
类名 对象名; // 调用默认构造函数
类名(); // 创建匿名对象
int* p = new int;
delete p;
p = NULL;
对象指针new
可以为对象设置初始值,例如下面代码
int* p = new int(100);
cout << *p << endl;
类名* 对象指针 = new 类名;// 调用默认构造函数
delete 对象指针;
int* pa = new int[10];
delete pa;// 只释放p[0]
delete [] pa;// 释放全部数组
对象数组指针new
不可以为对象设置初始值。
int* pa = new int[10](100); // error: array 'new' cannot have initialization arguments
实例:账单
基本类型的初始化新增语法:
int a(0);// 等价 int a = 0;
const float b(1.0);// 等价 const float b = 1.0;
空结构体与空类的大小(
sizeof
)为1
,主要在于初始化/实例化时,编译器给变量/对象分配内存(地址),内存最小单位为1个字节。
通常,sizeof(类型) == sizeof(变量)
。注意:C++除了特殊情况,很少直接使用
malloc()/free()
申请释放内存,取而代之的是new/delete
。
this
指针this
指针this
指针就存放指向对象数据的首地址如果成员函数形参与成员变量同名,使用
this->
做为前缀区分。
类名(参数){
函数体
}
new
动态创建类中没有显式的定义任何构造函数,编译器就会自动为该类型生成默认构造函数
类名(参数):成员变量(参数){
函数体
}
初始化非静态成员变量
从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。
* 必须使用初始化列表的情况
1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面。
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面。
3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
* 初始化列表与构造函数内部成员变量赋值的区别
成员变量初始化与成员变量赋值
能使用初始化列表的时候尽量使用初始化列表
#include
using std::cout;
using std::endl;
class Member1{
public:
Member1(){
cout << "Member1 Init" <public:
Member2(){
cout << "Member2 Init" <public:
Member3(){
cout << "Member3 Init" <public:
Test():m3(),m2(),m1(){};
private:
Member1 m1;
Member2 m2;
Member3 m3;
};
int main(){
Test test;
}
成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。
C++的函数可以增加默认参数。
写法:
1. 默认参数必须写在函数声明中,不能写在函数实现的参数列表中中。
2. 默认参数必须写在所有非默认参数的后面。
3. 默认参数可以写任意多个。
使用:
默认参数可以不用传递值,此时,使用默认值。
默认参数可以传值,此时,使用实参值。
~类名(){
函数体
}
~
delete
类中没有显式的定义析构函数,编译器就会自动为该类型生成默认析构函数
释放对象所申请占有的资源
RAII(资源的取得就是初始化,Resource Acquisition Is Initialization)
C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。 – 百度百科
声明:const 类型名& 对象名
/类型名& 对象名
使用:与对象变量、基本类型变量一样
类型:
* 基本类型(bool
、char
、int
、short
、float
)
* 复合类型(指针、数组、引用)
* 自定义类型(struct
、union
、class
)
引用作用:取代指针
*
,指针需要解引用;sizeof 引用
得到的是所指向的变量/对象的大小,而sizeof 指针
得到的是指针本身的大小;引用通常用于两种情形:成员变量和函数的参数列表。
类名(类名& 形参){
函数体
}
或者
类名(const 类名& 形参){
函数体
}
或者
类名(const 类名 形参){
函数体std:swap();
}
手动调用
类名 对象名; // 调用默认构造函数
类名 对象2 = 对象1; // 调用复制构造函数
类名 对象3(对象1); // 调用复制构造函数
自动调用
* 一个对象作为函数参数,以值传递的方式传入函数体
* 一个对象作为函数返回值,以值从函数返回
* 一个对象用于给另外一个对象进行初始化(赋值初始化)
实践说明:
* gcc/clang自动RVO/NRVO优化,不执行拷贝构造函数。可以在编译命令添加选项禁止-fno-elide-constructors
;
* VC在Debug环境下返回值执行拷贝构造函数,在Release环境下实施RVO/NRVO优化。
如何禁用默认拷贝构造函数?在类定义,添加私有的复制构造函数的声明,但不实现。
类名& operater=(const 类名& 形参){
// 赋值操作
return *this;
}
如何禁用默认赋值运算符重载函数?在类定义,添加私有的赋值运算符重载函数的声明,但不实现。
拷贝构造函数与赋值操作符的区别
* 拷贝构造函数:当一个已经存在的对象来初始化一个未曾存在的对象
* 赋值操作符:当两个对象都已经存在
问题下面那些是拷贝构造函数与赋值操作符?各有几次?
Demo a;
Demo b;
b = a;
Demo c = a;
Demo d = Demo(a);
一个类中存在指针类型的成员变量。并且类构造和析构管理指针的申请与释放。
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)
如果类中明确定义下列其中一个成员函数,那么必须连同其他二个成员函数编写至类内,即下列三个成员函数缺一不可:
* 析构函数(destructor)
* 复制构造函数(copy constructor)
* 复制赋值运算符(copy assignment operator)
const
限定符const
与变量/对象const 类型 变量 = 初始值;
const 类型 对象;
例如:
const int size = 4;
现在比较前卫写法
类型 const 变量 = 初始值;
类型 const 对象;
例如:
int const size = 4;
const
变量默认作用域是定义所在文件const
对象只能调用const
成员函数
const
与宏定义#define
的区别const |
宏定义#define |
---|---|
编译器处理方式 | 编译运行阶段使用 |
类型 | 有具体的类型 |
安全检查 | 编译阶段会执行类型检查 |
存储方式 | 分配内存 |
* const
与指针
No. | 类型 | 语法 | 作用 |
---|---|---|---|
1 | const 指针 |
类型* const 变量 = 初始值; |
指针指向地址不能改变 |
2 | 指向const 对象的指针 |
const 类型* 变量 = 初始值; 类型 const* 变量 = 初始值; |
指针指向对象不能改变 |
3 | 指向const 对象的const 指针 |
const 类型* const 变量 = 初始值; |
指针指向地址和对象不能改变 |
No.2 是使用最频繁的方式。
const
与引用类型 const &变量 = 初始值;
与const 类型& 变量 = 初始值;
都是引用对象不能改变。
const
与函数的参数和返回值类型 | 语法 | 作用 | 说明 |
---|---|---|---|
const 参数 |
返回值类型 函数(const 类型 形参) |
函数内部不能改变参数的值 | 这样的参数的输入值 |
const 返回值 |
const 返回值类型 函数(形参列表) |
函数的返回值不能改变 | 常用于字符串/指针 |
* const
成员变量
* 不能在类声明中初始化const
数据成员
* const
成员变量只能在类构造函数的初始化列表中初始化
class 类名{
public:
类名(类型 形参):成员变量(形参){}
private:
const 类型 成员变量;
}
如果使用
const
成员变量不能使用赋值运算符重载函数
const
成员函数 class 类名{
public:
返回值类型 函数名(形参列表)const;
}
返回值类型 函数名(形参列表)const;
必须在成员函数的声明和定义后都加上
const
const修饰位置 | 作用 |
---|---|
变量 | 变量不可修改,通常用来替代#define |
对象/实例 | 对象的成员变量不可修改,只能调用const 成员函数 |
函数参数 | 参数不能在函数内部修改,只作为入参 |
函数返回值 | 返回的结果不能被修改,常用于字符串 |
成员变量 | 只能在初始化列表中初始化 |
成员函数 | 不改变成员变量 |
只要能够使用
const
,尽量使用const
。
static
限定符class 类名{
static 返回类型 函数(形参列表);
};
返回类型 类名::函数(形参列表){
函数体;
}
类名::函数(实参列表);
static
只能用于类的声明中,定义不能标示为static
。private
,public
,protected
访问权限this
关键字const
与virtual
) 因为静态成员函数是属于类而不是某个对象。
因为静态成员变量是属于类而不是某个对象。静态成员变量所有类的对象/实例共享。
static 修饰位置 |
作用 |
---|---|
变量 | 静态变量 |
函数 | 只源文件内部使用的函数 |
成员变量 | 对象共享变量 |
成员函数 | 类提供的函数,或者作为静态成员对象的接口 |
单例模式:使用静态成员变量和静态成员函数。
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();
}
变量类型 | 声明位置 |
---|---|
一般成员变量 | 在构造函数初始化列表中初始化 |
const 成员常量 |
必须在构造函数初始化列表中初始化 |
static 成员变量 |
必须在类外初始化 |
static const /const static 成员变量 |
变量声明处或者类外初始化 |
注意:
static const
/const static
成员变量必须是整形。
inline
– 宏定义的接班人
inline
必须与函数实现/定义体放在一起才能使函数成为内联,将inline
放在函数声明前面不起任何作用内联函数的代码直接替换函数调用,省去函数调用的开销
案例:实现复数类
运算符重载主要有两种方式实现:
* 成员函数运算符重载
返回值类型 operator 运算符(参数){
函数体
}
friend 返回值类型 operator 运算符(形参列表) {
函数体
}
No. | 类型 | 运算符 | 成员函数 | 友元函数 |
---|---|---|---|---|
1 | 双目算术运算符 | + - * / % |
类名 operator 运算符(const 类名&) const |
类名 operator 运算符(const 类名&, const 类名&) |
2 | 关系运算符 | == != > >= < <= |
bool operator 运算符 (const 类名& ) const |
bool operator 运算符 (const 类名&,const 类名&) |
3 | 双目逻辑运算符 | && ¦¦ |
bool operator 运算符 (const 类名& ) const |
bool operator 运算符 (const 类名&,const 类名&) |
4 | 单目逻辑运算符 | ! |
bool operator !() const |
bool operator ! (const 类名&) |
5 | 单目算术运算符 | + - |
类名& operator 运算符 () |
类名& operator 运算符 (const 类名&) |
6 | 双目位运算符 | & ¦ |
类名 operator 运算符 (const 类名& ) const |
类名 operator 运算符 (const 类名& ,const 类名& ) |
7 | 单目位运算符 | ~ |
类名 operator ~ () |
类名 operator ~ (类名&) |
8 | 位移运算符 | << >> |
类名 operator 运算符 (int i) const |
类名 operator 运算符 (const 类名&,int i) |
9 | 前缀自增减运算符 | ++ -- |
类名& operator 操作符 () |
类名& operator 操作符 (类名&) |
10 | 后缀自增减运算符 | ++ -- |
类名 operator ++ (int) |
类名 operator ++ (类名&,int) |
11 | 复合赋值运算符 | += -= *= /= %= &= ¦= ^= |
类名& operator 运算符 (const 类名& ) |
类名& operator += (类名&,const 类名&) |
12 | 内存运算符 | new delete |
参见说明 | 参见说明 |
13 | 流运算符 | >> << |
- | 参见说明 |
14 | 类型转换符 | 数据类型 |
参见说明 | - |
15 | 其他运算符重载 | = [] () -> |
参见说明 | - |
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);
流运算符只能使用友元函数实现。
inline ostream &operator << (ostream&, 类名&)
inline istream &operator >> (istream&, 类名&)
这些运算符只能使用成员函数实现。
operator char* () const;
operator int ();
operator const char () const;
operator short int () const;
operator long long () const;
这些运算符只能使用成员函数实现。
类名& operator = (const 类名& );
char operator [] (int i);//返回值不能作为左值
const char* operator () ();
T operator -> ();
.
、作用域运算符::
、sizeof
、条件运算符?:
函数重载
#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;
}