C++11_学习笔记

Vector(std::initializer_list)  //{1.2,12.3,111.2}

关键字virtual的意思是可能随后在其派生类中重新定义;含有纯虚函数的类称为抽象类;

Vector::Vector(Vector&& a):elem{a.elem},sz{a.sz}

{ a.elem = nullptr;

  a.sz = 0;

}

//移动语义的关键在于:1:移动过来的对象要清零;2.要把移动的对象给当前对象;要令源对象处于可析构和赋值的状态。

y = std::move(x);

Vcetor(Vector&) = delete; // 抑制操作;删除默认的构造

typename另外一个作用为:使用嵌套依赖类型(nested depended name),如下所示:

class MyArray

{

public:

typedef int LengthType;

}

 

template

void MyMethod( T myarr )

{

typedef typename T::LengthType LengthType;

LengthType length = myarr.GetLength;

}

这个时候typename的作用就是告诉c++编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有typename,编译器没有任何办法知道T::LengthType是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。

[&](int a){ return a < x; }  // 返回bool;

using size_t = unsigned int;

using value_type = T;

---------------------------

forward_list

deque

set

multiset

map

multimap //允许重复关键字的map;

unordered_map

unordered_set

unordered_multiset

---------------------------------

template

vector find_all(C& c, V v)

----------------------------------

ostream_iterator oo {cout}; //将字符串写入cout;

unique_copy(b.begin(),b.end(),oo) ; // 将不重复的单词拷贝输出,丢弃重复值;

mutex m

void f(){

    unique_lock lck{m};

}

unique_ptr sp {new x};

 

using namespace std::chrono;

auto t0 = hign_reslution_clock::now();

this_thread::sleep_for(milliseconds{20});

auto t1 = high_resolution_clock::now();

cout << duration_cast(t1-t0).count();

async()会根据系统有多少内核来决定启用多少线程,不要试图对共享资源且需要用锁机制的任务使用async().

numeric_limits

numeric_limits::min();

类型函数是C++的编译时计算机制的一部分,它允许程序进行更严格的类型检查以获取更优的性能,我们通常把这种用法称为元编程,或者模板元编程.iterator_traits机制检查当前容器支持哪种迭代器;

template

using iterator_category = typename

std::iterator_traits::iterator_category;

bool b1 = Is_arithmetic(); //类型谓词是标准库的类型函数,负责回答一个关于类型的问题;

bool b2 = Is_arithmetic();

中有相应的谓词;is_class,is_pod,is_literal_type,has_virtual_destructor,is_base_of

用于诊断: static_assert(Is_arithmetic(),'error');utility中定义了pair,tuple;

tuple表示任意形式的元素序列;

auto pp = make_pair(v.begin(),2); //pair::iterator,int> // pp.first,pp.second

tuple t2('sid',1,3.14);

string s = get<0>(t); //获取tuple第一个元素;wchar_t //尺寸依赖于实现;

char16_t : 该类型存放utf16位字符集;

char32_t : 该类型存放utf32等32位字符集;

如果你需要更精细地控制整数的尺寸,可以使用中定义的别名;这些别名包括int64_t,uint_fast16_t;int_least32_t;

u'c' ;  //char16_t

U'char32_t'; //char32_t

L'c'; //wchar_t

u8R""前缀 utf8原始字串

U"foo"

LR"foo"

size_t,ptrdiff_t;alignof(int); //获取4;

线程局部:thread-local对象,随着线程的创建而创建,随着线程的销毁而销毁;

utf8字符串的结尾是\0,utf16是u'\0',utf32是U'\0';

前缀u和R对于顺序和大小写敏感

reinterpret_cast(q) - reinterpret_cast(p);

const int x ; //错误,因为我们无法给const对象赋值,所以它必须初始化;

int stat()//同一作用域,结构和函数可以同名;

普通旧数据(内存中连续字节序列),没有高级语义的概念,比如运行时多态,用户自定义的拷贝语义,这么做的主要动机是在硬件条件允许 的范围尽可能高效地移动对象;

std::memcpy();POD(普通旧数据)是指能被”仅当作数据”处理的对象,程序员无须顾及类布局的复杂性以及用户自定义的构造、拷贝和移动语义;

有默认构造函数的不行,有自定义默认构造函数的不行,有虚函数的不行;

POD条件:

不具有复杂的布局(比如含有vptr),不具有非标准(用户自定义的)拷贝语义;含有一个最普通的默认构造函数;

POD必须是属于下列类型的对象:标准布局类型,平凡可拷贝类型;

如果需要定义一个默认构造函数,使用=default,我们认为它是平凡的;

不是POD:含有一个非标准布局的非static成员或基类包含virtual函数;含virtual基类;

含有引用类型的成员,其中的非静态数据成员有多种访问修饰符,阻止了重要的布局优化,在多个基类中都含有非static数据成员,或者在派生类和基类中都含有非static数据成员,或者基类类型与第一个非static数据成员的类型相同;

基本上,标准布局是指与C语言的布局兼容的类型,并且应该能被常规的C++应用程序 二进制接口中ABI处理;如果一个拷贝操作能实现成逐位拷贝的形式,则它是平凡的。

一般操作是用户定义的,操作所属的类含有virtual函数,含有virtual基类,这些操作所属的类含有非平凡的基类或者成员;

is_pod

 

template

void mycopy(T* to, const T* from, int count)

{

   if(is_pod::value)

    memcpy(to,from,count* sizeof(T));

   else

        for(int i = 0; i != count; ++i)

    to[i] = from[i];

}

位域:

struct ppn{

 int: 3;

 bool dirty:1;   //把=换成了://有的去掉了变量名;

};

//注意语法:int中间无名,且分号;bool中间无初始化符号;冒号;

域必须是整形或枚举类型,我们无法获取域的地址;

union是一种特殊的struct,它的所有成员都分配在同一地址空间上,在同一时刻只能保存一个成员的值;

C++允许为联合的最多一个成员指定类内初始化器,此时,该初始化器被用于默认初始化;

union U2{

    int a;

    const char* p{""};

};

 

U2 x1; //默认x1=''

U2 x2{8}; //x2.a ==7

---------------------------

