C++笔记

c++介绍

main()函数

int main()
{
    statements
    return 0;
}
  • c++语法要求main()函数定义以int main()开始。
  • int main() 与int main(void)等效,不接收任何参数
  • main()函数结束后没有写return则默认return 0结束,只适用于main函数

注释

  • //开头
  • /**/ c风格注释

c++预处理器和iostream

  • c++和c一样,也使用一个预处理器,该程序在进行主编译之前对源文件进行处理
  • #include <iostream>是一种预处理

头文件

  • 像iostream它们被包含在其他文件中
  • c++编译器自带了很多 头文件
  • c语言包含.h的文件,c++新增了不带.h的用法,如math.h改为cmath
  • 没有.h 的头文件可以包含名称空间

名称空间

  • using编译指令
  • 名称空间是c++一项特性,为了让编写大型程序以及多个厂商现有代码组合更容易
  • 比如两个已经封装好的产品,都包含叫wanda()的函数。这样使用Microflop::wnda()和Piscine::wanda()就可以区分开
  • c++标准组建都被放置在std空间中
  • std可以省略使用using
  • 大型项目中最好using std::cout; 使用的时候不必加上std::

声明语句和变量

  • 声明指明了数据类型和程序对数据使用的名称
  • 首次使用前声明它

赋值语句

  • 等号是赋值运算符
  • c++可以连续使用赋值运算符a=b=c=88; 从右往左赋值

类简介

  • 类是用户定义的一种数据类型

函数

  • 有返回值函数
  • 无返回值函数
  • 函数原型
  • 函数定义

处理数据

简单变量

变量名

整型

无符号类型

const限定符

  • const int months=12; 使用const后,months就被固定了,编译器将不允许再修改该常量的值

浮点数

c++算术运算符

类型转换

  • c++自动执行类型转换
  • 将一种算术类型赋值给另一种算术类型变量时
  • 表达式中包含不同的类型
  • 将参数传递给函数时
  • 初始化和赋值进行转换
  • 以{}方式初始化进行转换(c++11)
  • 表达式中转换
  • 传递参数时转换
  • 强制类型转换

c++11中的auto声明

c++11让编译器能够根据初始值的类型推断变量的类型,为此重新定义了auto的含义,编译器将把变量的类型设置成与初始值相同.

处理复杂类型,自动类型推断才能显示出优势

// c++98 代码
std::vector<double> scores;
std::vector<double>::iterator pv = scores.begin()
// c++11 允许写成
std::vector<double> scores;
auto pv = scores.begin();

复合类型

数组

// 声明数组通用格式
typeName arrayName[arraySize];

数组的初始化规则

  • 定义数组才能初始化
  • 不能将一个数组赋值给另一个数组
  • 初始化时可以提供少于数组的元素数目
  • long totals[500] = {0} 初始化为0
  • long totals[500] = {1} 只有第一个元素被初始化为0
  • long totals[] = {1,1,1,1} 编译器将计算元素个数并初始化,让编译器计算这个方法不推荐

c++11数组初始化方法

  • 初始化数组时,可省略等号

int a[4] {1, 2, 4, 5}

  • 可不再大括号包含任何东西,将会初始化为零
  • 列表初始化禁止缩窄转换

字符串

string类简介

  • c++98添加了string类
  • 使用string,必须在程序中包含头文件string
  • string位于std中,要使用可以std::string来包含

c++字符串初始化

char foo[] = {"helloworld"};
char foo[]  {"helloworld"}
string foo[] = {"helloworld"}
string foo[]  {"helloworld"}

赋值拼接附加

  • string可以将对象赋值给另一个string
  • 使用+号可以合并两个string

string其他操作

  • strcpy直接用=号
  • strcat直接用+号
  • strlen直接用.size()

string类I/O

结构简介

  • c++允许在声明变量时省略struct

c++11结构初始化

  • 可以省略=号
  • 不允许缩窄转换

