1、布尔类型 bool
bool是C++的基础类型之一
bool用于表示逻辑概念,只有两种值,即真和假
在C++中,用true表示真,用false表示假
bool的值占用一个字节的存储空间。
(C++中不需要额外头文件,直接用<iostream>头文件)
主要用于条件判断及函数返回类型。
例:
判断一个链表是否为空
bool isEmpty();
bool ok = isEmpty();
if (ok) // 判断条件为真
{}
if (!ok) // 判断条件为假
{}
2、字符串 string
string是由标准C++库提供的一个类类型,用于表示字符串
使用方式:
1、包含头文件
#include <string>
2、实例化对象
std::string 对象名; // 定义一个空的字符串
std::string 对象名(初始值); // 定义字符串对象,同时初始化
std::string 对象名 = 初始值; // 定义字符串对象,同时初始化
3、使用公有方法
size()/lenght() 用于计算字符串中实际字符的个数,不包括\0
empty() 判断字符串是否为空
at() 返回给定索引位置的字符, 例:s1.at(0) 返回字符串的第一个字符
c_str() 把string类型的字符串 转换为 const char*
substr() 获取字符串的子串
同时支持以下运算符:
= + += == != > >= < <= << >> []
问题:
当把一个复杂的对象以值的形式传递到一个函数中,有可能造成错误
例:
StringList list1;
list1.push_back("hello");print_list(list1); // void print_list(StringList list2);
当调用该函数时,会在函数内部创建一个新的StringList类型的对象list2
list2的成员变量的值与实参list1的成员变量的值一样。
如果成员变量是指针,就会造成两个指针的值相同,即两个指针指向同一个对象(链表)
而当函数调用完成,形参会自动销毁,对象销毁时会调用析构函数
实参销毁时,也会调用析构函数
也就是说同一个对象可能被释放两次!这是一种错误!
引用
引用是对象的别名!
语法格式:
类型名 & 引用名 = 对象名;
例:
int a = 10;
int& r = a; // r是a的另一个名字,r就是a, a就是r,表示的是同一个对象
特性:
引用必须在定义的同时初始化。
引用一旦初始化完成,与它的初始对象就一直绑定在一起,不能再成为其它对象的别名。
对引用进行的所有操作,都是在操作与之绑定的对象。
引用的类型必须与它绑定的对象类型一致,但有以下例外:
1、允许常量引用绑定到非常量对象
int a = 10;
const int& r = a;
2、基类引用可以绑定到派生类对象(下回分解)
主要用于 函数传参 和 函数返回 。
引用的分类:
常规引用
绑定到左值的引用,也叫左值引用,如果希望通过引用修改绑定对象的值,则使用左值引用
const引用
既可以绑定到左值,也可以绑定到右值,如果不希望通过引用修改绑定对象的值,则使用const引用
右值引用
C++11中新增的特性,只能绑定到右值的引用,所引用的对象在使用之后,就无需要保留。 (下面详细介绍)
右值引用主用于处理对象的移动。
对象拷贝
拷贝构造
拷贝赋值
拷贝构造函数
用于拷贝对象的构造函数,称为 拷贝构造函数。
拷贝构造函数名字与类名相同,但参数是本类类型的const引用
例:
class X
{
public:
X(const X&); // 拷贝构造函数
};
如果类中没有显式的定义拷贝构造函数,编译器会自动生成。一般称为默认拷贝构造函数。
默认拷贝构造函数 执行对象的拷贝工作:
内置类型的成员 直接拷贝
类类型的成员,则调用成员对象的拷贝构造函数
数组成员,则逐元素的拷贝数组中的成员。
拷贝构造函数的调用时机:
1、用一个已存在的对象 初始化一个新对象
例:
Point p1(100, 200);
Point p2(p1); // Point p2 = p1; Point p2{p1};
2、一个对象作为 函数参数,以 值传递的方式传入函数
void print(Point pos);
print(p1); // Point pos = p1;
3、一个对象作为 函数返回值,以值的方式从 返回 中返回
Point func()
{
Point p3(0,0);
return p3;
}
深拷贝与浅拷贝
多数情况下,都可以使用 默认拷贝构造函数 来执行对象的拷贝
但是,对于拥有资源的类来说,由默认拷贝构造函数执行的是浅拷贝(逐成员拷贝),则可能会出错误
此时,需要自定义拷贝构造函数,以实现深层次的拷贝。
如果一个类拥有资源,当该类的对象发生拷贝时,资源重新分配,这种拷贝称为深拷贝
反之,对象存在资源,但拷贝过程并 未拷贝 资源 的情况就称为浅拷贝。
省略拷贝构造
1、在对象的初始化过程中,当 初始化表达式与 对象类型 为同一类型的 纯右值时
例:
Point pos = Point(1, 2); // Point tmp(1,2); Point pos(tmp);
2、在return语句中,操作的对象 与 函数返回类型 为同一类型的 纯右值时
return Point(1,2);
如果没有显式的定义拷贝构造函数,或定义了可在类的外部访问的拷贝构造函数
阻止拷贝构造
在C++11之前,常规做法,是把拷贝构造函数私有化(如果不显式的定义拷贝构造函数,编译器会自动生成一个公有的拷贝构造函数)
例:
class Screen
{
private:
Screen(const Screen&); // 拷贝构造函数私有声明
};
在C++11中,引入了 删除函数 的概念
即,如果不显式声明某些函数,编译器会自动生成,而有时候根本不需要编译器自动生成,此时,可以使用关键字 delete 显式的指示编译器删除某些自动生成的函数
例:
class Screen
{
public:
Screen(const Screen&) = delete; // 删除拷贝构造函数
};
注:
如果需要某些默认生成的函数,可以使用关键字 default 显式的指示编译器生成相应的函数
例:
class Point
{
public:
Point() = default; // 要编译器自动生成默认构造函数
Point(int x, int y);
};
右值引用
常规引用
const引用
右值引用
所谓右值引用,就是必须绑定到右值的引用
右值要么是字面常量,要么是在表达式求值过程中创建的临时对象
通过&&来获取右值的引用。
右值引用主要用来绑定到一个将要销毁的对象上,使用右值引用可以获取这个将要销毁的对象所拥有的资源。
例:
int i = 10; // 变量是左值
int& r = i; // OK
const int& r = i; // OKint&& r = i+1; // OK
int&& r = 11; // OKint&& r = i; // error 右值引用不能绑定到左值
对象的移动
移动构造
移动赋值
在很多情况下,都会发生对象的拷贝(如初始化、传参、返回等),在某些情况下,对象被拷贝后,就立即销毁了
例:
Dir dir("./");
StringList filenames = dir.getFileNames();
getFileNames函数定义如下:
StringList getFileNames()
{
StringList list;
...
list.push_back(...);
return list; // StringList tmp = list; list被拷贝后,立即销毁了
}
在这些情况下,如果不拷贝对象,而是把将要销毁的对象的资源 移动到 它们本来要拷贝到的地方去,则会大大的提升执行效率。
为了支持移动操作,新标准引入了右值引用。
移动操作的基本思想:
拷贝左值
移动右值
移动构造函数的参数是 非const 的右值引用
除了完成资源的移动,移动构造函数 还必须确保 移动后 销毁源对象 是 安全的(不影响其它对象)
而且一旦完成资源移动,源对象 必须不再指向 被移动的资源,即资源的所有权属于新对象了。
例:
class ForwardList
{
public:
ForwardList(ForwardList&& rhs)
{
// 转移资源
this->head = rhs.head;
this->tail = rhs.tail;
this->size = rhs.size;
// 右侧对象放弃对资源的所有权
rhs.head = nullptr;
rhs.tail = nullptr;
rhs.size = 0;
}
private:
Node* head;
Node* tail;
int size;
};