class Entry{

enum class Tag { number,text};

Tag type;

...

if (type!=Tag::number) throw Bad_entry{};

if (type==Tag::text) s.~string(); //显示地消毁

enum class 它的枚举值名字位于enum的局部作用域内,枚举值不会隐式地转换成其他类型;

普通的enum它的枚举值名字与枚举类型本身位于同一作用域中,枚举值隐式地转换成整数;

enum class color:char; // 声明

enum class Color_code:char{red,yellow,green,blue};

 

enum {arrow_up=1,arrow_down,arrow_sideways};

如果想紧凑地存储数据,则把结构中尺寸较大的成员布局在较小的成员之前;

对于内置数组T v[N]来说,编译器使用V和V+N代替begin(v)和end(v).头文件为内置数组和所有标准容器提供了begin(c),end(c).

如果想在范围for循环中修改元素值,则应该使用元素的引用;for(int& x:v) ++x;

break语句负责跳出最近的外层switch语句,或者循环语句;

为每个类,模板,名字空间分别编写注释;为每个非平凡的函数分别编写注释并指明:函数的目的,用到的算法,以及该函数对其应用环境所做的某些设定;为全局和名字空间内的每个变量及常量分别编写注释;为某些不太明显或不可移植的代码编写注释;

如果可能的话,优先选用switch而非if;优先选用范围for而非普通的for;

typeid(type)

dynamic_cast  运行时检查的类型转换;

static_cast 编译时检查的类型转换;

reinterpret_cast不检查的类型转换;

const_cast const转换;

new (expr-list> type)放置语法

new (expr-list) type{expr-list}

new(expr-list) type(expr-list)

new(expr-list) type{expr-list}

delete poointer

delete[] pointer

后置++的优先级高于一元*,因此*p++的意思是*(p++);//若*p优先级高,则增的是值,而事实是,增的是地址,但是后增的结果要反应在下一次。所以cout<<*p++,第一次仍是首元素的结果;

设计了一组关键词作为等效的替代:

and &&

and_eq &=

bitand &

bitor |

compl ~

 

or ||   注意位与什么的加了bit, bitor,bitand;

==的优先级高于&,所以if(i&mask==0) // if( (i&mask)==0 )

临时量可以用作const引用或者命名对象初始化器;const string& s = s1+s2;

string ss = s1+s2;

Error:     const char* cs = (s1+s2).c_str(); //临时对象给了指针。。。野指针;不要这样用

常量表达式是指由编译器求值的表达式,它不能包含任何编译时未知的值。

含有constexpr构造函数的类称为字面值常量类型,构造函数必须足够简单才能声明成constexpr,函数体必须为空且所有成员都是用潜在的常量表达式初始化的。

全局变量等静态分配的对象的地址是一个常量,然而,该地址值是由链接器赋值的,而非编译器。如果我们转换了某个值的类型,然后能够把它再转回原类型并且保持初始值不变,则称该转换是值保护的。

float f = FLT_MAX;

double d2 = DBL_MAX;

numeric_limits 定义在

long double ld2 = numeric_limits::max();

任何指向对象类型的指针都能隐式地转换成void*,指向派生类的指针(或引用)能隐式地转换成指向其可访问的且明确无二义的基类的指针或引用。

T*可以隐式地转换成const T*,类似的,T&能隐式地转换成const T&.

constexpr是一个关于值的概念

通用complex特例化成complex的过程:

template<> class complex{

public:

         constexpr complex(double re= 0.0,double im = 0.0);

}

除非万不得己,不要把对象放在自由存储上,优先使用作用域内的变量;

int* p1 = new int[n];

unique_ptr p2{new int[n]};

放置语法:X* p2 = new(buf) X;

命名对象的生命周期由作用域决定,然而,某些情况下我们希望对象与创建它的语句所在的作用域独立开来,很多时候我们在函数内部创建了对象,并且希望在函数返回后仍能使用这些对象,new负责创建这样的对象,运算符delete则负责销毁它们。

函数的一项重要作用:把一个复杂的运算分解为若干有意义的片段;函数的规模大约在7-40行左右;如果发现函数调用的代价比较高,可以考虑用内联机制来消除其影响。程序员应该把函数视作代码的一种结构化机制。形如[[...]]的概念被称为属性,属性可以置于C++语法的任何位置,[[noreturn]]void exit(int); exit永远不会返回任何结果;当程序调用函数时,我们为该函数的形参申请内存空间,并用实参初始化对应的形参,参数传递 的语义与初始化的语义一致(严格地说是拷贝初始化)。.

f(vector{12,3,4}); //  调用f(vector&&);除非万不得己,不要使用引用传递,当我们需要修改对象的值时,传递指针比使用引用更容易表达清楚程序的原意;当作为参数被传入函数时,类型T[]会被转换成T*,但是当需要决定如何处置“不好”的参数值时,就得依靠程序员了,我们把函数调用时应该遵循的约定称为前置条件,precondition.把函数返回时应该遵循的约定称为后置条件(postcondition)。获取函数地址可以用&,也可以不用。

(*f1)("Mary rose");

void f(int) noexcept;

using Pc = extern "c" void(int);

#define printx(x) cout<< #x "=" << x <<'\n';   //cout << 'a' << "=" << a << '\n';

__cplusplus: 在C++编译器中有定义,__DATE__,__TIME__,__FILE__源文件的名字;

__LINE__,__FUNC_——,__STDC_HOSTED__。

C语言有两种可能的运行环境,1. 独立(freestanding),在独立环境中,C程序执行不需要操作系统的支持,因此只具有最小的链接库能力。2. 宿主(hosted) :在宿主的环境中,C程序会在操作系统的控制下执行,并且会使用操作系统所提供的支持,具有完整的标准链接库能力。为宿主环境而编译的程序必须定义一个名为main的函数,这是程序开始时调用的第一个函数。析构函数不抛出异常;文件,锁,网络连接,线程都是系统资源,函数在抛出异常前必须释放这些资源或者把它们移交给其它的资源句柄;

------------------------------

File* f = open(fn,"r");

try{

    use f;

}

catch(...){

fclose(f);

throw;

}

fclose(f);

}

------------------------------

 

注意类的定义:

operator FILE*(){return p;}

---------------------------------------------

template

struct Final_action {

    Final_action(F f) :clear{ f } {}

    ~Final_action() { clear(); }

    F clear;

};

template

Final_action finally(F f)

{

    return Final_action(f);

}

---------------------------------------------

库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本。简单起见,我主要以stringstream为中心,因为每个转换都要涉及到输入和输出操作。

ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中。ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓冲区,替代sprintf。

ios_base<-ios<-ostream<-oostringstream;

处理异常的时候不要抛出异常;不要抛出一个无法捕获的异常;任何措施都无法捕获在名字空间和线程局部对象的初始化及析构过程中抛出的异常,这也是我们尽量避免使用全局变量的另一个原因。

在设计初期尽早确定异常处理策略;当无法完成即定任务时抛出异常;用异常机制处理错误;为特定任务设计用户自定义异常类;使用层次化异常处理;抛出异常前先释放局部资源

可以把构造函数的函数体(包括成员初始化列表在内)放在一个try块中,这样该构造函数就能自己捕获异常了。

class X{

    vector vi;

    vectorvs;

public:

    X(int,int);

};

X:X(int sz1,int sz2)//注意这,构造函数初始化器这里;

try:vi(sz1),vs(sz2)

{

}

catch(std::exception& err){

}

能捕获成员构造函数抛出的异常;“结束时留有throw"的意思是在某处抛出了异常,但是在该异常未被捕获,因而运行时系统试图把该异常从函数传递给函数的调用者。

默认情况下,terminate()会调用abort(),对于大多数用户来说,也可以通过调用中的std::set terminate()提供一个终止处理程序。

如果异常在线程中未被捕获,系统将调用std::terminate();

可以用标准库函数current_exception()把某一线程的异常传给另一线程的处理程序。

库,源文件,编译单元则提供了粗粒度的分离,逻辑上最理想的方式是模块化,独立的事物保持分离。只允许通过良好定义的接口访问“模块”。

C++通过其他语言特性(如函数,类,名字空间)的组合和源码的组织来表达模块化。

全局作用域也是一个名字空间,用::来引用。

类也是名字空间;

using std::string;//using声明

using namespace std; using指示;

参数依赖查找:

void f(Chrono::Date d,int i)

{

    std::string s = format(d);//Chrono::format() //会查找参数类型的Chrono::中的format()

    std::string t = format(i);//Err ..作用域中没有format()的定义;

}

如果一个参数是一个类成员,关联名字空间即为类本身(包括其基类)和包含类的名字空间;

如果一个参数是一个名字空间的成员,则关联名字空间即为外层的名字空间;

如果一个参数是内置类型,则没有关联名字空间;

名字空间是开放的:不需要连续放在单一的文件中。

namespace A{ int f(); }   namespace A{ int g(); }

使用名字空间别名无法重新打开名字空间,理想情况下,一个名字空间应该:表达一组逻辑相关的特性;不会让用户相关的特性;不会给用户增加符号表示上的严重负担;通常应该避免将using指示放在头文件中,因为这样会大大增加名字冲突的机会;如果名字空间中有inline,指出是默认含义;如果使用using namespace *,则默认用inline版本;这种inline namespace方法是侵入式的;

如必要,使用名字空间别名为长名字空间名提供简写;不要给你的名字空间的使用者增加太多符号表示上的负担 ;为接口和实现使用人离的名字空间;用inline名字空间支持版本控制;将using指示用于代码转换,用于基础库以及用于局部作用域内;

