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;
自动存储、静态存储和动态存储
- 自动存储
函数内部定义的常规变量使用自动存储空间,被称为自动变量。自动变量是一个局部变量,其作用域为包含它的代码块。自动变量通常存储在栈中,这意味着在执行代码块的时候,变量将依次加入到栈中,离开时则按相反的顺序释放
- 静态存储
静态存储是整个程序执行期间都在的存储方式,一种是在函数外面定义它,另一种是声明变量时使用static
- 动态存储
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)