其他结构属性

  • 可以将结构作为参数传递给函数
  • 可以让函数返回一个结构
  • 可以赋值运算符将结构体赋值给另一个同类型

结构体数组

结构体中位字段

struct register
{
    unsigned int sn : 4;
    unsigned int : 4;
    bool goodIn : 1;
    bool goodTorgle : 1;
};

共用体

union是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型

枚举

enum工具提供了另一种创建符合常量的方式

设置枚举量的值

//显式
enum bits{one = 1, two = 2, four = 4, eight = 8};
//显示定义一些枚举的值
enum bigstep{first, second = 100, third};
//可以创建多个值相同的枚举量
enum {zero, null = 0, one, numero_uno = 1};

枚举的取值范围

  • 取值范围定义:最大值2的次方减1,如最大枚举值101,则最接近的是128-1。如果不小于0,则下限为0,否则负数采用同样的方式

指针和自由存储空间

  • home是变量,则&home是它的地址
  • 指针存储的地址,*间接值,解除引用运算符

使用new来分配内存

typeName * pointer_name = new typeName;
  • 一般变量都被分配在栈内存区域中,new被分配在堆或者自由存储区

使用delete来释放内存

  • delete只能释放new分配的内存

使用new创建动态数组

type_name * pointer_name = new type_name[num_elements];

指针、数组和指针算术

  • 指针和数组基本等价的原因在于指针算术和c++内部处理数组的方式
  • 指针加1后,增加的量相当于类型的字节数
  • 数组名是第一个元素地址

short tell[10]; cout << tell << endl; //值是&tell[0] cout << &tell << endl; //值是整个数组的地址 虽然两个地址相同,tell+1地址将加2个字节,&tell+2地址将加20

指针小结

  • 声明指针

typeName * pointerName;

  • 给指针赋值

``` double * pn; double * pa; char * pc; double bubble = 3.2; pn = &bubble; //bubble的地址赋值给pn pc = new char; //新分配内存地址赋值给pc pa = new double[30]; //赋值30个元素数组的第一个元组地址给pa

```

  • 数组的动态联编和静态联编

//使用new[]运算符创建数组 int * pz = new int [size]; //释放 delete [] pz;

自动存储、静态存储和动态存储

  1. 自动存储

函数内部定义的常规变量使用自动存储空间,被称为自动变量。自动变量是一个局部变量,其作用域为包含它的代码块。自动变量通常存储在栈中,这意味着在执行代码块的时候,变量将依次加入到栈中,离开时则按相反的顺序释放

  1. 静态存储

静态存储是整个程序执行期间都在的存储方式,一种是在函数外面定义它,另一种是声明变量时使用static

  1. 动态存储

new和delete运算符提供动态存储,他们管理了一个内存池,这在c++中被称为自由存储空间或堆。在栈中,自动添加和删除机制使得占用内存总是连续的,但new和delete相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难

数组的替代品

模板类vector和array时数组的替代品

模板类vector

  • 类似于string,也是动态数组
  • 可在运行阶段设置对象长度,可在末尾附加新数据

模板类array(c++11)

  • vector类功能比数组强大,但是效率稍低
  • array对象长度是固定的,使用栈,而不是自由存储区
  • 效率和数组一样

函数

指针和const

  • 指针指向常量对象,防止指针修改所指向的值
  • 指针本身声明为常量,防止改变指针的位置
int age = 39;
const int * pt = &age; //pt指向一个const int,不能使用pt修改值,*pt为const
  • c++禁止将const变量地址赋值给非const指针。但是可以使用强制类型转换来突破这种限制

函数探幽

c++内联函数

  • 编译器使用相应的函数代码替换函数调用
  • 内联函数运行速度比常规函数稍快,但是需要占用更多内存

引用变量

创建引用变量

int rats;
int & rodents = rats;
//其中&不是地址运算符,二十类型标识的一部分。int &指向的是int的引用
  • 引用必须在创建时初始化
  • 不能先声明

引用用作函数参数

引用的属性和特别之处

  • 如果不想修改引用参数,就用const