将一个源文件提交给编译器后,首先对文件进行预处理(处理宏,以及将#include指令指定的文件包含进来),预处理的结果称为编译单元(translation unit) 。链接器是将分离编译的多个部分绑定在一起的程序。编译器有时候叫加载器(loader),关键字extern;声明,而非一个定义,如果己经初始化,extern将会被忽略。如果一个名字在其定义处之外的编译单元中也可以使用,我们称其具有外部链接,如果一个名字只能在其定义所在的编译单元中被引用,我们称具有内部链接;

我们可以通过使用头文件来保持inline函数定义的一致性;

默认情况下,名字空间中的const对象,constexpr对象,类型别名以及任何声明为static的实体都具有内部链接。如果必须使用全局变量,至少应限制它们只在单一源文件中使用;将声明放在无名名字空间中,声明实体时使用static.使用无名名字空间可以令名字成为编译单元的局部名字,无名名字空间的效果非常像内部链接。

在包含指令以中,<>""内的空格不会被忽略。

头文件一般包括:具名的名字空间: namespace N{},inline名字空间;类型定义,模板定义,声明。函数声明。inline函数定义;constexpr函数定义;数据声明,const定义,constexpr定义;枚举。名字声明,类型别名;编译时断言。包括指令,宏定义,条件编译指令;

头文件不应该包括:普通 的函数定义。数据定义。集合定义,无名名字空间,using指示;

单一定义规则(one-defination rule,ODR).一个类,模板,内联函数的两个定义可以被接受,被认为是相同唯一定义。它们出现在不同的编译单元中,源码一样,含义一样;只要不违反ODR,一个模板定义就可以在多个编译单元中被#include,甚至是函数模板定义以及包含成员函数定义的类模板都可以。可以指定extern声明中使用哪种链接(linkage)规范;extern "C" 表示的是链接规范而非语言,extern "C"通常用于将函数链接到恰好符合C实现规范的Fortran和汇编程序;

C++提供了一种机制为一组声明指定链接规范:例如:

extern "C"{

char* strcpy(char*,const char*);...

} 这种构造通常称为链接块(linkage block),甚至可用来封装C头文件,从而使之适用于C++程序;

extern "C" { #include }

extern "C" int g3; //声明,非定义;

extern "C" { int g4;} //  定义;

调用规范:extern "C" {   

typedef int (*CFT)(const void*, const void*);

}

extern "C" int ccmp (const void*,const void*);

unix系统中的编译器通常将可执行文件命名为.out.

查看程序执行后的返回值:$ echo $?

win: echo %ERRORLEVEL%

g++ -o prog prog.c // -o prog是参数;

-std=c++0x 参数打开C++11支持;

cerr clog 而clog用来输出程序运行时的一般性信息;

写入endl的效果是结束当前行,并将与设备关联的缓冲区(buffer)内的内容刷到设备中。

curly brace:花括号{};wchar_t 宽字符16位char16_t unicode 16 char32_t unicode 32位

double 10位有效数字 long double 10位有效数字 Unicode是用于表示所有自然语言中字符的标准;

计算机以比特序列存储数据,每个比特非0即1.可寻址的最小内存块称为“字节”,存储的基本称为“字”(4-8字节)通常几个字节组成。

注意面值前缀后缀:

u char16_t unicode16字符;U char32_t unicode32字符;L 宽字符 wchar_t

u8 utf8(仅用于字符串字面常量)ll LL   = long long  。

初始化列表有一个重要特点,如果存在丢失信息的风险,则编译器将报错(不支持窄转换);声明为constexpr的变量一定是一个常量,而且必须用常量表达式来初始化;const int *p = nullptr // p是一个指向整形常量的指针;typedef double wages; // 原始类型在前,别名在后using SI = Sales ; // 别名在前,原始名在后;decltype(r+0) b; 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型;

while(getline(cin,line))

    cout << line << endl;

cctype头文件:

isalnum(c),isalpha(c),iscntrl(c),isgraph(c),islower(c)isprint(c) ispunct(c)是标点符号,isspace(c),isupper(c),isxdigit(c),tolower(c),toupper(c)

vector v(n,val),vector v(n)

vector svce(10,"hi")

v.empty(),v.size(),v.push_back(t),v[n]

item->mem   //解引用迭代器,等价于(*iter).mem;

if ( s.begin() != s.end() ) //确保非空,vector::const_iterator it,vector::iterator it;

注意括号不能少:(*it).empty() ; // 等于 it->mem:

不能在范围for循环中向vector对象添加元素,任何一种可能变改vector对象容量的操作,都会使对象的迭代器失效(你在加,尺寸在变);

但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素;

距离指的是右侧的迭代器向前移动多少位置能追上左侧的迭代器(3210),其类型名为difference_type的带符号整形数,string,vector都定义了difference_type;

constexpr unsigned sz = 42; //常量表达式,string strs[get_size()]; 当get_size是constexpr时正确,否则错误,int(&arrRef)[10] = arr ; arrRef引用一个含有10个整数的数组;

int* (arrRef)[10]  //arrRef是数组的引用,该数组含有10个整形指针;

int (*p)[10] = &arr  //p 指向一个含有10个整数的数组;

cstddef头文件中定义了size_t类型;

int ia[] = {10,2,2,2};   int *beg = begin(ia) ; //注意一般是v.begin();

ptrdiff_t,两种指针相减的类型是一种名为ptrdiff_t;

ptrdiff_t是cstddef头文件中定义的一种与机器实现有关的带符号整数类型,空间够大能表示数组中任意两个指针之间的距离;

当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置);

右移>>如果是带符号类型,在左侧插入符号位的副本或值为0的二进制位;

sizeof(type),sizeof expr 返回所占字节数;

逗号表达式:首先对左侧的表达式求值,然后将求值结果丢掉,逗号运算符真正的结果是右侧表达式的值;如果两个对象类型不同,不会直接将两个不同的类型相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值;const_cast只能改变运算对象底层const;只有const_cast能改变表达式的常量属性。reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释;

() // 类型构造  type(expr_list)

typeid(tyep) //类型id

typeid(expr) 运行时类型ID

ptr->*ptr_to_member; 指向成员选择的指针;// ->*

obj.*ptr_to_member ; 指向成员选择的指针 // .*

对于异常程序:时刻清楚异常何时发生,发生后应如何确保对象有效,资源无泄漏,程序处于合理状态;exception头文件定义了最通常的异常类exception,它只报告异常的发生,不提供任何额外的信息;stdexcept头文件定义了几种常用的异常类;runtime_error.

range_error,overflow_error,underflow_error,logic_error,domain_error[逻辑错误,参数对应的结果值不存在],invalid_argument,length_errr,out_of_range;

new定义了bad_alloc,type_info定义了bad_cast异常类型;

返回一个值的方式和初始化一个变量或形参的方式完全一样,返回的值用于初始化调用点的一个临时量,该临时量就是函调用的结果;EXIT_FAILURE //定义在cstdlib头文件中;EXIT_SUCCESS; //定义在cstdlib头文件中;typedef int arrT[10] ; //arrT是一个类型别名,它表示的类型含有10个整数的数组;func(int i) 函数声明:(*func(int i)) 意味着我们可以对函数的结果执行解引用操作,(*func(int i))[10] //解引用func的调用将得到一个大小是10的数组;int (*func(int i))[10] ; //int (*func(int i))[10] 表示数组中的元素是int类型;声明一个返回数组指针的函数;type (*func(parament))[dimension]

auto func(int i) -> int(*)[10]; //接受一个int参数,返回一个指针;指针指向含10个整数的数组;

decltype并不负责把数组类型转换成对应的指针;

const是否影响重载,看内容:

Record lookup(Account*);

Record lookup(const Account*); //新函数,作用于指向常量的指针;

constexpr函数是指能用于常量表达式的函数,返回类型及所有形参的类型得是字面值,只有一条return.

和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义,多个定义必须完全一致,内联函数和constexpr函数通常定义在头文件中;如果两个函数唯一的区别是它的指针形参指向常量或非常量,则编译器能通过实参是常量决定选用哪个函数,如果实参是指向常量的指针,调用形式是const*函数,如果实参是指向非常量指针,调用形参是普通指针函数;

int (*f1(int))(int*,int); //f1有形参表,所以f1是个函数,f1前面有*,所以f1返回一个指针,进一步发现,指针的类型本身也含形参表,因此指针指向函数,该函数的返回类型是int.

当我们将decltype作用于某个函数时,它返回函数类型而非指针类型,因为,我们显示地加上*以表明我们需要返回指针;而非函数本身;

g++ -std=c++0x aue_aut.cpp -o main.exe

#include    // INT_MAX,LONG_MAX,SHRT_MAX 可移植;CHAR_BIT char的位数;

int py{} //括号里不包括任何东西,则将初始化为零;

通用字符名的用法类似于转义序列,通用字符名可以以\u或\U打头,\u后面是8位16进制位,\U后面则是16个16进制位。

wchar_t bob = L'P';

wcout << L"tall" << endl;

d.dddE+n指的是将小数点向右移动n位,而d.dddE-n指的是将小数点向左移n位。之所以称为浮点,就是因为小数点可移动。

cout.setf(ios_base::fixed,ios_base::floatfield);

长度固定的数组,array,使用栈(静态内存分配)。

using namespace std;

array ai;    // 5个int

array ad = {1.2,3.1,3.43,4.3};  //ad[-1] // 引用和数组是一样的;注意下标为负,向前移一个array,vector 的at()//a2.at(1) = 2.3  使用at()时,将在运行期间捕获非法索引,执行下标检查。

ctime头文件中有一个clock(),返回程序开始执行后所用的系统时间,clock()返回的时间单位不一定是秒;符号常量:CLOCKS_PER_SEC,该常量等于每秒包括的系统时间单位数;将系统时间除以这个值,得到秒数;clock()返回clock_t类型;

float sec;//输入秒数;

clock_t delay = sec * CLOCKS_PER_SEC;

clock_t start = clock();

while(clock() - start < delay ) ; //每次调用的时候,返回系统时间,减去开始;

cin.get();

 

处理文件:fstream头文件 (ofstream),open(),close()

ofstream of ; of.open("xxx.txt");

cout.precision(2);

of.precision(4); //of /write.txt

读文件:fstream头文件,ifstream; // eof(),fail();读取过程中发生了类型不匹配的情况

if.is_open() //是否打开;

如果文件受损或硬件故障,bad()返回true,没有发生任何错误返回good() true;

调用函数时,将结构的地址(&pplace)而不是结构本身(pplace)传递给它;

const double* (*pa[3])(const double*,int) = {f1,f2,f3}; //函数指针数组;f1,f2,f3为函数名;

常规变量和const变量都可以视为左值,因为可通过地址访问它们,但常规变量属于可修改的左值const变量属于不可修改的左值

函数调用讲明白了,如果一个函数调用,形参与实参类型不匹配,则先要生成一个临时匿名变量,进行一定的转换,然后再调用。而如果类型匹配正确,则不需要临时变量;生成的临时变量只在调用期间存在,此后编译器可以随意删除;

尽可能的使用const,使用const引用使函数能够正确生成并使用临时变量。使用const使函数能够处理const和非const实参,否则,将只能接受非const数据;

double&& ref = 3.0; //右值引用语法;注意&&在类型右侧;

free_throws& jolly = clone(three) ;

int f(int n,int m = 7, int x = 3);默认参数只能是最后几个;只有函数原形声明时指定了默认值,定义的时候可以不指定;函数重载:名称修饰,名称矫正,函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型来替换;通过将类型作为参数来传递给模板,可使编译器生成该类型的函数,模板特性有时候也称参数化类型(parameterized types)

在标准C++98添加关键字typenae之前,C++使用class来创建模板,c++98后用typename,使参数表示类型这一点更为明显;模板函数也可以重载,特征标不同;可以提供一个具体化函数定义-称为显式具体化(explicit specialization);template<> swap(job&,job&);//声明;<>空尖括号说明是具体化,函数名,具体化的类型,形参表;实现;

Template void swap(int,int);另外,如果没有定义显示的实例化,也可以在调用的时候显示的实例化:cout << Swap(x,m) << endl; //显示的实例化;可能会执行强制类型转换;

候选,常规函数优于模板;非const的优先于const,具体化优于模板化;用于找出最具体的模板的规则被称为函数模板的部分排序规则(partia ordering rules).

如果expression是一个左值,则var为指向其类型的引用。

double xx = 4.4;

dceltype ((xx)) r2 = xx ; // r2 is double&

注意:decltype((variable))(注意是双层括号)的结果永远是引用,
而decltype(variable)的结果只有当variable本身是一个引用时才是引用

线程存储持续性(C++11),如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。

动态存储持续性:用new,直到delete将其释放;

链接性(linkage)描述了名称如何在不同的单元间共享,链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享,自动变量没有链接性,因为它们不能共享;

虽然该变量只在该代码中可用,但它在该代码块不处于活动状态时仍然存在,因此在两次函数调用之间,静态局部变量的值将保持不变;

volatile 为了改善编译器的优化能力,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查询二次这个值,而是将这个值缓存到寄存器中,这种优化假设以量的值在这两次使用之间不会变化,如果不声明为volatile,则编译器将进行这种优化,将变量声明为volatile,相当于告诉编译器,不要进行这种优化;

mutable说明符解const,即使函数声明为const,只要有mutable,变量也不受这种限制;

const全局变量的链接性为内部的,也就是说,在C++看来,全局const定义,就像使用了static说明符一样。

可以使用static将函数的链接性设置为内部的,使之只能在一个文件中使用;只在这个文件中可见;

extern "C" void spiff(int); // use C protocol for name look-up

extern void spoff(int); // use C++ protocol for name look-up

extern "C++" void spaff(int) // use c++ protocol for name look-up

动态内存由newdelete控制,而不是由作用域和链接性规则控制。通常,编译器使用三块独立的内存,一块用于静态变量,一块用于自动变量,一块用于动态存储;

假设: float * p_fees = new float[20]; //由new分配的80个字节的内存一直保留在内存中,直到使用delete运算符将其释放;但是当包含声明的语句块执行完结后,p_fees指针将消失。

new失败,std::bad_alloc;

定位(placement)new运算符:让您能够指定要使用的位置;1.要包含new头文件;

p1 = new(buffer1) char;

p2 = new(buffer2) int[20]; // new后面跟括号,括号内是可用地址;

定位new,放置语法使用传递给它的地址,它不跟踪也不查找未使用的内存块。

定位new函数不可替换,但可以重载,它至少需要接收两个参数,其中第一个总是std::size_t;

namespace MFC = myth:elements::fire; //名字别名

using MFC::fire;

explicit构造函数:用单一参数调用一个构造函数,其行为类似于从参数类型到类自身类型的转换。

explicit可以指明构造函数不能用作隐式的转换;如果构造函数的声明带有explicit,则它只能用于初始化和显式类型转换; 防隐式转换的,避免含糊错误。

用=进行的初始化可看作是拷贝实始化,一般来说,初始化器的副本会被放入待初始化的对象,但是,如果初始化器是一个右值,这种拷贝可能被优化掉(取消),而采用移动操作,省略=会将实始化变为显式初始化。显式初始化 Data d1{15} 也称为直接初始化。

默认情况下,应该将单参数的构造函数声明为explicit,除非你有很好的理由。否则注释原因。

explicit在定义中不能重复;即外面不能带explicit.也可以用于无参或多参;

一个函数不仅在类中声明,还在类中定义,那么它被当作内联函数处理;

class data{

  int month() const { return m;} //const指出这些函数不会修改Date的状态

} 试图在const函数中改变成员的值的操作都会出错

const定义不能省,无论在类内还是类外;

 

class Date{

public:...

private:

   mutable bool cache_valid;

   mutable string cache;

};

 

d.add_day(1).add_maon(1); //连续操作就要返回data&;

这时要返回*this;表达式*this引用就是调用此成员函数的对象;非static成员函数中,this指向调用它的对象的指针;this被当作一个右值,因此我们无法获得this的地址或给他赋值;

如果我们希望引用类的一个公共成员,而不是某个特定对象的成员,应该使用类名后接::的限定方式。

int X::sm {7}; //X的静态成员。

成员指针语法:  int(S::*) pmf() {&S::f} ; //X的成员f;课本中的,但是编译器不认

 int (B::*pFun)(int);    //声明指向类成员函数的指针pFun; 这个编译器认;

在多线程代码中,static数据成员需要某种锁机制或访问规则来避免竞争条件。

语言必须为用户提供定义小的具体类型机制,这种类型称为具体类型(concrete type)具体类,以区别抽象类和类层次中的类;

若不修改对象值,定义为const成员函数;

X& operator = (const X&);拷贝赋值运算;

X& operator = (X&&); 移动赋值运算符;

类不变式(class invariant),就是当成员函数(从类外)被调用时必须保持的某些东西。

注意一个问题:

Vector* f(int s)

{

    return new Vector(s+s);

}

 

void g(int ss)

{

    Vector* p = f(ss); //这里没有new,但是,返回的也是new.所以可以用delete

    delete p;

}

构造函数调用其基类的构造函数,然后调用成员的构造函数;执行自身的函数体,析构相反;

构造函数按声明顺序(而非初始化的顺序)执行成员和基类的构造函数;

struct X{ X(int);};

struct S2{ X x;};

S2 x1; //err.没有为x1.x提供值

S2 x2{1}; //x2.x用1进行初始化 ;注意这,类的成员,直接用初始化的值给了

void C::push_back(cons X& a)

{

    new(p) X{a};

}

void C::pop_back(){

   p->~X(); //显示调用析构函数

}

通过声明构造函数=delete或private,就可以阻止其析构;

可以显示的销毁,但不能隐式的销毁;

class Nonlocal{

...public: destroy(){this->~Nonlocal();} //中转;

private:

~Nonlocal(); //把析构声明为私有

};

delete p;/err.出错。

p.destroy(); //正确

}

 

