- c++和c一样将数组名视为指针,如果在数组名前加上‘&’则返回整个数组的地址
typedef const double* (*func)(const *value);//func now a type name
- 引用必须在声明时初始化
-
c++函数的作用域可以是整个类或整个名称空间(包括全局的),但不能是局部的(因位不能在代码块内定义函数,如果函数的作用域位局部,则只对它自己是可见的,因此不能被其他函数调用。这样的函数将无法运行)
-
c++比c语言更进了一步–它提供了作用域解析运算符(:😃。放在变量名前面时,该运算符表示使用变量的全局版本。
-
只有未使用extern关键字的声明才能进行初始化
- 类的构造函数的参数名不能与类成员相同,否则将造成混乱
- 默认参数不能同时出现在函数的声明和定义中(函数定义中不需要再写默认函数)
new type[length]<->delete [] value||new type<->delete value
- 当使用定位new运算符为类开辟空间后,在使用delete删除空间之前,应该显示的调用该类的析构函数,且调用的顺序应该与在该空间内“定义”的类的顺序相反
value->~Class_Name()
- c++中结构体是一种其成员在默认情况下为公有的类
- initializer_list<>可以支持列表初始化语法:
string(initializer_list<char> p)==>string test = {'a','b','c','d'}
函数模板
typedef struct{
int a;
int b;
}TEST;
//template prototype
template <typename T>
void Swap(T &arg1,T &arg2){
//function body
T temp = arg1;
arg1=arg2;
arg2=temp;
}
//模板重载
template <typename T>
void Swap(T &arg1,T &arg2, int n){
//function body
}
显式具体化(explicit specialization)
可以提供一个具体化的函数定义(比如之前定义了交换两个结构体的函数模板,现在只想交换结构体中的一部分)当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板
//explicit specialization for TEST
//相当于给函数模板编写一种用于特殊类型的模板函数
//template <> void Swap(TEST &a,TEST &b)
template <> void Swap<TEST>(TEST &a,TEST &b){//为TEST结构体显式定义了Swap函数
//function body
}
//Swap<TEST>中的<TEST>是可选的
//显式实例化
//template void Swap(TEST &,TEST &);
实例化和具体化、显式实例化显式具体化
- 实例化:
函数模板本身并不会生成函数定义,它只是一个用于生产函数定义的一个方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例(instantiation 隐式实例化)
- 具体化:
隐式示例化、显式实例化和显式具体化统称为具体化
- 显式具体化:
- 显式实例化:
关键字decltype
//decltype(expression) var
int x;
decltype(x) y;//make y the same type as x
decltype(x+y) xy;
//when expression is 左值
decltype((x)) c = x; // c is int &
int indeed(int x);
decltype(indeed(3)) m;//m is type int
//并不会实际调用函数。编译器通过查看函数的原型来获悉返回类型,而无需实际调用函数
后置返回类型(trailing return type)
auto func(int x,float y)->double;
//这将返回类型移到了参数声明后面。->double被称为后置返回类型,其中auto是一个占位符
template<typename A, typename B>
auto sum(A a, B b) ->decltype(a + b){
}
模板匹配与具体化函数匹配的优先级比较
内存模型和名称空间
使用new运算符初始化
int *pi = new int (6);//set *pi to 6
struct where {double x;double y;double z};
where *one = new where {2.5,5.3,7.2};
int *ar = new int [4] {2,4,6,7};
定位new运算符
通常,new负责在(heap)中找到一个足以能够满足要求的内存块
从指定位置分配内存
char buffer1[50];
char buffer2[500];
struct chaff{
char dross[20];
int slag;
};
chaff *p1,*p2;
int *p3,*p4;
p1 = new chaff;//place structure in heap
p3 = new int[20];//place int array in heap
p2 = new (buffer1) chaff;//place structure in buffer1
p4 = new (buffer2) int[20];//place array in buffer2
名称空间
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。
namespace Name{
//type Elements;
//void function(){}
}
//可以对名称空间取别名
namespace N = Name;
说明符和限定符
- 说明符
auto | register | static | extern | thread_local | mutable |
---|---|---|---|---|---|
自动推断类型 | 用于在声明中指示寄存器存储 | 引用声明 | 指出变量的持续性与其所属线程的持续性相同 |
2.限定符
const | volatile | mutable |
---|---|---|
内存被初始化后程序不能对它进行修改 | 即使程序代码没有对内存单元进行修改其值也可能发生变化(作用是为了改善编译器的优化能力) | 即使结构或类变量为const,其某个成员也可以被修改。 |
struct data{
char name[30];
mutable int accesses;
}
const data test = {"test",0};
strcpy(test.name,"hello");//not allowed
test.accesses++;//allowed
语言链接性
#对象和类
class World{
float mass;//private by default
char name[20];//private by default
public:
void TellTheTruth();
void Inline(){
std::cout<<"This is a inline function"<<std::endl;
}
}
World::TellTheTruth(){
//fuction body
}
//当然也可以使用inline关键字使其成为内联函数
//inline World::TellTheTrue(){Function body}
const Stock & topval(const Stock &s) const;
//第一个const表明返回一个const引用
//第二个const表明表明函数中不可以修改显式访问的s
//第三个const表明函数中不可以修改隐式访问的调用者
作用域内枚举
enum class egg{Small, Medium, Large};//可以将class换为struct
enum class t_shirt{Small, Medium, Large};
//避免了egg中的Small和t_shirt中的Small发生冲突
使用类
运算符重装
//operator +()
//operator 关键字之后跟上要重载的运算符
友元
- 类的友元函数是非成员函数,其访问权限与成员函数相同
创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend
class Time{
public:
Time(int a,int b){
//function body
}
//Time operator*(const double& m)const;
friend Time operator*(const double& m,const Time &t);
}
Time operator*(double m,const Time &t){//不要在定义中使用关键字friend
//function body
}
- 因为友元不是类成员,所以不能使用作用域解析运算符来指出要使用哪一个函数(那么在派生类中如何使用基类的友元呢?强制类型转换)
//假设hasDMA是从baseDMA派生而来,在这两个类中都有友元:重载的<<运算符
std::ostream & operator<<(std::ostream & os,const hasDMA & hs){
os<<(const baseDMA &)hs;//强制类型转换,使用基类友元
//......
}
类的自动转换和强制类型转换
将其他类型转换到类
class Stonewt1{
public:
Stonewt1(int a){
//function body
}
}
Stonewt1 myCat1;
myCat1 = 12;//use Stonewt1(int) to convert 12 to Stonewt
//只有接受一个参数的构造函数才能作为转换函数
class Stonewt2{
public:
explicit Stonewt2(int a){//关闭隐式转换
//function body
}
}
Stonewt2 myCat2;
myCat2 = 12;//not valid if Stonewt2(int) is declared as explict
myCat2 = Stonewt2(12);//ok,an explict conversion
myCat2 = (Stonewt2)12;//ok,old form for explict typecast
将类转换到其他类型
//转换函数
//要转换为typeName类型,需要使用这种形式的转换函数:
//operator typeName(); 必须是类方法,不能指定返回类型,不能有参数
class Stonewt1{
public:
Stonewt1(int a){
//function body
}
operator int() const{
//function body
}
}
赋值构造函数与重载赋值运算符
//赋值构造函数(新建一个对象并将其初始化为同类现有对象时、按值传递和返回对象时都将调用赋值构造函数)
class StringBad{
public:
StringBad(){}
//默认复制构造函数逐个复制非静态成员,复制的是成员的值(浅复制)
//Class_Name(const Class_Name&);
StringBad(const StringBad&);//复制构造函数
//Class_Name& operator=(const Class_Name&);
StringBad& operator=(const StringBad&);//将已有的对象赋给另一个对象时调用
//赋值运算符和复制构造函数的区别就在于要赋值的对象是否存在,若已经存在则调用赋值运算符,否则赋值构造函数
}
初始化列表语法
class Queue{
private:
const int Qsize;
public:
//...
}
//Queue中存在常量Qsize,可以对它进行初始化,但不能对它赋值
//从概念上来说,调用构造函数时,对象将在括号中的代码执行之前被创建(构造函数将导致程序首先给4个成员变量分配内存。然后程序流程进入到括号中,使用常规的赋值方式将值存储到内存中)
//使用初始化列表(对类来说使用初始化列表的语法效率更高)
Queue::Queue(int qs):Qsize(qd){
//function body
}
//只有构造函数可以使用这种初始化列表语法
类继承
- c++有三种继承方式:公有继承、私有继承、保护继承
- 如果在派生类中重新定义了基类的方法,那么将隐藏所有同名的基类方法,重新定义基类的方法并不是重载;解决办法:在派生类中重新定义基类方法
公有继承 | 私有继承 | 保护继承 |
---|---|---|
public | private | protected |
基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问 | 基类的公有成员和保护成员都将成为派生类的私有成员 | 基类的公有成员和保护成员都将成为派生类的保护成员 |
访问控制
public | private | protected |
---|---|---|
protected与private类似,区别在于在派生类中保护成员的行为与公有成员相似 |
class RatedPlayer : public TableTennisPlayer{
}
//公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问
//创建派生类对象时,程序首先创建基类对象。从概念上来说,这意味着基类对象应当在程序进入派生类构造函数之前被创建(列表初始化)
RatedPlayer::RatedPlayer(type1 value1,type2 value2...) : TableTennisPlayer(Vale1,Value2...)
{
//function body
}
//必须首先创建基类对象,如果不调用基类构造函数,程序将使用默认的构造函数,以下代码等效
RatedPlayer::RatedPlayer(type1 value1,type2 value2...)//what if no initializer list
{
//function body
}
RatedPlayer::RatedPlayer(type1 value1,type2 value2...) : TableTennisPlayer()
{
//function body
}
- 当初始化列表包含多个项目时,这些项目被初始化的顺序为他们被声明的顺序
虚方法
- 在基类方法声明中使用关键字virtual可使该方法在基类以及所有的派生类(包括派生类的派生类)中都是虚的4
-
友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数
声明一个虚方法在前面加上关键字virtual(只用于类声明的方法原型中,和friend类似)
如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。
如果要在派生类中从新定义基类的方法,通常应将基类方法声明为虚的
class Brass
{
private:
//...
public:
Brass(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0);
void Deposit(double amt);
virtual void Withdraw(double amt);
virtual void ViewAcct() const;
virtual ~Brass() {}
};
class BrassPlus : public Brass
{
private:
//...
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.11125);
BrassPlus(const Brass & ba, double ml = 500,
double r = 0.11125);
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
//...
};
在派生类方法中,标准技术是使用作用域解析运算符来调用基类方法
void BrassPlus::ViewAcct()const{ ViewAcct();//将导致无限递归 Brass::ViewAcct();//调用父类方法 }
纯虚函数(pure virtual function)
-
c++通过使用纯虚函数提供未实现的函数。纯虚函数的声明结尾处为=0;当类声明中包含纯虚函数时,则不能创建该类对象。这里的理念是,包含纯虚函数的类只能用作基类。要成为真正的ABC(abstract base class),必须至少包含一个纯虚函数。
多重继承
- 多重继承中,除非特别指出,否则编译器将认为是私有派生
- 虚基类(在继承的时候使用virtual):防止在继承中出现多个基类的拷贝,当基类为虚的时候需要显示的调用虚基类的构造函数,否则将使用默认的构造函数(c++在基类是虚的时候禁止信息通过中间类自动传递给基类)
- 当多重继承从不同基类那里继承了同名的方法时,在调用的时候将导致函数调用的二义性,可以采用作用域解析云算法来避免
// studenti.h -- defining a Student class using private inheritance
#ifndef STUDENTC_H_
#define STUDENTC_H_
#include <iostream>
#include <valarray>
#include <string>
class Student : private std::string, private std::valarray<double>
{
private:
typedef std::valarray<double> ArrayDb;
// private method for scores output
std::ostream & arr_out(std::ostream & os) const;
public:
Student() : std::string("Null Student"), ArrayDb() {}
//使用包含时将使用对象名来调用方法,而使用私有继承时将使用类名和作用域解析运算符来调用方法
explicit Student(const std::string & s)
: std::string(s), ArrayDb() {}
explicit Student(int n) : std::string("Nully"), ArrayDb(n) {}
Student(const std::string & s, int n)
: std::string(s), ArrayDb(n) {}
Student(const std::string & s, const ArrayDb & a)
: std::string(s), ArrayDb(a) {}
Student(const char * str, const double * pd, int n)
: std::string(str), ArrayDb(pd, n) {}
~Student() {}
double Average() const;
double & operator[](int i);
double operator[](int i) const;
const std::string & Name() const{
//使用私有继承时,使用强制类型转换来访问内部的string对象
return (const std::string &)*this;
}
// friends
// input
friend std::istream & operator>>(std::istream & is,
Student & stu); // 1 word
friend std::istream & getline(std::istream & is,
Student & stu); // 1 line
// output
friend std::ostream & operator<<(std::ostream & os,
const Student & stu);
};
#endif
类模板
- 类模板的声明和实现必须放在一起
- 仅在程序包含模板并不能生成模板类,而必须请求实例化
-template <class T, int n>
其中的int n称为表达式参数(整型、枚举、引用、指针),实例化模板时,用作表达式参数的值必须是常量表达式 - 可以为类型参数提供默认值
template <class T1, class T2 = int>
- 模板可以嵌套或将一个模板用作另一个模板的成员
- 模板类也可以有友元
template <class Type>
class Stack
{
private:
enum {SIZE = 10}; // default size
int stacksize;
Type * items; // holds stack items
int top; // index for top stack item
public:
explicit Stack(int ss = SIZE);
Stack(const Stack & st);
~Stack() { delete [] items; }
bool isempty() { return top == 0; }
bool isfull() { return top == stacksize; }
bool push(const Type & item); // add item to stack
bool pop(Type & item); // pop top into item
Stack & operator=(const Stack & st);
};
template <class Type>
Stack<Type>::Stack(int ss) : stacksize(ss), top(0)
{
items = new Type [stacksize];
}
//嵌套模板的语法
//template <class Type1>
// template <class Type2>
//......
template<typename T>
using arrtype = std::array<T,12>;//将arrtype定义为一个模板别名
typedef const char* ccp;
using ccp = const char *;//这两句效果相同
友元类
- 友元类的所有方法都可以访问原始类的私有成员和保护成员(也可以只将特定的成员指定为另一个类的友元[友元函数])
- 使用前向声明解决类定义顺序的问题
friend class classname;//将类声明为友元
friend ReturnType ClassNem::FuncName(/*value list*/);//将一个类中的成员函数声明为另一个类中的友元
异常机制try、catch、throw
- 在函数后面加上noexcept表明函数不会抛出异常(noexcept()函数用于检测操作数是否会引发异常)
- 异常总是创建一个临时拷贝,哪怕异常规范和catch块中指定的是引用
- 当程序引发未捕获的异常时(在没有try块或没有匹配catch块时),在默认情况下将导致程序异常终止(程序会首先调用terminate函数,默认情况下terminate函数调用abort函数),可以指定terminate函数调用的函数来修改这种行为。调用set_terminate()函数(需要包含exception头文件);同时还存在一个set_unexpected()函数用于设置如果函数引发了异常规范中没有规定的异常将调用的函数(默认unexpected()函数调用terminate())
try{
//....
}
catch (/*value*/){
}
以下是一个完整的例子:
//error5.cpp -- unwinding the stack
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#include <string>
#include "exc_mean.h"
class demo
{
private:
std::string word;
public:
demo (const std::string & str)
{
word = str;
std::cout << "demo " << word << " created\n";
}
~demo()
{
std::cout << "demo " << word << " destroyed\n";
}
void show() const
{
std::cout << "demo " << word << " lives!\n";
}
};
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
double means(double a, double b);
int main()
{
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
{
demo d1("found in block in main()");
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try { // start of try block
z = means(x,y);
cout << "The mean mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Enter next pair: ";
} // end of try block
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg)
{
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", "
<< hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
}
d1.show();
}
cout << "Bye!\n";
// cin.get();
// cin.get();
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
throw bad_hmean(a,b);
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b)
{
if (a < 0 || b < 0)
throw bad_gmean(a,b);
return std::sqrt(a * b);
}
double means(double a, double b)
{
double am, hm, gm;
demo d2("found in means()");
am = (a + b) / 2.0; // arithmetic mean
try
{
hm = hmean(a,b);
gm = gmean(a,b);
}
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
std::cout << "Caught in means()\n";
throw; // rethrows the exception
}
d2.show();
return (am + hm + gm) / 3.0;
}
运行阶段类型识别RTTI(Runtime Type Identification)
c++有3个支持RTTI的元素:
dynamic_cast | typeid | type_info |
---|---|---|
如果可能的话,dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,否则改运算符返回0——空指针 | 返回一个指出对象类型的值 | 存储了有关特定类型的信息。 |
- dynamic_cast不能回答“指针指向的是哪类对象?”但能回答"是否可以将对象 的地址赋给特定类型的指针"
- typeid运算符返回一个对tpye_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类(重载了==和!=同时还包含以name()成员[通常是类的名称])。
typeid(Magnificent)==typeid(*pg)
如果pg是个空指针,程序将引发bad_typeid异常。
class Grand{/*......*/};
class Superb : public Grand{};
class Magnificent:public Superb{};
Grand *pg = new Grand;
Grand *ps = new Superb;
Grand *pm = new Magnificent;
Superb *pm = dynamic_cast<Superb *>(pg);//如果pg可以安全的转换为Superb *,运算符将返回对象的地址,否则将返回空指针
typeid(test1) == typeid(*test2)
cout << "Now processing type " << typeid(*pg).name() << "\n";
类型转换运算符
dynamic_cast | const_cast | static_cast | reinterpret_cast |
---|---|---|---|
用于执行只有一种用途的类型转换,即改变值为const或volatile(可以修改const类型) | static_cast<typename>(expression)仅当typename可以隐式转换为expression所属的类型或反过来可以隐式转换是才合法(使得类继承向下转换合法) |
//const_cast<typename>(expression)
Heigh bar;
const Heigh *pbar = &bar;
Heigh *pb = const_cast<Heigh*> (pbar);
//static_cast<typename>(expression) 将整型转换为枚举型(使得向下转换合法包括double->int、float->long)
//reinterpret_cast<typename>(expression)
标准模板库
- string实际上是模板具体化basic_string<char>的一个typedef
移动构造函数(move constructor)
智能指针模板类
智能指针是行为类似于指针的类对象;头文件memory
-
程序试图将一个unique_ptr赋给另一个值时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。
-
c++有一个标准库函数str::move(),让您能够将一个unique_ptr赋给另一个。
- 只有unique_ptr能有使用new和new []的版本,既只有unique_ptr有能使用数组
auto_ptr | unique_ptr | shared_ptr |
---|---|---|
c++98 | c++11 | c++11 |
建立所有权概念,对于特定的对象,只能有一个智能指针能够拥有它(防止同一个智能指针指向同一个对象在析构的时候将导致删除两次) | 允许多个智能指针指向同一个对象 |
// smrtptrs.cpp -- using three kinds of smart pointers
#include <iostream>
#include <string>
#include <memory>
class Report
{
private:
std::string str;
public:
Report(const std::string s) : str(s) { std::cout << "Object created!\n"; }
~Report() { std::cout << "Object deleted!\n"; }
void comment() const { std::cout << str << "\n"; }
};
int main()
{
{
std::auto_ptr<Report> ps (new Report("using auto_ptr"));
ps->comment(); // use -> to invoke a member function
}
{
std::shared_ptr<Report> ps (new Report("using shared_ptr"));
ps->comment();
}
{
std::unique_ptr<Report> ps (new Report("using unique_ptr"));
ps->comment();
}
// std::cin.get();
return 0;
}
标准模板库
- 为区分++运算符的前缀版本和后缀版本,c++将operator++作为前缀版本,将operator++(int)作为后缀版本。
- STL定义了5种迭代器:输入迭代器、输出迭代器、正向迭代器、双向迭代器、随机访问迭代器
- 不能将任何类型的对象存储在容器中,具体的说,类型必须是可复制的和可赋值的。基本类型满足这些要求;只要类定义没有将赋值运算符和复制构造函数声明为私有的,则也满足这种要求。
for(auto p = test.begin();p!=test.end();p++)
view(*p);
for_each(test.begin(),test.end(),view);
random_shuffle(test.begin(),test.end());//打乱test
//sort函数要求容器支持随机访问、
#include<iterator>
outstream_iterator<int,char>out_iter(cout," ");
//int是被发送给输出流的类型,char是输出流使用的字符类型。构造函数的第一个参数指出了要使用的输出流,它也可以是用于文件的输出流,最后一个字符串参数是在发送给输出流的每个数据项后显示的分隔符
//out_iter迭代器现在是一个接口
*out_iter++=15;//works like cout<<15<<" ";
//同样还包括一个instream_iterator模板
//除了ostream_iterator和istream_iterator之外,头文件iterator还提供了其他一些专用的预定义迭代器类型。它们是reverse_iterator、back_insert_iterator、front_insert_iterator、insert_iterator
back_insert_iterator<vector<int>> back_iter(test);
//这些迭代器将容器类型作为模板参数,将实际的容器标识符作为构造函数参数。上述为名为test的vector<int>容器构建一个back_insert_iterator
//以下是一个完整的例子
// inserts.cpp -- copy() and insert iterators
#include <iostream>
#include <string>
#include <iterator>
#include <vector>
#include <algorithm>
void output(const std::string & s) {std::cout << s << " ";}
int main()
{
using namespace std;
string s1[4] = {"fine", "fish", "fashion", "fate"};
string s2[2] = {"busy", "bats"};
string s3[2] = {"silly", "singers"};
vector<string> words(4);
copy(s1, s1 + 4, words.begin());
for_each(words.begin(), words.end(), output);
cout << endl;
// construct anonymous back_insert_iterator object
copy(s2, s2 + 2, back_insert_iterator<vector<string> >(words));
for_each(words.begin(), words.end(), output);
cout << endl;
// construct anonymous insert_iterator object
copy(s3, s3 + 2, insert_iterator<vector<string> >(words, words.begin()));
for_each(words.begin(), words.end(), output);
cout << endl;
// cin.get();
return 0;
}
reverse_iterator | back_insert_iterator | front_insert_iterator | insert_iterator |
---|---|---|---|
用在rbegin的地方 | 将元素插入容器尾部 | 将元素插入到容器前端 | 将元素插入到构造函数的参数指定的位置前面 |
函数对象
函数对象也叫函数符。函数符是可以以函数方式与()结合使用的任意对象。这包括函数名、指向函数的指针和重载了()运算符的类对象
for_each(test.begin(),test.end(),view)
for_each函数将指定的函数用于区间中的每一个对象
//假设test1和test2和out均是vector<double>,现在要将test1[i]和test2[i]相加结果保存到out[i]
#include<functional>
transform(test1.begin(),test1.end(),test2.begin(),out.begin(),plus<double>());
对于所有内置的算术运算符、关系运算符和逻辑运算符,STL都提供了等价的函数符
运算符 | + | - | * | / | % | - | == | != | > | < | >= | <= | && | || | ! |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
相应的函数 | plus | minus | multiplies | divides | modulus | negate | equal_to | not_equal_to | grater | less | grater_equal | less_equal | logical_and | logical_or | logical_not |
//multiplies()函数可以执行乘法运算,但是multiplies是一个二元函数,因此需要一个函数适配器将一个接受两个参数的函数符转换为接受一个参数的函数符
//STL适用男binder1st和binder2nd类自动完成这一过程,他们将自适应二元函数转换为自适应一元函数。
//假设test1是vector<double>,现在要将test1[i]*3,结果保存到out
#include<functional>
transform(test1.begin(),test1.end(),out.begin(),binder1st(plus<double>(),3));
//binder2nd与此类似,只是将常数赋给第二个参数而不是第一个参数
//string类不是STL的组成部分;
//next_permutation()算法将区间内容转换为下一种排列方式。对于字符串按照字母顺序递增的顺序进行。如果成功返回true;如果区间已经处于最后的序列中,则该算法返回false
//以下是一个使用STL完整的例子
//让用户输入单词,最后希望得到一个按用户输入顺序排列的单词列表、一个按字母顺序排列的单词列表
//usealgo.cpp -- using several STL elements
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include <algorithm>
#include <cctype>
using namespace std;
char toLower(char ch) { return tolower(ch); }
string & ToLower(string & st);
void display(const string & s);
int main()
{
vector<string> words;
cout << "Enter words (enter quit to quit):\n";
string input;
while (cin >> input && input != "quit")
words.push_back(input);
cout << "You entered the following words:\n";
for_each(words.begin(), words.end(), display);
cout << endl;
// place words in set, converting to lowercase
set<string> wordset;
transform(words.begin(), words.end(),
insert_iterator<set<string> > (wordset, wordset.begin()),
ToLower);
cout << "\nAlphabetic list of words:\n";
for_each(wordset.begin(), wordset.end(), display);
cout << endl;
// place word and frequency in map
map<string, int> wordmap;
set<string>::iterator si;
for (si = wordset.begin(); si != wordset.end(); si++)
wordmap[*si] = count(words.begin(), words.end(), *si);
// display map contents
cout << "\nWord frequency:\n";
for (si = wordset.begin(); si != wordset.end(); si++)
cout << *si << ": " << wordmap[*si] << endl;
// cin.get();
// cin.get();
return 0;
}
string & ToLower(string & st)
{
transform(st.begin(), st.end(), st.begin(), toLower);
return st;
}
void display(const string & s)
{
cout << s << " ";
}
其他库
c++提供了三个数字模板vector、valarray、array
valarray不是STL的一部份,它没有begin、end
sort(vad.begin(),vad.end());//是非法的
sort(vad,vad+n);//vad是一个对象它不是指针,也是非法的
sort(&vad[0],&vad[n]);//有可能会出错
//c++11提供了接受valarray对象作为参数的模板函数begin()和 end()
sort(begin(vad),end(vad));
//假设numbers是一个valarray<int>对象
valarray<bool> vbool = numbers > 9;//将创建一个bool数组,其中vbool[i]的值为numbers[i]>9的布尔值
//扩展的下标指定版本
//slice类:slice类对象可用作数组索引,在这种情况下,它表示的不是一个值而是一组值。slice对象被初始化为3个整数值:起始索引、索引数和跨距
varint[slice(1,4,3)] = 10;//则varint数组的第1,4,7,10个元素都被设置为10
输入、输出和文件
- 可以使用dec、hex和oct来控制整数以十进制、十六进制和八进制来显示
hex(cout) 或 cout<<hex
:控制整数以十六进制显示 - 使用width(int)方法来将长度不同的数字放到长度相同的字段中,width方法只影响将显示的下一个项目,然后字段宽度将恢复为默认值。当指定长度大于要显示的字段宽度时将使用空格来填充,可以使用fill()成员函数来改变填充字符
cout.fill(char)
- 使用
precision(int)
来设置显示精度,新设置的显示精度将会一直生效直到被重新设置 setf(int)
能够控制多种格式化特性
修改将一直有效直到被修改
常量 | 含义 |
---|---|
ios_base::showpoint | 显示末尾的小数点 |
ios_base::boolalpha | 输入和输出bool值,可以为true或false |
ios_base::showbase | 对于输出,使用c++基数前缀(0,0x) |
ios_base::uppercase | 对于16进制输出使用大写字母,E表示法 |
ios_base::showpos | 在正数前面加+ |
c++新标准
int *ar = new int[4] {2,4,6,7}; //c++ 11
//移动语义
//右值引用
int x = 10, y = 20;
int && c = x + y;
//有趣的是,将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址。也就是说,虽然不能将&用于x+y,但可将其用于c,通过将数据与特定的地址关联,使得可以通过右值引用来访问数据。
class demo1{
public:
demo1(demo1 && demo);//move constructor
demo1 & operator=(demo1 && demo);//move assignment
}
demo1 demoD1(test1 + test2);//use demo1(demo1 && demo)
//完整例子
// stdmove.cpp -- using std::move()
#include <iostream>
#include <utility>
// use the following for g++4.5
// #define nullptr 0
// interface
class Useless
{
private:
int n; // number of elements
char * pc; // pointer to data
static int ct; // number of objects
void ShowObject() const;
public:
Useless();
explicit Useless(int k);
Useless(int k, char ch);
Useless(const Useless & f); // regular copy constructor
Useless(Useless && f); // move constructor
~Useless();
Useless operator+(const Useless & f)const;
Useless & operator=(const Useless & f); // copy assignment
Useless & operator=(Useless && f); // move assignment
void ShowData() const;
};
// implementation
int Useless::ct = 0;
Useless::Useless()
{
++ct;
n = 0;
pc = nullptr;
}
Useless::Useless(int k) : n(k)
{
++ct;
pc = new char[n];
}
Useless::Useless(int k, char ch) : n(k)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = ch;
}
Useless::Useless(const Useless & f): n(f.n)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
}
Useless::Useless(Useless && f): n(f.n)
{
++ct;
pc = f.pc; // steal address
f.pc = nullptr; // give old object nothing in return
f.n = 0;
}
Useless::~Useless()
{
delete [] pc;
}
Useless & Useless::operator=(const Useless & f) // copy assignment
{
std::cout << "copy assignment operator called:\n";
if (this == &f)
return *this;
delete [] pc;
n = f.n;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
return *this;
}
Useless & Useless::operator=(Useless && f) // move assignment
{
std::cout << "move assignment operator called:\n";
if (this == &f)
return *this;
delete [] pc;
n = f.n;
pc = f.pc;
f.n = 0;
f.pc = nullptr;
return *this;
}
Useless Useless::operator+(const Useless & f)const
{
Useless temp = Useless(n + f.n);
for (int i = 0; i < n; i++)
temp.pc[i] = pc[i];
for (int i = n; i < temp.n; i++)
temp.pc[i] = f.pc[i - n];
return temp;
}
void Useless::ShowObject() const
{
std::cout << "Number of elements: " << n;
std::cout << " Data address: " << (void *) pc << std::endl;
}
void Useless::ShowData() const
{
if (n == 0)
std::cout << "(object empty)";
else
for (int i = 0; i < n; i++)
std::cout << pc[i];
std::cout << std::endl;
}
// application
int main()
{
using std::cout;
{
Useless one(10, 'x');
Useless two = one +one; // calls move constructor
cout << "object one: ";
one.ShowData();
cout << "object two: ";
two.ShowData();
Useless three, four;
cout << "three = one\n";
three = one; // automatic copy assignment
cout << "now object three = ";
three.ShowData();
cout << "and object one = ";
one.ShowData();
cout << "four = one + two\n";
four = one + two; // automatic move assignment
cout << "now object four = ";
four.ShowData();
cout << "four = move(one)\n";
four = std::move(one); // forced move assignment
cout << "now object four = ";
four.ShowData();
cout << "and object one = ";
one.ShowData();
}
std::cin.get();
}
//使用default 和 delete 来显式的声明方法的默认版本和禁用版本
class demo2{
public:
demo2(demo2 & demo) = defalut;//default constructor
demo2(demo2 && demo);//move constructor
demo2 & operator=(demo2 && demo) = delete;//delete move assignment
//通常用在禁止某种类型转换
//c++11
demo2(demo &) override;//虚说明符override指出要覆盖的虚函数,如果声明与基类方法不匹配,编译器将视为错误
demo2(demo &) final;//将导致禁止派生类覆盖指定的虚类基方法
}
Lambda函数
auto demoOne = [](int x){return x;};//使用自动类型推断
//仅当lambda表达式完全由一条返回语句组成时,自动类型推断才管用;否则需要使用新增的返回类型后置语法
auto demoTwo = [][](int x) -> int {return x + 1;};
//lambda可以访问作用域内的任何动态变量,要捕获使用的变量可将其名放在中括号内 如:[z1],按值访问变量z1 [&z2],按引用访问变量z2 还有[&] [=]