double refcube(const double &ra);

临时变量、引用参数和const

如果实参与引用参数不匹配,c++将生成临时变量。仅当参数为const引用时,c++才允许这样做

  • 实参的类型正确,但不是左值(左值时可被引用的数据对象,如:变量、数组元素、结构成员、引用和解除引用都是左值。非左值包括字面常量(用引号扩起的字符串除外,它们由其地址表示)和包含多项的表达式。
  • 实参的类型不正确,但可以转换为正确的类型
  • c++11新增了右值引用

将引用用于结构

  • 引用非常适合用于结构和类

何时使用引用参数

  • 能够修改调用函数中的数据对象
  • 通过传递引用而不是整个数据对象,可以提高程序运行速度
  • 数据对象很小可以按值传递
  • 如果数据对象是数组,则使用指针,这是唯一选择,并将指针声明为指向const的指针
  • 如果数据对象是较大的结构,则使用const指针或者cont引用,提高效率
  • 如果数据对象是类对象,则使用const引用
  • 如果数据对象是内置数据类型,则使用指针
  • 如果数据对象是数组,则只能使用指针
  • 如果数据对象是结构,则使用引用或指针
  • 如果数据对象是类对象,则使用引用

函数模板

重载的模板

内存模型和名称空间

单独编译

  • 头文件
  • 源代码文件

头文件包含内容

  • 函数原型
  • 使用#define或const定义的符号常量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数

存储持续性、作用域和链接性

  • 自动存储持续性: 在函数定义中声明的变量(包括函数参数)的存储持续性为自动的
  • 静态存储持续性:在函数定义的变量和使用关键字static定义的变量的存储持续性都为静态
  • 线程存储持续性(c++11):如果变量时使用关键字thread_local声明的,则其生命周期与所属的线程一样长
  • 动态存储持续性:用new运算符分配的内存将一直存在,直到delete运算符将其释放或程序结束为止,这种内存的存储持续性为动态,有时被称为自由存储或堆

作用域和链接

  • 作用域描述了名称在文件的多大范围内可见

自动存储连续性

  • 函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性
  • 自动变量使用栈

静态持续变量

  • 外部链接性(可在其他文件中访问)
  • 内部链接性(只能在当前文件中访问)
  • 无链接性(只能在当前函数或代码块中访问)

名称空间

  • 声明区域
  • 潜在作用域

新的名称空间特性

  • 一种新的声明区域来创建命名的名称空间,提供一个声明名称的区域

  • 名字唯一

  • 名称空间可以全局,也可以位于另一个名称空间中,但不能位于代码块中

  • 除了用户定义,还有一个全局名称空间

  • 任何名称空间的名称都不会与其他名称空间中的名称发生冲突

  • using声明:

using Jill::fetch;

  • using编译指令

``` using namespace std;

int main() { using namespace jack; } ```

  • using声明可能会导致二义性

  • using声明比using编译指令更安全

  • 可以不写名称空间的名字,这样不能被显式的调用

对象和类

抽象和类

类型是什么

  • 决定数据对象需要的内存数量

  • 决定如何解释内存中的位

  • 决定可使用数据对象执行的操作或方法

c++中的类

  • 类是一种将抽象转换为用户定义类型的c++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。
  • 访问控制: private,public,protected
  • 控制对成员的访问:公有还是私有

实现类成员函数

  • 定义成员函数时,使用作用域解析运算符(::)来标识函数所属的类
  • 类方法可以访问类的private组件
  • 同一个类中函数不必用作用域解析运算符
  • 方法可以访问类中私有成员
  • 类声明中的函数都将自动成为内联函数

使用类

修改实现

类的构造函数和析构函数

  • 特殊的成员函数,类构造函数

声明和定义构造函数

使用构造函数

  • 显式的调用
  • 隐式的调用

默认构造函数

  • 默认构造函数是在未提供显式初始值时,用来创建对象的构造函数

析构函数

  • 用构造函数创建对象后,程序负责跟踪该对象,对象过期时,程序将自动调用一个特殊的成员函数,析构函数

this指针

  • this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)
  • 每个成员函数(包括构造函数和析构函数)都有一个this指针
  • 如果方法需要引用整个调用对象,则可以使用表达式*this
  • 在函数的括号后面使用const限定符将this限定为const,将不能使用this来修改对象的值