一般析构函数是virtual,因为指向基类的指针可以指向派生类,如果不是虚的,就会导致资源泄漏;

一般是用基类指针来调用派生类以求通用性,这时候,虚析构函数就起作用了;

如果一个类有私有的数据成员(不是静态的),它就需要一个构造函数来进行初始化;

构造函数常用来建立类的不变式并获取必要的资源;只要是在构造对象的地方,我们都可以用{}初始化语法为构造函数提供参数;{}初始化有时也称通用初始化,这种语法可以用在任何地方。{}可以进行逐成员初始化;接受单一std::initializer_list参数的构造函数被称为初始化器列表构造函数(initializer-list).一个初始化器列表构造函数使用一个{}列表作为其初始化值来构造对象。一个初始化器列表的长度可以任意,但它必须是同构的。就是说,类型一样:{"bjan","bs"},{192,22,22}必须同类,第么全是字串,要么全是数字;

可以接受一个initializer_list参数的函数作为一个序列来访问,即通过成员函数的begin(),end(),size()访问。

为了显示使用一个initializer_list,必须#include头文件。vector,map一些标准库组件己经包含了initializer_list己经包含了此文件,所以,很少需要直接包含此头文件。

成员的构造函数按成员在类中声明的顺序调用,而不是按成员在初始化器列表中出现的顺序。

class Person{

string name;

string addr;

}

Persoon::Person(const string&n, const String&a):name{n}{

}

{

    address = a;

}

注意这个;addr首先被初始化为空字串,然后用a的副本。

 

使用一个成员风格的初始化器,但用的是类自身的名字(也是构造函数名),它会调用另一个构造函数,作为这个构造过程的一部分,这样的构造函数称委托构造函数(delegating constructor).有时也称为转发构造函数。forwording constructor).注意不能同时显式和委托初始化一个成员。X():X{42},a{56}{...} 出错。X{42}是委托,但又初始化a;

委托者执行完才表明构造完成,仅仅被委托者执行完成是不够的;

