c++总结
- enum 枚举
enum spectrum{red, orange, yellow, green, blue, violet, indigo, ultraviolet};
spectrum band;
band = blue; - 指针
int * pt = new int;
delete pt;
new的用武之地:创建动态数组
int* psome = new int[10]
delete [] psome; 方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。
指向数组的指针:
不同的C++存储方式是通过存储持续性、作用域和链接性来描述的。
C++使用三种(C++11种是4种)不同的方案来存储数据,这些方案的区别是数据保留在内存中的时间:
-
自动存储持续性
存储在栈中(后进先出LIFO) 执行代码块时,其中的变量依次加入到栈中,在离开代码块时,按相反的顺序释放这些变量。
在函数中定义声明的变量(包括函数参数)的存储持续性都是自动的,它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。 -
静态存储
整个程序执行期间都存在的存储方式。两种定义方式:在函数外面定义或者使用static。 -
动态存储 自由存储(free store)/堆(heap)
new delete
用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。 -
线程存储持续性
如果变量使用thread_local声明,则让其声明周期与所属的线程一样长。
作用域和链接
作用域:描述名称在文件(单元)的多大范围可见
链接性:描述了名称如何在不同单元中共享
链接性在外部的名称可在文件间共享
链接性为内部的名称只能由一个文件中的函数共享
自动变量没有链接性,因为它们不能共享
- 默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用于为局部,没有链接性。
- 静态存储的链接性有3种:
外部链接性(可在其它文件中访问)在外部声明
int global = 1000;//static duration, external linkage
内部链接性(只能在当前文件中访问)
在代码块外面声明,并且使用static限定符。
无链接性(只能在当前函数或代码块中访问)
在代码块内声明,并且使用static限定符
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无链接性 | 在代码块中 |
存储器 | 自动 | 代码块 | 无链接性 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无链接性 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部链接性 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部连接性 | 不在任何函数内,使用关键字static |
由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理它们。编译器将分配固定的内存块来存储所有的静态变量。
静态持续性,外部链接性
链接性为外部的变量通常简称为外部变量,外部变量也称全局变量
单定义规则:
在每个使用外部变量的文件中,都必须声明它,
定义声明:defining declaration 简称定义
引用声明,reference declaration 简称声明
引用声明使用关键字 extern
//file01.cpp
extern int cats = 20;
int dogs = 22;
int fleas;
//file02.cpp
extern int cats;
extern int dogs;
//file03.cpp
extern int cats;
extern int dogs;
extern int fleas;
作用域解释符(::),放在变量名前面时,该运算符表示使用变量的全局版本。
可以使用外部变量在多文件程序的不同部分之间共享数据;可以使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(名称空间提供了另外一种数据共享的方法)。如果将作用域为整个文件的变量变为静态的,就不必担心其名称与其他文件中的作用域为整个文件的变量发生冲突
-
存储说明符 CV限定符
存储说明符:auto register static extern thread_local mutable
CV限定符 const(内存被初始化后,程序便不能对它再进行修改)
volatile(即使程序代码没有对内存单元进行修改,其值也可能发生变化。告诉编译器,不要进行这种优化)
mutable:即使结构(或类)变量为const,其某个成员也可以被修改。
struct data
{
char name[30];
mutable int accesses;
}
const data veep={"Claybourne Clodde",0,...};
strcpy(veep.name,"Joye Joux");//not allowed
veep.accesses++;
在C++中,const对默认存储类型稍有影响。在默认情况下全局变量的链接性为外部。但const全局变量的连接性为内部。也就是说,在C++看来,全局const定义就像使用了static说明符一样
- 将一组常量放在头文件中
函数和链接性
所用函数的存储持续性都是静态的,即在整个程序执行期间都一直存在。默认情况下,函数的链接性为外部,即可以在文件间共享,可以在函数原型中使用关键字extern来指出函数是在另一个文件中定义的(但这是可选的)
还可以使用关键字static将函数的链接性设置为内部的,使之只能在一个文件中使用,必须同时在原型和函数定义中使用关键字static。
语言链接性(c++ language linkage)
链接程序要求每个不同的函数都有不同的符号名,在C语言中,一个名称只对应一个函数。但是C++中,一个名称可能对应多一个函数。必须将这些函数翻译为不同的符号名称,因此C++编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称。链接程序在寻找于C++函数调用的匹配的函数时,使用的方法与C语言不同,但如果要在C++程序中使用C库预编译的函数,可以用函数原型来指出要使用的约定:
extern "C" void spiff(int); //use C protocal for name look-up
extern void spoff(int);//use C++ protocal for name look-up
extern "C++" void spaff(int);//use C++ protocal for name look-up
存储方案和动态分配
前面的几种内存分配方案不使用与C++运算符new(或C函数malloc)分配的内存,这种内存被称为动态内存,动态内存由new和delete控制,而不是由作用域和链接性规则控制。与自动内存不同,动态内存不是LIFO,其分配和释放顺序要取决于new和delete在何时以何种方式被使用。
编译器使用三块独立的内存:一块用于静态变量,一块用于自动变量,一块用于动态存储。
存储方案不适用于动态内存,但使用与用来跟踪动态内存的自动和静态指针变量。
使用new运算符初始化:
int *pi = new int (6);
double * pd = new double(99.99);
struct where{double x;double y;double z;};
where *one = new where{2.5,5.3,7.2};
int * ar = new int[4]{2,3,6,7};
placement new
#include <new>
p1 = new chaff;//new a structure in heap
p1= new (buffer) chaff//new a structrure in the buffer
名称空间
声明区域(declaration region)可以在其中进行声明的区域
潜在作用域:从声明点开始,到其声明区域的结尾。
名称空间可以是全局的,也可以位于另一个名称空间中。但不能位于代码块中。在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量)
using声明 指定特定的标识符可用
using Jill::fetch;
main中的using声明Jill::fetch将fetch添加到main()定义的声明区域中。
在函数的外面使用using声明时,将把名称添加到全局名称空间中。
using编译指令 使整个名称空间可用
using namespace xxx ;
类
- 访问控制
private public protected 描述了对类成员的访问控制
使用类对象的程序都可以直接访问共有部分。但只能通过共有函数(或者友元函数)来访问对象的私有成员。
防止程序直接访问数据称为数据隐藏
封装:将实现细节放在一起,并且将它们与抽象分开
数据隐藏是一种封装,将类函数定义和类声明放在不同的文件中也是一种封装 - 通常将数据放在私有部分,将类接口的成员函数放在共有部分。
类函数可以访问类的private组件 - 内联函数
其定义位于类声明中的函数都将自动成为内联函数
也可以在类声明之外定义成员函数,并使其成为内联函数。只需在类实现部分中定义函数时使用inline限定符即可 - 类的构造函数和析构函数
构造函数 没有返回值
Stock(const string & co, long n = 0 , double pr = 0.0);
成员函数:
const Stock& topval(const Stock & s) const; - 对象数组
Stock mystuff[4];
···
const int STKS = 10;
Stock stocks[STKS] = {
Stock("NanoSmart",12.5,20);
Stock(),
Stock("Monolithic Obelisks",130,3.25);
}
···
初始化对象数组的方案:首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。要创建类对象数组,则这个类必须有默认构造函数 - 运算符的重载:
Time operator+(cons Time & t) const;
total = coding.operator+(fixing);
total = coding + fixing; - 重载的限制:
1 重载后的运算符必须至少有一个操作数是用户定义的类型
2 不能将减法运算符重载为计算两个double值的和,而不是它们的差。
不能修改运算符的优先级
3 不能创建新的运算符
4 不能重载下面的运算符:
sizeof()
.
::
?:
typeid
const_cast dynamic_cast reinterpret_cast static_cast
5 表中大多数运算符都可以通过成员函数或非成员函数进行重载:但下面的运算符只能通过成员函数进行重载:
=
()
[]
->
Time Time::operator*(double mult) const
{
}
- 类的自动转换与强制类型转换
只有接收一个参数的构造函数才能作为转换函数
Stonewt(double lbs);
Stonewt mycat;
mycat = 19.6;
程序将使用构造函数Stonewt(double)来创建一个临时的Stonewt对象,并将19.6作为初始化值。随后,采用逐成员赋值的方式将该临时对象的内容复制到mycat中。这一过程称为隐式转换。
如果第二个参数提供默认值,它便可用于转换int.
自动特性并非总是合乎需要的。因此,C++新增了关键字explicit,用于关闭这种自动特性:也就是说,可以这样声明构造函数:
explicit Stonewt(double lbs)
这样将关闭上述示例中介绍的隐式转换,但仍然允许显式转换,即显示强制类型转换:
Stonewt mycat;
mycat = 19.6;//not valid if Stonewt(double) is declared as explicit
mycat = Stonewt(19.6);//ok, an explicit conversion
- 使用特殊的C++运算符函数---转换函数
转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。如果定义了Stonewt到double的转换,就可以使用下面的转化:
Stonewt wolfe(285.7)
double host = wolfe;
//double host = double(wolfe)
operator typeName();
转换函数必须是类方法,转换函数不能指定返回类型,转换函数不能有参数,但是有返回值
operator double();
可以将转换运算符声明为显示的:
···
explicit operator int() const;
explicit operator double() const;
···
友元
-
友元函数
创建友元函数第一步是将其原型放在类声明中,并在类声明前面加上关键字friend
friend Time operator*(double m, const Time & t);
//在定义中不要使用friend
- 常用的友元:重载<<运算符 cout<<
ostream & operator<<(ostream & os, const Time & t)
{
os << t.hours << " hours" << t.minutes << "minutes";
return os;
}
cout << trip;
ofstream fout;
fout.open("savetime.txt");
Time trip(12,40);
fout << trip; //类的继承属性让ostream引用能指向ostream和ofstream对象
友元类
友元成员函数
特殊成员函数
默认构造函数,如果没有定义构造函数
如果定义了构造函数,C++将不会定义默认构造函数,如果希望在创建对象时不显示地对它进行初始化,则必须显示地定义默认构造函数,这种构造函数没有任何参数
带参数的构造函数也可以时默认构造函数,只要所有参数都有默认的值
默认析构函数,如果没有定义
复制构造函数,如果没有定义
新建一个对象并将它初始化为同类现有对象时,复制构造函数都将被调用
StringBad ditto(motto);
StringBad metto = motto;//
StringBad also = StringBad(motto);
//直接使用赋值构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also
StringBad* pStringBad = new StringBad(motto);
//初始化一个匿名对象,然后把新对象的地址赋值给pstring指针
定义一个显式复制构造函数以解决问题:
进行深度复制(复制构造函数应当复制字符串并将副本的地址赋给str成员)
默认构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
赋值运算符,如果没有定义
解决办法:进行深度复制
String::String(const String & st)
{
num_strings++;
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
}
String & String::operator=(const String & st)
{
if(this == &st)
return *this;
delete [] str;
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
return *this;
}
地址运算符,如果没有定义
(c++11移动构造函数
移动赋值运算符)
- 静态类成员函数:
可以将成员函数声明为静态的(函数声明包含关键字static)!!!
不能通过对象调用静态成员函数,静态成员函数甚至不能使用this指针。如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符来调用它。
其次,由于静态成员函数不与特定的对象相关联。因此只能使用静态成员数据。
可以重载赋值运算符,使之能接收对象的银行和直接使用常规字符串。
成员初始化列表
继承 --3种继承关系(公有继承,保护继承,私有继承)
公有继承 is-a关系 (is-a-kind-of)
class RatedPlayer : public TableTennisPlayer
{
...
}
公有派生,派生类对象包含积累对象。
使用公有派生,基类的公有成员将称为派生类的公有成员;基类的私有部分也将称为派生类的一部分,但只能通过基类的公有和保护方法访问。
派生类需要自己的构造函数
派生类可以根据需要添加额外的数据成员和成员函数。
派生类构造函数必须使用基类构造函数
RatedPlayer::RatedPlayer(unsigned int r,const string & fn,
const string & ln, book ht) : TableTennisPlayer(fn, ln, ht)
{
rating = r;
}
- 有关派生类构造函数的要点如下:
首先创建基类对象
派生类构造函数应通过成员初始化列表传递给基类构造函数
派生类构造函数应初始化派生类新增的数据成员 - 派生类和基类的关系:
派生类对象可以使用基类的方法
基类指针可以在不进行显示类型转换的情况下指向派生类对象。基类引用可以在不进行显示类型转换的情况下引用派生类对象。
多态公有继承
希望同一个方法在派生类和基类中的行为是不同的。方法的行为取决于调用该方法的对象。这种较复杂的行为称为多态--具有多种形态
有两种机制可以实现多态的公有继承:
在派生类中重新定义基类的方法
- 使用虚方法
//ViewAcct()不是虚函数
Brass dom("Dominic Banker",11224,4183.45);
BrassPlus dot("Dorothy Banker",12118,2592.00);
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct();//调用Brass的
b2_ref.ViewAcct();//调用Brass的
//使用虚函数
Brass dom("Dominic Banker",11224,4183.45);
BrassPlus dot("Dorothy Banker",12118,2592.00);
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct();//调用Brass的
b2_ref.ViewAcct();//调用BrassPlus
1 方法在基类声明为虚后,它在派生类中将自动成为虚方法。然而,在派生类声明中使用关键字virtual来指出哪些函数是虚函数也不失为一个好方法。
2 基类声明一个虚析构函数,这样做确保释放派生类对象时,按照正确的顺序调用析构函数。如果虚构函数不是虚的,则只调用对应于指针类型的析构函数。
Brass * p_clients[CLIENTS];
将派生类引用或指针转换为基类引用或指针被称为向上强制转换,这使公有继承不需要进行显示类型转换。
编译器对非虚方法使用静态联编
对虚方法使用动态联编
- 虚函数的工作原理
编译器处理虚函数的方法:
给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table, vtbl)。虚函数表中存储了为 类对象进行声明的虚函数地址。
调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。
overload(重载)相同函数名 不同参数
overwrite子类重写父类方法
在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管参数特征标如何。
protected
派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员
抽象基类
abstract base class, ABC
C++通过使用春虚函数提供未实现的函数。纯虚函数声明的结尾处为 =0
当类声明中包含纯虚函数时,则不能创建该类的对象。包含纯虚函数的类只用作基类。纯虚函数也可以有定义。
可以将ABC看作是一种必须实施的接口,ABC要求具体派生类覆盖其纯虚函数--迫使派生类遵循ABC设置的接口规则。
保护继承
私有继承
堆(heap)
- vector set
strlen返回字符串长度
指针
- 指针和const
第一种:让指针指向一个常量对象,这样可以防止使用该指针来修改所指向的值。
int age = 39;
const int * pt = &age; 指针指向const int
不能通过pt来修改age的值,但是可以通过age直接修改age的值,因为age不是const。
第二种:将指针本身声明为常量,这样可以防止改变指针指向的位置
int const * pt2 = &age; const指针
int * const pt2 = &age; - 函数指针
函数的地址:函数名
声明函数指针:
double pam(int);//函数原型
double (*pf) (int);//指向函数的指针,1个int参数,返回double;
pf=pam; 让函数指针指向函数
注意区分
double* pf (int) 和 double(pf)(int)
void estimate(int lines,double (pf) (int));//函数原型 第二个参数是一个函数指针
estimate(50,pam);
使用指针来调用函数
使用指针来调用被指向的函数。(pf)扮演的角色与函数名相同
double x = pam(4); //calling pam() using the function name
double y = (pf)(5);//calling pam() using the pointer pf
函数指针数组
const double* (*pa[3])(const double ,int) = {f1,f2,f3};
const double * px = (pa[0])(av,3);
double y = (pa[1])(av,3);
typedef const double (p_fun)(const double ,int);
p_fun p1 = f1; p1指向f1
p_fun pa[3] = {f1,f2,f3}; pa是一个array of 3 function pointers
p_fun (pd)[3] = &pa; pd points to an array of 3 function pointers.
- 浮点数的写法 科学计数法 10的几次方
10e-6 - 表达式中的转换
当同一个表达式中包含两种不同的算术类型时,C++将执行两种转换:
1 自动转换
short chicken = 20;
short ducks = 35;
short fowl = chicken + ducks;
在执行第三句的时候,程序把short先转化为int进行计算,然后将结果转换为short.
2 整型提升
4种类型转换
C++还引入了4个强制类型转换运算符
- dynamic_cast
- const_cast
- static_cast 将值从一种数值类转换为另外一种数值类型
int thorn;
static_cast<long> (thorn)
static_cast<typeName> (value) - reinterpret_cast
函数
- 内联函数
在函数声明前加上关键字inline
在函数定义前加上关键字inline - 引用变量
-
函数多态(函数重载)
同名函数,但是参数列表不同
多态:多种形式 -
函数模板:(通用编程)//这种是函数多态的升级 上面的函数多态对不同类型的参数还需要逐个写出来。模板就不用了,直接替代
通用的函数描述
使用泛型来定义函数,其中的泛型可用具体的类型替换。
template <typename AnyType> 建立一个模板 typename可以用class代替,template和typename是关键字 T来代替AnyType也是可以的
void Swap(AnyType &a, AnyType &b) 模板不会创建任何函数。当需要交换int函数时,编译器将按模板模式创建这样的函数
{
AnyType temp;
temp = a;
a = b;
b = temp;
} - 重载的模板
被重载的函数特征标必须不同
template <typename T>
void Swap(T& a, T& b);
template <typename T>
void Swap(T *a, T *b,int n); - 为特定类型提供具体化的模板
显示具体化(explicit specialization) 当编译器中找到与函数调用匹配的具体化定义时,将使用该定义而不再寻找模板。
对于给第那个的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及它们的重载版本
//not template function prototype
void Swap(job &, job &);
//template prototype
template<typename T>
void Swap(job &, job &);
template <> void Swap<job>(job&,job&);
template <> void Swap(job&,job&);//不要使用Swap模板来生成函数定义,而应该使用专门为int类型显式定义的函数定义
非模板版本>显示具体化>模板生成的版本
函数调用Swap(i,j)导致编译器生成Swap的一个实例,该实例使用int类型
隐式实例化(implicit instantiation),使用模板生成函数定义
显示实例化(explicit instantiaion) 可以直接命令编译器创建特定的实例
template void Swap<int>(int &,int &);//该声明的意思是“使用Swap()模板生成int类型的函数定义”
隐式实例化,显示实例化,显示具体化统称为具体化。相同之处:标识的都是具体类型的函数定义,而不是通用描述 - 编译器使用哪个函数版本:
完全匹配(常规函数优先于模板)
提升转换(char/short -> int, long->double )
标准转换(int -> char, long ->double)
用户定义的转换,如类声明中定义的转换。 - 堆栈
中缀表达式
后缀表达式 求值策略:从左向右扫描,逐个处理运算数和运算符号
先放进去后拿出来 堆栈
堆栈(stack)具有一定操作约束的线性表。只在一端(栈顶,Top)做插入、删除
插入数据:入栈(Push)
删除数据:出栈(Pop)
后入先出:Last in First Out(LIFO)
放在桌子上的一叠碗
堆栈可以用来倒序输出