对象数组

类作用域

  • 类作用域意味着不能从外部直接访问类的成员,要调用公有成员函数,必须通过对象
  • 类中定义的名称作用域都为整个类

作用域为类的常量

  • 错误方式

class bakery { private: const int Months = 12; //声明类只是描述对象的形式,并没有创建对象,没有存储值的空间 double costs[Months]; ... }

  • 正确方式

``` class bakery { enum {Months = 12}; double costs[Months]; ... }

class bakery { static const int Months = 12; //c++98中只能使用这种技术声明值为整数或枚举的静态常量,不能存储double常量。c++11中消除了这种限制 double costs[Months]; ... } ```

作用域内枚举(c++11)

  • 错误方式

enum egg {Small, Medium, Large, Jumbo}; enum t_shirt {Small, Medium, Large, Xlarge}; //这样会产生冲突

  • 正确方式

enum class egg {Small, Medium, Large, Jumbo}; enum class t_shirt {Small, Medium, Large, Xlarge}; // c++11提供新枚举,枚举量的作用域为类 // 也可以使用关键字struct代替class

  • c++11提高了作用域内枚举的类型安全

//有些情况下,常规枚举将自动转换成为整型 // 作用域内枚举不能隐式地转换为整型 enum egg_old {Small, Medium, Large, Jumbo}; // unscoped enum class t_shirt {Small, Medium, Large, Xlarge}; // scoped egg_old one = Medium; //unscoped t_shirt rolf = t_shirt::Large; //scoped int king = one; // 隐式转换 int ring = rolf; // 错误,不支持隐式转换 if (king < Jumbo) // 可以比较 std::cout << "Jumbo converted to int before comparison.\n"; if (king < t_shirt::Medium) // 不可以比较 std::cout << "Not allowed: < not defined for scoped enum.\n"; //必要时,可进行显式类型转换 int Frodo = int(t_shirt::Small);

  • c++98中如何选择取决于实现,包含枚举的结构长度可能随系统而异。

  • c++11消除了这种依赖性。默认情况下为int

// :short 将底层类型指定为short // 底层类型必须为整型 // 如果没有指定,编译器选择底层类型将随实现而异 enum class : short pizza {Small, Medium, Large, XLarge}

抽象数据类型

使用类

运算符重载

* operator +()
* operator *()

重载限制

  • 多数c++运算符都可以用这样的方式重载

  • 重载的限制

  • 重载后的运算符必须至少有一个操作数时用户定义的类型,这将防止用户定义的运算符重载的限制

  • 使用运算符时不能违反运算符原来的句法规则。不能将求模运算符重载成使用一个操作数

  • 不能创建新运算符

  • 不能重载下面的运算符

    • sizeof
    • .
    • ::
    • ?:
    • typeid
    • const_cast
    • dynamic_cast
    • reinterpret_cast
    • static_cast
  • 下面的运算符只能通过成员函数进行重载

=
()
[]
->

友元

  • 友元函数
  • 友元类
  • 友元成员函数

创建友元

//原型放在类声明中, 并加上friend
friend Time operator*(double m, const Time & t);

重载运算符:作为成员函数还是非成员函数

对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。非成员函数应是友元函数,这样才能直接访问类的私有数据

// 成员函数,一个操作数通过this指针隐式传递,另一个操作数作为函数参数显式传递

Time operator+(const Time & t) const;

// 非成员函数,两个操作数都作为参数来传递

friend Time operator+(const Time & t1, const Time & t2);


加法运算符需要两个操作数

例子: t1 = t2 + t3
// 成员函数
t1 = t2.operator+(t3)
// 非成员函数
t1 = operator+(t2, t3)