在少数情况下,在类内声明中初始化static成员也是可能的,条件是static成员必须是整形或枚举类型的const.或字面值类型的constexpr.且初始化器必须是一个常量表达式(constant-expression)

class P{

static const int Pi = 3.14;

 

成员常量的主要用途是为类声明中其他地方用到的常量提供符号名称;如:

template

class Fixed {

public:

static constexpr int max = N;

private:

    T a[max];

};

 

当我们需要从a到b传输一个值的时候,通常有两种逻辑上不同的方法:拷贝x=y,移动:将x变为y的旧值,y变为某种移出状态(moved from state).对我们最感兴趣的情况(容器,移出状态就是空)。

一般来说,移动操作不能抛出异常,而拷贝操作则可以。

编写一个移动操作时,应该将源对象置于一个合法的但未指明的状态,因为它最终会被销毁,而析构函数不能销毁一个处于非法状态的对象,而且,标准库算法要求能够向一个移出状态的对象进行赋值(使用移动或拷贝)。因此,设计移动时不要让它抛出异常,并令源对象处于可析构和赋值的状态。

类X的拷贝构造函数有二种:X(const X&)加拷贝赋值运算符: X& operator(const X&)

一个拷贝构造函数应该创建给定对象的副本。不应该修改它。

array dim; //二维数组;

首选创建一个副本,然后交换其内容,可以用这种方法提供强保保障;swap(tmp,*this);

拷贝构造函数可以用派生类初始化基类;拷贝操作必须满足两个准则:等价性和独立性;

默认拷贝语义是逐成员拷贝,因此一个默认拷贝操作会拷贝指针成员,但不会拷贝指针指向的对象。

对于共享子对象生命周期相关的问题,我们可以通过引入某种形式的垃圾收集机制来解决:

struct S2{

    shared_ptr p;

};

写前拷贝:在共享状态被修改之前,副本其实并不真的需要独立性,可以推迟共享状态的拷贝,直至首次修改副本前才真正进行拷贝。

类定义了一个bool shared;变量记录是否要修改。还定义了clone(),rep;

if(shared){ rep = clone(); shared = false; //不再共享;}}

如何防止切片:1.禁止拷贝基类,delete拷贝操作;2.防止派生类指针转换为基类指针,将基类声明为private或protected基类。

移动赋值背后的思想是将左值的处理与右值的处理分离,拷贝赋值操作和拷贝构造函数接受左值,而移动赋值操作和移动构造函数则接受右值。对于return值,采用移动构造函数;

move()是一个标准库函数,它返回其实参的一个右值引用,move(x)意味着给我一个X的右值引用,即std::move(x)本身不移动任何东西;它只是允许用户移动x。它返回的是实参的一个右值引用。可能将move()改名为rval()更好,习惯了。

-------

struct S{

string a;

int b;

};

 

S f(S arg){

S s0{}; //默认构造{“”,0}

S s1 {s0}; //拷贝构造

s1 = arg; //拷贝赋值

return s1 ;// 移动构造

}

 

可以声明希望由编译器自动生成一个;Y(const string& s); //用一个字串生成Y

Y() = default; //显示生成默认;二个构造,一个自定义,一个自动生成;

在构造函数中建立不变式;在拷贝和移动操作中保持不变式;在析构中处理清理工作;

如果一个类有一个指针成员,就要对默认的拷贝和移动操作保持警惕。

可以使用delete删除任何我们能声明的函数;

Foo* clone(Foo*) = delete;

 

Shape* ps2 = clone(ps); //没问题

Foo* pf2 = clone(pf);   //错误:clone(Foo*)己经被删除;

另一种是删除不需要的类型转换:Z(int) = delete; //不能用整数初始化;

void * operator new(size_t) = delete; //这个类型没办法用new了;

注意使用=delete删除的函数与只是未声明的函数的差别:前一种情况下,编译器会发现程序试图使用delete删除的函数并报错;后一种可能用默认的代替。

如果一个类有虚函数,它就需要一个虚析构函数;如果一个类没有构造函数,就可以进行逐成员初始化;如果一个类是一个容器,为它定义一个初始化器列表构造函数;

按声明顺序初始化成员和基类,如果一个类有一个引用成员,它可能需要拷贝操作(拷贝构造和拷贝赋值),在构造函数中优先选择成员初始化而不是赋值操作;

假设b和c的类型是complex,则b+c等价于b.operator+(c).

c++语法规定符号{}只能表示初始化器,并且只能位于赋值运算符的右侧;

用户无权定义下列运算符:      ::作用域解析     .成员选择      .* 通过指向成员的指针访问成员

具名“运算符”,负责报告其运算对象的某些基本情况,也不能重载:

sizeof,alignof(对象的对齐方式),typeid(对象的type_info).

此外,我们用operator""表示用户定义的字面值常量,operator T()表示向T的类型转换;

aa@bb可以理解成aa.operator@(bb)或者operator@(aa,bb).

对于一元运算符,不管它是前置还是后置的,我们既可以用不接受任何参数的非static成员函数定义它,也可以用接受一个参数的非成员函数定义它。对于任何一个前置运算符@,@aa可以理解成a.operator@()或者operator@(aa)非成员。

对于一元后置运算符@,aa@可以理解成aa.operator@(int) 或者opearator@(aa,int).非成员

运算符预置:当作用于类的对象时,运算符=(赋值),&(取地址),自带预置的含义;

枚举类型是用户自定义类型,我们可以为其定义运算符;

表达式中通过构造函数显式或者隐式构造的对象是自动对象,一有机会就会销毁它。

在.或者->的左侧不会执行隐式的用户自定义类型转换,即使.本身是隐式的也是如此;

例如:3.operator+=(z);   3+=z; //err 3不是对象.

运算符是容器vector,智能指针,迭代器以及承担资源管理功能的类的设计关键;

[] () -> ++ -- new delete ;

const int& operator[](const string&) const;

int& ooperator[](const string&);

 

struct Assoc{

vector> vec; //vector的元素是名字,值,对

const int& operator[](const string &) const;

int& ooperator[](const string&);

重载()运算符:函数调用;函数调用可以看成是一个二元运算。

struct Action{

int opeartor()(int);

}

Action act = act(2);

调用运算符(call operator)又称为应用运算符(application operator).

运算符()最直接也是最重要的目标是为某些行为类似函数的对象提供函数调用语法,其中,行为模式与函数类似的对象称为函数对象(function-like object)或者简称函数对象。

operator()()必须是非static成员函数,函数调用运算符通常是模板;

-〉箭头解引用运算符可以定义成一个一元后置运算符;重载->的主要目的是创建“智能指针”,即行为与指针类似的对象;

operator++() 前置++   //  operator++(int) 后置++;(作为成员函数重载)

成员函数operator new()和operator delete()是隐式的static成员,因此,它们无法使用this指针,也不能修改对象的值。

 

int operator"" Mb(unsigned long long m) {

    return m * 1024;

}

实现继承:通过共享基类所提供的特性来减少实现工作量;

接口继承:通过一个公共基类提供的接口允许不同派生类互换使用。接口继承常被称为运行时多态,或动态多态;

模板所提供的类的通用性与继承无关,常被称为编译时多态或静态多态;

类层次中析构函数通常应该是virtual的。

给定一个Base*类型的指针,它指向的对象的真正派生类型是什么?C++提供了四种基本解决方法:

[1]保证指针只能指向单一类型的对象

[2]在基类中放置一个类型域,供函数查看

[3]使用dynamic_cast。

[4]使用虚函数

虚函数机制允许程序员在基类中声明函数,然后在每个派生类中重新定义这些函数。

虚成员函数有时也称方法(method),默认情况下,覆盖虚函数的函数自身也变为virtual的,我们在派生类中可以重复关键字virtual,但这不是必须的,建议是不重复virtual,如果你希望明确标准覆盖版本,可以使用override.显然为了实现多态性,编译器必须在每个Employee类的对象中保存某种类型信息,并利用它选择虚函数print()的正确版本,在一个典型的C++实现中,这只会占用一个指针大小的空间,常用的编译器实现技术是将虚函名转换为函数指针表中的一个索引。这个表通常称为虚函数表(the virtual function table),或简称为vbtl.每个具有虚函数的类都有自己的vbtl,用来标识它的虚函数。从构造函数或析构函数中调用虚函数能反映出部分构造状态或部分销毁状态的对象,因此,从构造函数或析构函数中调用虚函数通常是一个糟糕的主意。

=0纯虚函数,必须被覆盖;override: 函数要覆盖基类中的一个虚函数;

final:函数不能被覆盖;注意加override的位置:

void g(int) override; //和const加的位置一样,参数类型,要符合重载的类型;

void f(int) const noexcept override; //正确;override只能放在最后;

说明符override不是函数的一部分,而且在类外定义中不能重复(只在类定义的时候声明就好)

注意:override,final不是一个关键字,它只是所谓的上下文关键字,放在函数后面说明一下;这样做是为了以前的代码兼容性;final也是放在函数后面,这个函数不能覆盖;

class For_statment final:public Node{}//在类名后加上final,我们可以将一个类的所有virtual成员函数声明为final;

可以用using声明将一个函数加入作用域中:

struct D2:Base{

    using Base::f;

    void f(double);

};

可以使用using 从基类引入名字,然后函数会根据参数解析调用相应的函数;

声明继承构造函数:

template

struct Vector:std::vector{

    using vector::vector;//继承vector的构造函数;

如果在派生类中继承了构造函数,又定义了需要显示初始化的新成员变量,注意;

struct B1{

    b1(int){}

};

 

struct D1:B1{

    using B1::B1;//隐式声明D1(int)

    string s;

    int x;//这个值

};

D1 d{4}; // d.x未初始化,原因是继承的构造函数等价于只初始化基类的构造函数;

D1 e; //err D1没有默认构造函数;

下面的代码好理解一些,和上面的等价:

struct D1:B1{

    D1(int):B1(i){}

    string s;

    int x;

};

返回类型放松:覆盖函数的类型必须与它所覆盖的虚函数的类型完全一致,C++对这一规则提供了一种放松规则,返回类型B&可放松为D&,这一规则有时称为协变返回规则(covariant return).这一规则适合返回类型是指针或引用的情况,但不能是unique_ptr;

由于new_expr()和clone()这样的函数是virtual的且间接构造对象,它们常被称为虚构造函数;这种函数都是简单地使用了普通构造函数来创建一个适合的对象;

为了创建一个对象,构造函数并不完全是一个普通函数,因此,构造函数不能是virtual的,而且,一个构造函数并不完全是一个普通函数。因此,你不能接受一个构造函数的指针并将其传递给一个对象创建函数;可以通过定义一个函数来调用构造函数并返回构造的对象。

抽象类就是要作为通过指针和引用访问的对象的接口(为保持多态行为)。因此,对一个抽象类来说,定义一个虚析构函数通常很重要,抽象类提供的接口不能用来创建对象,因此抽象类通常没有构造函数;

抽象类所支持的设计风格称为接口继承(interface inheritance),它与实现继承(implementation inheritance)相对,实现继承由带状态或定义了成员函数的基类所支撑的,两种风格组合使用是可能的。

如果是protected的,仅可以被所属类的成员函数和友元以及派生类的成员函数和友元函数所使用。

一个高效的非侵入性链表通常要求数据结构掌握元素的动态,若一个链表不要求修改其元素(例如,要求元素类型有链接域),那么它就是非侵入性的(nonintrusive).

 

使用返回类型后置语法:    auto List::get_free()->link*{}

访问基类: class X : public B{} //公有继承 可以省略,类默认为私有继承,结构为公有

基类的访问说明符控制基类成员的访问以及从派生类类型到基类类型的指针和引用转换。

如果B是一个private基类,其公有和保护成员只能被D的成员函数和友元函数使用,只有D的友元和成员可以将一个D*转换为一个B*。

struct B{};

class D1:public virtual B{}

class D2:public virtual B{} //都从B继承,虚基类

class D12:public D1,private D2{///} //多重继承

成员指针是一种类似偏移量的语言构造,允许程序员间接引用类成员。 ->* 和 .*

使用->*,可以访问一个类成员,其名字保存在一个成员指针中,pt::p->*ptm.

成员指针不能赋予void*或任何其他普通指针。

函数成员指针:using Pstd =void (std_interface::*)(); //成员指针类型;

用类型声明变量:   Pstd ps = &std_interface::suspend; //指向类函数成员suspend()的指针。

std_interface * p ; //指向对象的指针;

p->suspend() ; //正常通过指针调用;

p->*s(); //通过成员指针调用;

获得成员指针的方法是对一个完全限定的类成员名使用地址运算符&,如:&std_interface::suspend;我们可以使用如X::*的声明

成员指针更像一个结构内部的偏移量或数组内的下标;

成员函数的应用场景是在我们需要引用一个函数,但又不知道其名字的时候。

一个static成员不关联某个特定对象,因此static成员的指针就是一个普通指针;

class Task{

    static void schedule();

};

void (*p)() = &Task::schedule; //ok. //前面是普通函数指针语法

using pf = void(c::*)(int); //C的成员函数指针,接收int.

using pm = const char* C::*; //Cchar*数据成员指针

pf = &C::print;

pm = &C::val;

调用:

C& z1;

(z1.*pf)(2);

z1.*pm = "csn" ;

通过指针或引用来访问多态对象;使用抽象类,以便聚焦于清晰接口的设计应该提供什么;

使用override显示说明覆盖,使用抽象类说明接口,使用抽象类保持实现细节和接口分离;

有虚函数也应该有虚析构函数;确实有需要才使用protected,不要将数据成员声明为protected;

二义性,假设基类都有一个同名函数Debug_info di1 = Satellite::get_debug();

Debug_info di2 = Displayed::get_debug();

上面是显示的消除二义性,但是,比较繁琐,解决此问题的方法是在派生类中定义一个新函数,class Comm_sat:public Statllite,public Displayed{

    Debug_info di1 = satellite::get_debug();

    Debug_info di2 = Displayed::get_debug();

    return Merge_info(di1,di2);

}

virtual void win_draw() = 0; //强制派生类必须覆盖;

对象的各个部分必须共同享同一个Storable,我们应该通过把基类声明成virtual来避免重复,因为派生类的每个virtual基类都是同一个(共享)对象表示的。class Transmitter:public virtual Storable{};

为虚基类提供一部分实现(但非全部实现)的类称为混入类(mixin).在运行时检测对象类型最明显也是最有用的操作就是类型转换,若对象确为预期类型,该操作应该返回合法指针,否则返回一个空指针;

if(auto pb = dynamic_cast(pw)){ //pw指向一个lval_box

}

else{

}

以上代码把基类指针转换成类层次的lval_box*,如果转换成功,就OK。

 

在运行时使用类型信息通常称为“运行时类型信息”,RTTI。从基类到派生类称向下转换;从基类到兄弟的转换,则称交叉crosscast转换;dynamic_cast的真正用武之地是编译器无法确定类型转换正确性的情形,在此情况下,dynamic_cast(p)查看p指向的对象(如果有的话)如果对象的类型是类T或其类型有唯一的基类,则dynamic_cast返回一个指向该对象的T*类型的指针,否则返回nullptr.如果p的值是nullptr,dynamic_cast(p)也会返回nullptr。dynamic_cast不会允许意外地破坏对私有和保护基类的保护,类型转换要求必须对可唯一识别的对象进行,dynamic_cast要求给定的指针或引用指向一个多态类型,以便进行向下或向上转换。限制dynamic_cast只能转换多态类型在逻辑上也是有道理的,如果一个对象没有虚函数,那么在不了解确切类型的情况下,是无法安全操作它的。

向void*进行dynamic_cast可以用来确定一个多态类型的对象的起始地址:

void* pb2 = dynamic_cast(pb); //ok.

对于一个引用r,dynamic_cast(r)并不是一个问题,而是一个断言,r引用的对象类型为T.对r进行dynamic_cast得到的结果实际上己经隐含地被dynamic_cast实现自身检查过了,如果dynamic_cast引用对象不是所期望的类型,它会抛出一个bad_cast异常;

void(lval_box& r){

    lval_slider& is = dynamic_cast(r);

}

dynamic_cast可以从一个多态虚基类转换到一个派生类或是一个兄弟类,static_cast则不行,因为它不检查要转换的对象。对于一个void*所指向的内存,编译器不能做任何假设,这意味着dynamic_cast不能将一个void*转换为其他类型,因为dynamic_cast必须探查一个对象的内部来确定其类型。如果这时要转换成类对象,就需要用static_cast.

强制去除constvolatile则需要使用const_cast.但即使是这样,只有当对象最初不是声明为const(或volatile)时,转换结果才能安全使用。要避免在构造和析构过程中调用虚函数;

typeid可以满足这种需求,它生成一个对象,表示它所处理的类型。

typeid(expr)必须指向一个完整定义的类型,如果expr的值为nullptr,则typeid(expr)会抛出一个std::bad_typeid异常,typeid(*p); //获得p指向对象的类型;typeid(p); // 获得指针的类型,如果typeid()的运算对象不是多态类型或者不是一个左值,则结果在编译时即可确定;无须运行时对表达式求值;

type_info类定义看起来像是这样的:

class type_info{

    bool operator == (const type_info&) //可以比较

    bool operator != (coonst type_info&)//不等

    size_t hash_code()

    const char* name();名字

    bool before(const type_info&) //定义了序,与继承关系间并不存在关联;

};

如果使用了动态链接,很可能有重复的type_info对象。应该使用==比较type_info对象是否相等,而不是比较type_info指针

用得最多的就是用于诊断信息:

Component *p

##debug  typeid(*p).name();

}

type_index是一个标准库类型,用于比较和哈希type_info对象;

C++模板机制允许在定义类,函数或类型别名时将类型或值作为参数;

概念:模板为实参设定的一组要求;

标准库提供了basic_string,它很像模板化了的String,在标准库中,stringbasic_string的一个别名

using string = std::basic_string;

map m;

只有一个成员函数被使用时才会为其生成代码(模板);

当定义一个类模板时,通常一种好的方式是,先编写调试一个特定类,如String,然后再将其转换为一个模板,如String,这样,我们就能针对一个具体实例来处理很多设计问题和大多数代码错误。

template instantiation,一个模板针对某个特定模板实参列表的版本被称为特例化(specialization)

可以将“C必须为一个容器”看作一个谓词,它接受一个类型参数C,若C是一个容器则返回true,否则返回假,如Container>(),Container>()为真,我们把这种谓词称为概念。用来推理对模板实参的要求

什么样的条件才是容器? T重载[],有size(),必须有value_type元素类型;

template

class Buffer;

Buffer b2; //Buffer,1001-1> b1;

编译器可以对常量求值;

注意模板在处理派生类时,不能转换;要注意这点;

Shape* p {new Circle(p,100)};

vector* q {new vector{}};

vector* q {new vector{}}; //err. vector* 不能转vector*

模板参数T只能被模板自身访问,如果其他代码使用元素类型,目前我们能用的方法只有提供一个别名;using value_type = T;

通过成员别名来表示的类型名称常被称为关联类型(associated type.

模板类的静态成员:

static constexpr Point p{100,250};

static int m3;

//初始化:

template int X::m1 = 88;

template

int  X::a = 12; //注意静态声明中间的这个int.一般很易掉

一个static成员只有真被使用时才需要定义:

int* p // int*  p = &X::a;

int* p = &X::a; //这个是取静态变量的地址,*p =13 ; //改变成13;

成员枚举可以在类外定义,但在类内声明中必须给其出其基础类型;

成员模板不能是virtual的:

template virtual bool intersect(const T&) const = 0;

类模板的友元:friend Vector operator* <>(const Matrix&, const Vector&);

后面的<>是必须的,它清楚的指出友元函数是一个模板函数,如果没有<>,友元函数将被假定是非模板函数,有了上述定义,乘法运算符即可直接访问Matrix和Vector中的数据。

友元类:

friend class C4; //正确,引入了一个新的类C4.

 

template

class my_other{

    friend T;  //friend class T; //err.class不用。应 friend T;

};

友元不能继承,不能传递;

推断:

template

void f(const T*,U(*)(U));//第二个参数为函数指针;

int g(int);

void h(const char* p)

{

    f(p,g);//T是char,U是int;

}

模板实参推断过程中是区分左值和右值的,X类型的左值被推断为一个X&,而右值被推断为X

如果两个函数模板都可以调用,且其中一个比另一个更特殊化,则接下来的步骤只考虑最特殊化的版本。普通函数和一个模板特例化一样好,普通函数优先;如果无匹配,则出错;

如果推断有二义性,可以通过显式限定来消解二义性; max('a',1); //max('a',1);

       template class X

       {

           int i;

           std::vector<int> ivec;

           std::vector::iterator iter;

          

           T type;

           std::vector tvec;

           std::vector::iterator titer;

       };

       /* 前3个成员变量是不依赖于模板参数,所以是non-dependent name,后3个是dependent name

       ,直到实例化该模板的时候才会知道到底是什么类型。*/

 

 

//下面来讨论typename的第二种用法。现在假设我们有一个类如下:

   template class Y

   {

       T::iterator *iter;       ...

   };

   /* 我们可能本意是想定义一个迭代器对象,例如我们如果用vector来实例化这个模板,那么iter则应该是一个迭代器指针,但是,如果我们用下面这个类来实例化这个模板:*/

   class cType

   {

       static int iterator;     

   };

   /* 那么T::iterator *iter会被编译器解释为两个数相乘。事实上,C++编译器会采用第二种解释方法

   ,即使iterator的确是一个类型名。

   为了避免这种矛盾,当我们适用qualified dependent name的时候,需要用typename来指出这是一个

   类型名.即: */

   template class Y

   {

       typename T::iterator *iter;

       typedef typename T::iterator iterator; //定义了Y::iterator类型名称

       ...

   };

   //typename 指出下面紧跟着的名称是一个类型

------------------------------------------------

template

typename Iter::value_type mean(Iter first,Iter last);

代入后,假设Iter为int*, 则为: int*::value_type mean(int*, int*);

//指出value是一个依赖类型名,只有实例化后才知道是什么类型;这个类型作为mean的返回值;

C++不支持模板分别编译,在每个用到模板的编译单元中都#include模板定义;

将大的模板和较严重依赖上下文的模板分开编译;

模板为编译时计算和类型处理提供了一种强有力的机制,可以生成非常紧凑和高效的代码。

类型(类)即可以包含代码也可以包含值;

模板是C++支持泛型程序设计的主要特性,它提供了(编译时)参数化多态。

标准库对一些常见的运算,如plus,multiply提供了对应的函数对象,可用作实参。

std::plus,std::multiply

类型名和模板名首字母大写;

template

constexpr bool Small_size()

{

    return N <= 8;

}

//产生函数模板,调用则要加括号:Small_size<1>()

将概念表达为编译时谓词(constexpr函数),并用static_assert(),enable_if<>测试它们

值参数可以是:外部链接的对象或函数的指针或引用;指向非重载成员的指针,空指针

一个指针必须具有&of或是f的形式,才能作为模板实参,其中of是对象或函数的名字,f是函名,指向成员的指针必须具有&X::of的形式。其中of是成员的名字;字符串字面值不能作为模板实参;

char lx2 [] = "BMW3"; X x2 ; //ok,lx2具有外部链接

整数常量必须是一个常量;

在模板参数列表中,一个类型模板参数出现后即可当作一个类型来使用;

template

将比较类型作为一个模板类型参数传递,是STL采用的方式;

函数对象可携带状态: Complex_compare f3{"French",3};

map m3{f3};

传递函数对象的优点:

lambda不能转换为函数对象类型,可以命名lambda,然后使用名字:

auto cmp =[](const string& x, const string& y) const {return x < y;} //命名cmp

map c4 {cmp}; //推断类型,使用cmp.

模板作为实参:

template class C>

class X{

    C mems;

特例化:

template<>  //指明模板参数特例化版本;

class Vector{}; //指明特例化Vector,的版本 //完整特例化

template

class Vector:private Vector{ //部分特例化

模式中包含模板参数的特例化版本称为部分特例化(partial specialization)与完整特例化相对,其中“模式”指一个特定类型;

最通用的模板定义为主模板;主模板必须在特例化版本前声明;

最特例化版本优于通用其它版本;

只有在需要定义时才必须生成类模板的特例化。特别是,如果只是为了声明类的指针,是不需要实际的类定义;

模板函数,只有当它真正被使用时(调用,取地址),才需要一个函数实现来实例化它。实例化并不意味要实例化所有成员函数;

一个显式实例化请求(explicit instantiation)在语法上就是一个特例化声明加上关键字template前缀(template).

template class Vector;类

template int& vecot::operator[](int);成员函数

template int convert(double);非成员函数

模板声明以template<开始,而简单的template则表示一个实例化请求的开始

禁止隐式实例化: extern template class MyVector; //在其他某处实例化;

C++语言将模板定义中使用名字分为以下两类:1.依赖性名字:即依赖于模板参数的名字,这类名字在实例化点完成绑定;2.非依赖性名字:

如果被调用函数的实参或形参明显依赖于模板参数,则函数名是依赖性名字。

注意:默认情况下,编译器假定依赖名字不是类型名,因此,为了使依赖性名字可以是一个类型,你须用关键字typename显示说明。如:

templateContainer>

void fct(Container& c){

{

Container::value_type v1 = c[7] ; //语法错误,编译器默认假设依赖名不是类型名

typename Container::value_type v2 = c[9]; //ok.显示说明value_type是类型

auto v3 = c[11] ; //也正确,让编译器推断;

这时,我可以引用类型别名来代替typename的尴尬:

template

using Value_type = typename T::value_type;

 

template

void fct2(Container& c){

value_type v1 = c[7] ;// 展开还是用了typename 声明;但界面友好些

 

类似的,命名点号 .  ->或::后面的成员模板需要使用关键字template.

class Pool {

public:

template T* get();

...

template

void f(Alloc& all)

{

int* p1 = all.get();

int* p2 = all.template get();

}

void user(Pool& pool){

f(pool)

实参依赖查找:argument-dependent lookup,通常简称为ADL。

避免完全通用的模板可被ADL找到,使用概念或static_assert避免选择不恰当的模板,使用using声明限制ADL触及的范围,恰当地使用->或T::限定来自模板基类的名字;

操纵类和函数这种程序实体的编程通常称为元编程(metaprogramming).

元编程就是元数据和程序设计的组合,一个元程序就是一个编译时计算的代码,它生成运行时使用的类型或函数;

标准库模板conditional是一个编译时选择器,可实现两种方案中的一个,如果第一个实参求值为true,则结果(以成员type的方式给出)是第二个实参,否则,结果是第三个实参。

Conditional::value,Delay,Error>

谓词是返回bool值的函数;

void copy(T* p,const T* q,int n)

{

    if (std::is_pod::value)

        memcpy(p,q,n);

 

    else

        for (int i = 0; i != n; i++)

            p[i] = q[i];

}

 

如果在编译时无法获知对象的实际类型,就必须使用类层次;

一个基类指针可用作模板实参来提供运行时多态,一个模板参数可用来指定一个基类接口从而提供类型安全性;C++语言不支持virtual函数模板;

不存在set& set& 的内置类型转换,注意逻辑错误;

当定义一个指针模板时,我们可能希望反映所指对象间的继承关系,成员模板允许我们在需要时指定这种关系。

template

class Ptr{

    template

    explicit operator Ptr();

 

一个模板的模板参数列表和模板成员不能组合在一起; //模板成员:模板内套模板

模板类可用来为公共实现提供一个灵活类型安全的接口,

template

class Vector:private Vector{};

此技术通常可用来提供类型安全的接口,以及将类型转换的工作交给编译器,而不是强制用户编写转换操作;

继承和参数化的组合具有很强的表达力,类型安全,性能以及源代码规模最小化,而类层次和虚函数所提供的扩展性也不会被妥协掉;

类型函数,它接受至少一个类型参数或至少生成一个类型结果,如sizeof(T)。

类型函数不一定像常规则函数,is_polymorphic模板参数的形式接受参数,而将名为value的成员作为返回结果: if(is_polymorphic::value) std::cout << "bad...";

template

struct Array_type{

using type= T;

static const int dim = N;

};

using Array = Array_type;

Array::type x;

constexpr int s = Array::dim;

---------------------------------

constexpr int on_stack_max = sizeof(std::string);

template

struct Obj_holder{

    using type = typename std::conditional<(sizeof(T) <= on_statck_max),Scoped,On_head>::type;

 

使用:typename obj::type v1; //在栈中分配double

typename obj>::type v2 ; //在自由存储中分配array;

另一种:

template

using Holder = typename Obj_holder::type;

 

Holder v1;

Holder> v2 ;

 

template

constexpr bool Is_pod(){

    return std::is_pod::value;

}

 

Conditional<(sizeof(int)>4),fun1,fun2>{}(7);创建fun1或fn2,并调用;

 

可以将萃取理解为返回很多结果的类型函数或是一组类型函数;

Conditional<(std::is_polymorphic::value),X,Y> z;

Conditional(),Square,Cube>{}(99); //调用Square{}(99)或Cube{}(99);

constexpr版本即可以用于编译时求值,也可以用于运行时求值,模板(元编程)版本则只能用于编译时;

标准库提供了enable_if(type_traits)

Enable_if(),T>* operator->(); //如果是一个类,则选择一个成员;

C++程序通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象(POD)。

std::cout.setf(std::ios_base::fixed,std::ios_base::floatfield);

std::cout.precision(3);

std::streamsize prec = std::cout.precision(3);

std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed);

std::cout.setf(orig,std::ios_base::floatfield);

-------------------------------------------------

using std::cout;

using std::ios_base;

ios_base::fmtflags orig = cout.setf(ios_base::fixed,ios_base::floatfield);

std::streamsize prec = cout.precision(3);//saved..

//use std;;

..

cout.setf(orig,ios_base::floatfield);

cout.precision(prec);

构造函数:成员函数私有成员:string m_company //加m 或是末尾加下划线

string company_ //私有数据成员;

如果构造函数可以接受一个参数,则可以Bozo truby = 32; //可以这样初始化;

接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值;

this指针指向用来调用成员函数的对象,this指向调用对象,如果方法需要引用整个调用对象,则可以使用表达式*this。

class Bakery{

private:

    enum {Months = 12};

    doouble const[Months];

用这种方式声明枚举并不会创建类数据成员,所有对象中不含枚举,Months只是一个符号名称,遇到他时,编译器用12替换它;

C++提供了另一种定义常量的方式:使用static:

static const int Months = 12;

传统枚举存在的一些问题:如果有二个Small;

enum egg{Small,Medium,Large,Jumbo};

enum t_shirt{Small,Medium,Large};

将无法通过编译,位于相同的作用域内,冲突;C++引入了新枚举;

enum class egg{Small...

enum class t_shirt{Small...}

t_shirt roollf = t_shirt::Large;

声明底层类型:enum class:char pizza{Small,...

t4=t1+t2+t3;

t4 = t1.operator+(t2.operator+(t3));

-------------------------------------------

#include

ofstream fout;

fout.open("save.txt");

fout << "xxxx";

重载负号: Vector Vector::operator-() const {...}

#include rand(),srand(), time()

srand(time(0))

转换函数必须是类方法,转换不能指定返回类型,转换函数不能有参数;

operator double(); //返回double.不必再声明一次;

C++11可以显示声明转换函数: explicit operator int() const;//C++11支持将关键字

explicit用于转换函数,允许显式转换,但不允许隐式转换;

1.strlen(s).2.new char[len+1] 3.std::strcpy(str,s);num_string++

方法的行为取决于调用该方法的对象。

通常编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,这种数组称为虚函数表,虚函数表中存储了为类对象进行声明的虚函数地址。基类对象包含一个指针,该指针指向基类中所有虚函数的地址表,派生类对象将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本。调用虚函数时,程序将查看存储在对象中的vtb地址,然后转向相应的函数地址表。每个对象,新增一个指针;每个类,创建一个虚表(数组),对于每个函数调用,需要执行一项额外的操作,即到表中查找地址。

如果重新定义的继承的方法,应确保与原来的原型完全相同,但如果返回值类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变(convariance of return type),因为允许返回类型随类类型变化而变化。(说白了,就是如果只是返回类型不同,基类返回基类,派生类也可以返回派生类,如果只是返回类型有基类和派生类的差别,也算是重载;否则算隐藏基类版本)。

复制构造函数调用的时机:

1.新对象初始化为一个同类对象;2.按值将对象传递给函数;3.函数按值返回对象;4.编译器生成临时对象;

赋值运算符用于处理同类对象之间的赋值,不要将赋值与初始化混淆了,如果语句创建新的对象,则使用初始化,如果语句修改己有对象的值,则是赋值;

对于基类,即使它不需要析构函数,也应提供一个虚析构函数;

并不总是可以返回引用,临时对象消失,在这种情况下,应返回对象,以生成一个调用程序可以使用的副本。

blips = snips;  // 将调用blips.operator=(snips);

派生类引用不能自动引用基类对象,除非有转换构造函数;另一种方式是定义一个用于将基类赋给派生类的赋值运算符;

valarray v3(10,8) // 8个整形,每个值为10

valarray v5 {1,2,3,4,5};

方法:operator[],size(),sum(),max(),min();

初始化的顺序为它们被声明的顺序,而非初始化列表中的顺序;

如果是私有继承,不能隐式向上转换。注意;

使用using重新定义访问权限:将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员,即使采用的是私有派生。如

class Student:private std::string,private std::valarray{

public :

    using std::valarray::min; //using 引入,编译器默认是私有派生;

虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。

class Singer:virtual public Worker{...};

 

C++在基类是虚的时候,禁止信息通过中间类自动传递给基类,有虚基类的构造函数应该是:

SingingWaiter(const Worker& wk,int p = 0, int v = Singer::other):

Worker(wk),Waiter(wk,p),Singer(wk,v){}

对于虚基类,显式调用构造函数Worker(const Worker&),这是合法的,对于虚基类,必须这样做,但对于非虚基类,则是非法的;调用的问题,如果调用基类的show.有二个,可以使用作用域解析运算符来澄清编程者的意图:

SingingWaiter newhire("Elise Hawks",2005,5,spo);

newhire.Singher::show(); //用了作用域解析符;

模板不是函数,它不能单独编译,模板必须与特定的模板实例化请求一起使用。

泛型标识符type,称为类型参数,

template 关键字class指出T为类型参数,int指出n的类型为int,这种参数

称为非类型参数(non-type)表达式参数,假设有下面的声明;ArrayTP egights; 定义名为ArrayTp的类,并创建一个类型为ArrayTP的对象;表达式参数(后面的12)可以是整形,枚举,引用或指针;模板代码不能修改参数的值,也不能使用参数的地址,

模板也可以有默认参数: template class Txx{...};

具体化template<> class T{...};老编译器不接受模板成员,一些接受,但不接受类外面定义,然而,如果所用的编译器接受外面的定义,则会出现二个template语法;

template

class bate{

template //又声明一个,hold ,要用,内部类

class hold;

    hold q;

};类声明完了,

在类外定义的语法:

template

  template

    class bate::hold{ V val;...//还必须指出是bate::的类成员

这就会出现双重template;

将模板用作参数:template