C++语言笔记C11库

简介:

1.C++函数库

Algorithms 

<algorithm> 

C Library Wrappers 

<cassert> , <cctype><cerrno><cfenv><cfloat><cinttypes><ciso646><climits><clocale><cmath><csetjmp><csignal><cstdarg><cstdbool><cstddef><cstdint><cstdio><cstdlib><cstring><ctgmath><ctime><cwchar><cwctype> 

Containers 

Sequence 

<array> 、<deque><forward_list><list><vector> 

Ordered Associative 

<map> , <set> 

Unordered Associative 

<unordered_map> , <unordered_set> 

Adaptor 

<queue> , <stack> 

Error Handling 

<exception><stdexcept><system_error> 

Input/Output 

<filesystem> , <fstream><iomanip><ios><iosfwd><iostream><istream><ostream><sstream><streambuf><strstream> 

Iterators Library 

<iterator> 单独的迭代器通常用于泛型算法中,而不会单用

Localization 

<codecvt> , <cvt/wbuffer><cvt/wstring><locale> 

Math and Numerics 

<complex> , <limits><numeric><random><ratio><valarray> 

Memory Management 

<allocators> , <memory><new><scoped_allocator> 

Multithreading 

<atomic> , <condition_variable><future><mutex><thread> 

Other Utilities 

<bitset> , <chrono><functional><initializer_list><tuple><type_traits><typeinfo><typeindex><utility> 

Strings and Character Data 

<regex> , <string> 

 

1.C++命名空间
C++只有一个命名空间std,包含了所有的C++库。关于命名空间,同一个命名空间的名字可以在不同的文件或同一文件中多次出现,也可以出现在头文件里面,表示同一命名空间的不同部分代码。

2.C++引用与指针
C++的引用一般用于函数入参和函数返回值。引用和指针其实存在本质的区别,引用就是一个对象的别名,没有分配存储对象指针的存储空间;而指针需要分配一个存储对象指针的存储空间。所以指针和引用一般情况下是没有办法相互转换的。C++中,如果对象没有名字(如用new分配的对象)那么它也不可能有别名,而其他语言(如C#)可以只有别名。

3.C++的模板:针对类型的一种统一操作
C++的模板分为函数模板和类模板,两者声明的形式都一样。
template <class T1,...,class Tn> 函数或类声明
{
函数体或类体
}
函数模板在调用时自动实例化,而类模板必须先实例化(指明类型),才能调用。

4.关于throw的栈展开
throw进行栈展开时会调用很多析构函数或进行很多函数的局部变量回收,直到遇到了catch为止。

5.操作符重载
操作符的本质是一个函数,其重载有两种情况:第一种操作符在类中作为成员函数重载;第二种操作符作为全局函数重载。两种重载方式有一点区别:1)在类中作为成员函数重载,将默认第一个函数入参(双目时为左操作数)为this指向的本类对象,所以在类中重载操作符时,双参在形式上变成了单参。2)基于第一条规则,有些左操作数特殊的操作符(如<<>>等要求左操作数必须为流对象)不能在类中重载。在众多的运算符中,大多对左右操作数是敏感的,不允许左右操作数交换。第一个入参是左操作数,第二个入参是右操作数。

6.友元
在类中定义一个友元函数(或重载的操作符)或友元对象,那么在该友元中可以以‘.’访问该类对象的私有成员,这是在其他非友元作用域里做不到的。
Class A{private :int a ;friend void myfunc(void);}
void myfunc(void)
{
A  myA;
cout<<myA.a;//在非友元作用域中不能这样访问。
}

7.容器
所有容器提供的内置方法绝大部分是与迭代器有关的,大部分泛型算法也与迭代器有关。
迭代器和指针是完全一样的,但是迭代器更安全。
容器在构造时可以使用其他不同类型的容器的迭代器或指针来构造,但是需要保证两种容器或数组的数据类型是一致的。

8.

头文件

容器

说明

<iostream>

istream

Wistream,wostream,wiostream

从流中读写数据

ostream

iostream

<fsteram>

ifstream

Wifstream,Wofstream,wiofstream

从文件中读写数据

ofsteram

fsteram

<sstream>

istringstream

Wistringstream,Wostringstream,wstringstream

字符串流中读取数据(底层没有字符串)

ostringstream

stringstream

在流对象使用中,如果产生任何失败,则其对象为0,否则为非0,相应的错误状态被记录在该对象中。

例:Int a;Cin>>a;如果输入了一个字符,那么将产生错误。则cin==0If (cin) 将为false。其它流都有这种用法。

流的作用:->本质上是字符处理。将字符从不同的底层以多种格式(或方式)输入到内存中来,或者相反。以下是例举的几种流的作用:

(1)以单个字符、字符串、行任意形式存取字符

(2)以整形、浮点型等形式存取字符

wKioL1XpElngE4CEAAClZx9iNEQ694.jpg 

 

9.lambda 函数

返回值类型[可使用的全局变量列表](形参列表){函数体}

如果“可使用的全局变量列表”为“=”,则能使用所有的全局变量

10.顺序容器

特点:根据位置存储和访问容器,容器元素的排列次序与元素值无关,而由元素添加到容器里的次序决定。标准库只定义了三种顺序容器:vector,list,deque.

和三种顺序容器适配器(适配器是以基本的容器类型为底层,通过定义新的操作接口,实现不同的数据操作):stack,queue,priority_queue

容器和容器适配器都只提供了少量操作,大部分操作由泛型算法库提供。

容器的长度都是动态可增长缩短的。

容器元素的类型约束有两点:元素类型必须支持赋值运算和元素类型对象必须可复制。(即不能为引用和流)

迭代器的范围为[iter.begin,iter.end),其编程意义为:当iter.begin== iter.end迭代器范围为空。

 

容器内置的操作主要有:

任意位置任意方式增减元素个数的操作,迭代操作,交换元素操作。

11.关联容器
基本的有4种:map,set,multimap,multiset

map中有个不好的地方:

Map<string,int>word_count;

Int occurs=word_count[foobar];

这样的查找如果foobar键不存在于word_count容器中,那么将会增加一个foobar键,并返回值为0。这是编译语言与解释语言的区别。.find方法提供了判断该键是否存在的方法。

.count方法提供了判断mapmultimap中键个数的方法。

python中能实现的操作,在C++中基本上都有这些功能。没有相应的语法支持,但是可以提供对象内置方法来解决(反射除外)

Set容器提供了insert,count,find,erase等操作。可以使用其他容器的迭代器来构造set容器,只保留不重复的元素集合。

除了容器内置的迭代器以外,C++还提供了3类独立迭代器:

1)插入迭代器
back_inserter,front_inserter,inserter.使用容器构造

2)流迭代器
istream_iterator,ostream_iterator

3)反向迭代器
reverse_iterator

12.指向类成员的指针

Class screen{;}

定义指向数据成员的指针:Int screen::*ptr;定义了一个指向screenint类型成员的指针。

定义指向函数成员的指针:void (screen::*ptr)(void);定义了一个指向类函数的指针

指向类成员的指针可以定义在类中也可以定义在类外。

使用定义在类中的指向类成员的指针:两个新的解引用操作符->*.*,他们能够将成员指针绑定到实际对象。

 

成员函数指针列表 :typedef void (*action)(void);

screen::action screen::menu[]={&screen::home,...}

13.auto自动数据类型

Auto int,double不同,他能推断数据的类型,从而将其转化为推断出的类型。

14.序列for循环

所有能使用iterator的序列都能使用序列for循环

Map<string,int>m{{a,1},{b,2},{c,3}};

For(auto p:m){cout <<p.first<<:<<p.second<<endl;}

15.c98和c11的智能指针

(1)auto_ptr 能够代理new[]和new,能赋值(与unique_ptr的区别),但同一时刻只能有一个auto_ptr管理指针(不能放在容器中,但unique_ptr能保存容器指针),不带有引用计数(c98)

(2)unique_ptr 能够代理new[]和new,不能转让,不允许拷贝构造和拷贝赋值(不能放在容器中,但unique_ptr能保存容器指针),不带有引用计数,可以完全代替scoped_ptr和scoped_array(c11)

(3)shared_ptr能够代理new的指针,实现了引用计数(应该是new int了一个内存用来保存引用计数,当发生shared_ptr赋值时将引用计数加1并将引用计数指针赋给另外一个shared_ptr),支持拷贝和赋值,可以放入容器中且可以保存容器指针(make_share模板函数)(最有价值的指针)(c11)

(4)shared_array代理new[]的指针,shared_array能力有限。一般使用shared_ptr<vector>或vector<shared_ptr>来替代(c11)

(5)weak_ptr 该指针是辅助shared_ptr的,shared_ptr给weak_ptr赋值不会增加引用计数(c11)

(6)boost/smart_ptr.hpp

scoped_ptr 接受new的指针,不能转让(不支持拷贝和复制(私有拷贝和复制函数实现),不放入作容器),且只能在本作用域内使用,与auto_ptr基本相同

scoped_array接受new[]的指针,不能转让(不支持拷贝和复制(私有拷贝和复制函数实现),不能用作容器),且只能在本作用域内使用,可以用vector替代

16.一些需要注意的C++编程细节

(1)环形缓存:
#define SIZE 100
int Buffer[SIZE];
Buffer[i%SIZE]='X'

(2)类的复制控制:
定义private的复制构造函数(如IO类),可以保证该对象只有一个副本。通过设计模式还可以使得该类只被构造一次。
classfa a;
classfa b=a;//其实是调用了classfa的默认复制构造函数 classfa(const classfa &)
string c="ancd" //调用了字符构造函数和复制构造函数。

(3)顺序容器:string,vector,list都可以使用.erase()和insert()在任意位置删除和插入指定迭代器的节点。

(4)友元friend 是不支持继承的。即类A是BASE的友元,但它不会是该BASE 的CHILD的友元。

(5)一个C标准函数:将从流中读出的数据,重新压入流的缓存中,可以通过此函数实现peek()函数
    ungetc( char ch, FILE* f);   //向IO缓存中压入数据
   一次只能压入一个字符

(6)递归:(连接2个链表)
已知两个链表head1 和 head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 
Node * MergeRecursive(Node *head1 , Node *head2) 

if ( head1 == NULL ) 
return head2 
if ( head2 == NULL) 
return head1 
Node *head = NULL   
if ( head1->data < head2->data ) 

head = head1   
head->next = MergeRecursive(head1->next,head2); 

else 

head = head2   
head->next = MergeRecursive(head1,head2->next); 

return head 
}

(7)引用:
常引用:不能通过引用改变变量的值
int b=1;
const int & a=b;
a=2;//错
b=2;//对

string foo( ); 
void bar(string & s); 
那么下面的表达式将是非法的:
bar(foo( )); 
bar("hello world"); 
    foo()的返回值必然是const string型的,“hello world”也被编译器构造为const string,而把他们用于非const函数入参,编译器将会报错。函数的引用入参应尽量指明为const,这样const和非const的实参都不会报错。
    引用作为函数返回值类型的好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!

(8)求一个数中包含的二进制1的个数:
def func(x):
cont=0;
while(x):
  x=x&(x-1)
  cont=cont+1
return cont
求任意数据由二进制表示时包含的1的个数

(9)虚析构函数作用:
将析构函数定义为virtual 这样在用delete删除基类指针时可以运行子类的析构函数。

(10)mutable关键字:在const变量中被改变
c++ 中 mutable关键字申明的变量可以在const申明的函数中改变。
class M
{mutable int a;
void set() const
{a=10;}
}

(11)静态成员:
class M
{
static M a; //correct
M *b;   //correct
M c;     //error
}
静态成员是类的一部分,而不是对象的一部分。

(12)const 用法:
const对象不能调用非const的成员函数和变量;为了使const对象和非const对象都可以调用一个同名函数,可以重载该非const和const成员函数。

(13)c数组:
在c/C++中数组是一种完整的数据类型,同结构体,对象一样:
main( ){
  using namespace std;
  int num[5]={1,2,3,4,5};
  cout <<*((int *)(&num+1)-1) <
  }
  在C语言中,一维数组名表示数组的首地址,而且是一个指针.如上例num,
  对&num,表示指针的指针.意味着这里强制转换为二维数组指针.
  这样 &num+1 等同于 num[5][1],为代码空间. (&num+1)-1表示 num[4][0].即num[4].

char (*a)[3][4] 定义一个数组指针

(14)switch的入参:
switch的参数不能是不能自动转化为整型的参数。
因为switch后面只能带自动转换为整形(包括整形)的类型,比如字符型char,unsigned int等,实数型不能自动转换为整形.可以手动强转实数型(int)double,但是会导致精度的丢失.如果后面要对实数型做选择的话,可以乘以10的倍数,然后进行选择,这样不会丢失精度.但是这样的话就要靠你去手动的控制乘以多少了

(15)sizeof

使用sizeof对变量求所占内存长度时,是对该变量直接求内存长度,需要搞清楚,sizeof的对象是谁。

char *p=“dk4m7”;//sizeof(p),p内存为4字节

char p[]="dk4m7";//sizof(p),p内存为6字节,(p符号相当于一个引用,本身不占内存)

(16)多维数组

char a[2][3][4]={{{1,2,3,4},{5,6,7,8},{9,10,11,12}},{{13,14,15,16},{17,18,19,20},{21,22,23,24}}};

a符号表示一个引用,不占内存。其指向的地址为a[0],a[0][0],&a[0][0][0]

a表示一个3维数组,a+1表示最高维加1,相当于a[1]的首地址

*a表示一个2维数组,*a+2,表示2维数组加1,相当于a[0][2]的首地址

*(a+1)+2相当于a[1][2]的首地址

*(*(a+1)+2)+3相当于a[1][2][3]的首地址





c11新特征

1.列表初始化

任何对象或对象数组都可以使用列表初始化,列表赋值不支持任何形式的类型转化,包括浮点到整形的转化。

例子:以下4种等价

int s=0;

int s={0};

int s{0};

int s(0);

vector<int> vi{1,2,3,4,5};

vector<int> *vi=new vector<int>{1,2,3,4,5};

vi={5,6};//任何形式的列表赋值

map<string,string> authors={{"joyce","james"},{"austen","jane"}};//关联容器的列表初始化

pair<string,string>authors{"james","joyce"};//列表初始化pair


2.constexpr 编译时验证,赋值表达式是否为常量赋值

constexpr int mf=20;

constexpr int linit=mf+1; //正确,编译时计算

constexpr 也可用于函数,要求函数只用一个return语句,并且形参类型必须是字面值类型

3.类型别名申明

typedef double  base,*p;   /旧方法。  base是double的别名;p是double*的别名

using double=base

using double*=p             //新标准使用USING申明变量别名

4.auto 自动推断赋值表达式的左值类型

auto b=20;

auto &a=b;

5.decltype  作用同auto,只能推断表达式的类型。但是可以灵活的指定表达式

const int ci=0,&cj=ci,p=&ci;

decltype(ci) x=0;

decltype(cj) y=x;   //指定推断cj表达式的类型,推断的类型为引用

decltype(*p) z=10;  //对×解引用推断的结果是引用,必须初始化

decltype((ci)) m=ci; //强制推断为引用  (3种引用推断方式)

6.类实例化时,使用()和不使用()的区别

classA a;//不会进行处理为显式初始化的成员

classA a();//会将a的未初始化的成员默认初始化为0 


7.范围for

string str("some string");

for(auto c:str)


8.initializer_list 

a.initializer_list函数形参列表

void error_msg(ErrCode e,initializer_list<string> il){

 for( const auto &elem:il){

    cout<<elem<<"  "<<endl;

  }

}


调用:

 error_msg(ErrCode(42),{"func1","ok","sd"});

 

 b.initializer_list函数返回值

 vector<string> process(){

  if(expected.empty()){

     return {};

   }else if(expected==actual){

     return {"fun","ok"};

    else{

     return {"fun","ok","lk"};

    }

   }

 }

 

 9.函数的尾置返回类型

 对于函数的返回值类型较长的函数可以使用尾置返回类型

 auto func(int i)  -> int(*)[10]   //返回一个指向10个元素的数组指针

10. =default 申明默认构造函数

有时在含有有参构造函数时,也需要默认构造函数,这是就需要使用=default申明

Sales_data()=default;

11.c11支持的所以顺序容器

vector,deque,list,forward_list(单向链表),array,string

都可以使用列表初始化

12.lambda函数

形式:

[捕获参数列表](形参列表){...;[return]}   //无需指定返回值类型

捕获参数列表中有2个特殊的符号

&:表示所有外部变量的捕获使用引用

=:表示所有外部变量的捕获使用值

13.bind函数

用于绑定函数名及其参数,可以交换参数位置。bind的返回值一般直接赋给auto


14.智能指针

shared-ptr<?>  实现引用计数,可以转移或赋值

unique-ptr<?>  不能转移或赋值,取代auto-ptr<?>   但允许swap(因为该函数实现的是转移)

scoped-ptr<?>  可以转移或赋值,但是同时只可能有一个对象持有指针

weaked-ptr<?>  解决share-ptr<?>的循环链中发生内存泄露的问题。


15.移动构造函数,右值引用,标准库中的move函数,移动赋值

  Quote& operator=(Quote&&){...} //移动赋值

16.final 通过定义类的final来阻止继承

class NoDerived final{...}


17.模板的类型别名

template<typename T> using twin=pair<T,T>;

twin<string>authors{"jim","ali"};//是一个pair



c11补充


1.异常

(1)throw可以抛出异常对象和异常对象的指针,当抛出指针是必须保证catch处该指针变量还有效(一般必须在同一函数中处理)。

range_error r("error");

throw r; 抛出异常对象

exception *p=&r;

throw *p;抛出异常对象的指针

(2)栈展开

栈展开:抛出异常时将暂停当前函数的执行,开始查找匹配的catch子句。首先检查throw本身是否在try块内部,如果是,检查与该try相关的catch子句,看是否其中之一能与被抛出的对象匹配,如果找到匹配的catch,就处理异常;找不到,就退出当前函数,并且继续在调用函数中查找。

(3)栈展开要执行的操作

a.为局部对象调用析构函数:当一个包含throw的函数由于异常提早退出时,该函数的栈上的局部存储会被释放,将释放在throw调用前创建的所有栈对象。如果局部对象是类类型的,就直接调用对象的析构函数。new分配的对象不能释放。

b.析构函数应该从不抛出异常:栈展开期间会经常执行析构函数,如果析构函数再次抛出自己没有处理的异常,将导致系统调用terminate函数,使整个程序崩溃。所以编程者不要在析构函数中抛出异常,并且必须处理析构函数中的默认异常。

c.构造函数常常抛出异常:如果在构造对象的时候发生异常,则对象可能部分被构建而一些成员可以没有被初始化。需要保证适当的撤销以构造的元素。

d.异常被抛出到线程入口时还没有被捕获,则程序将调用terminate终止程序。

(4)catch

a.catch中可以使用throw;不加对象或rethrow重新抛出异常。被抛出的异常是原来的异常对象而不是形参。如果想改变抛出的异常对象的成员,形参可以使用&引用

(5)容器与数组

void fun(){

 vector<string> v;

 while(cin>>s){

  v.push_back(s);

 }

 string *p=new string[20];

 //do something

 delete[] p;

}


如果在do something发生了任何异常,那么Vector的内存能够正常释放,但数组p的内存无法释放。因为不管何时发生异常析构函数总是会被调用的。

(6)用类来分配资源

将分配资源的额动作一定要写在类中,而不是单个的函数中,这样在析构函数中写释放资源的操作,保证资源总能被释放。

这种通过定义类来封装资源的分配和释放的技术称为RAII(资源分配即初始化)。原理就在于无论是否发生异常,类的析构函数总是会被调用的。

(7)auto_ptr类(保证异常安全-new出的对象自动释放)

auto_ptr类是RAII技术的一个例子。auto_ptr类可以接收任意一个非数组类型或模版的形参,为动态分配的对象(new)提供异常安全。auto_ptr可以保存任何类型指针。auto_ptr类的对象唯一持有指向的对象(不提供引用计数),这也决定了它不能放在容器中。

auto_ptr对象的复制和赋值是破坏性操作,将改变右操作数。

auto_ptr<string>ap1(new string("stg"));

auto_ptr<string>ap2(ap1);//ap1将解除绑定

ap1=ap2; //将对ap1以前指向的对象执行析构(虽然ap1没有指向对象)

此时将解除右操作数对指针指向的对象的绑定,同时对左操作数原来指向的对象执行析构。(左右操作数都将有影响)

auto_ptr赋值时不能直接将一个地址赋给auto_ptr对象

p_auto=new int(1024);//error

而必须调用auto_ptr的reset函数来重新赋值

if(p_auto.get())//判断该指针是否为0

   *p_auto=1024;

else

  p_auto.reset(new int(1024)); //可以用0来重置p_auto.reset(0)。调用reset方法可以使多个auto_ptr指针指向同一对象。

自动指针赋值(ap1=ap2;)的过程为:对左边执行析构;左边得到右边的值;对右边执行解除绑定

auto_ptr指针释放对象时使用的是delete而不是deleted[],所以他不能管理数组。

由于auto_ptr不满足复制和复制操作,所以他不能放置在容器中。auto_ptr作为形参时,必须使用引用。

(相当于auto_ptr在内存中是静止的,不能移动)

(8)函数的异常说明

void fun(void) throw (runtime_error,logic_error)

{;}//表明该函数只可能抛出一个runtime_error的类及其子类

void fun(void) throw ()

{;}//表明该函数不会抛出异常

如果函数抛出了其他没有说明的异常,那么系统将会调用unexpected函数,将会异常终止程序。

类继承时,在虚函数中,子类抛出的异常说明必须比基类可能抛出的异常少,且必须是基类异常的一个子集。

(9)函数指针的异常说明

void (*pf)(int) throw(runtime_error);

表明pf只能指向一个只可能抛出runtime_errorauto_ptr类的函数。



2.运行时类型识别(RTTI)

c++通过一下2个操作符提供RTTI功能

(1)typeid操作符,返回指针或或引用所指向对象的实际类型。(基类和子类是相等的)

(2)dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类型的指针或引用。


3.c++11的移动语义与右值引用

C++ 11中引入的一个非常重要的概念就是右值引用。理解右值引用是学习“移动语义”(move semantics)的基础。而要理解右值引用,就必须先区分左值与右值。

对左值和右值的一个最常见的误解是:等号左边的就是左值,等号右边的就是右值。【左值和右值都是针对表达式而言的】,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。【一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。】下面给出一些例子来进行说明。

移动语义体现在2点:

1. g++ -std=c++11 main.cpp   c++11标准使用了赋值的移动语义 object a=foo();即将a与foo()返回的零时对象内存指针交换,然后释放零时变量指向的内存。

2. 定义的&&对零时变量进行引用(右值引用),常作为函数入参。用于获取临时变量中可以复用的内存。

例如:

 container& operator = (container&& other)   //移动赋值

    {

        delete value; //假如value是一个使用new分配的内存指针,此时就可以复用这个内存。

        value = other.value;

        other.value = NULL;

        return *this;


    }

 转移语义的定义

右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。


转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。


通过转移语义,临时对象中的资源能够转移其它的对象里。


在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,【需要定义转移构造函数,还可以定义转移赋值操作符】。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。


普通的函数和操作符也可以利用右值引用操作符实现转移语义。  

  char* _data; 

  size_t   _len; 

    void _init_data(const char *s) { 

    _data = new char[_len+1]; 

    memcpy(_data, s, _len); 

    _data[_len] = '\0'; 

  } 

    MyString(const MyString& str) { 

    _len = str._len; 

    _init_data(str._data); 

    std::cout << "Copy Constructor is called! source: " << str._data << std::endl; 

  } 

   

  MyString(MyString&& str) { 

    std::cout << "Move Constructor is called! source: " << str._data << std::endl; 

    _len = str._len; 

    _data = str._data; 

    str._len = 0; 

    str._data = NULL; 

 }


和拷贝构造函数类似,有几点需要注意:

a. 参数(右值)的符号必须是右值引用符号,即“&&”。

b. 参数(右值)不可以是常量,因为我们需要修改右值。

c. 参数(右值)的资源链接和标记必须修改。否则,右值的析构函数就会释放资源。转移到新对象的资源也就无效了。   

    

转移语义的目的:实现零时对象中申请的资源的复用。    

    

    

4.c++的成员指针调用成员函数(非virtual函数,实现基类调用子类的函数)


方法1:非静态map存储子类的成员函数,并遍历。原理类似于虚函数(可见:只要基类有子类成员函数的指针就可以调用到子类的成员函数)

#include "stdafx.h"

#include <iostream>

#include <string>

#include <map>

using namespace std;


class Inr{

public:

virtual void display(void){;}

};


class Myview :public Inr{

public:

typedef void (Myview::*memfun)(void);

 map<string,memfun> sav;  //如果为静态将出现无法连接的编译错误;如果这里map定义为静态的,则必须在类外申明一下,以表示初始化,方式为map<string,memfun>Myview::sav;即可(复杂类必须在类外申明初始化,分配内存)


public:

 void Myview::display(void){

map<string,memfun>::iterator itr=sav.begin();   

for(;itr!=sav.end();itr++){

(this->*(itr->second))();

}

}

};


class Childview :public Myview{

public:

Childview(){

sav["disview"]=(memfun)disview;

}

public:

void disview(void){

cout<<"this is child view"<<endl;

}

};


int main(int argc, char* argv[])

{

Childview Am;

Am.display();

return 0;

}


方法2:静态数组存储子类的成员函数,并遍历。

 #include <OBJBASE.H>

using namespace std;


interface Base1{

     public:

     virtual void ExeFunc(void)=0;

     };


typedef void (Base1::*Pfunc)(int);

typedef struct typefunclink{

     Pfunc * PFmen;

  typefunclink *MemLink;

}TypeFuncLink;


class Base2: public Base1{

public:

     Base2(){;};

     ~Base2(){;};

     static Pfunc Memfunc[];

     static TypeFuncLink LkupList;

     void B2Printf(int);

      virtual void ExeFunc(void);

     };


     Pfunc Base2::Memfunc[2]={(Pfunc)(&B2Printf),0};


     TypeFuncLink Base2::LkupList={Base2::Memfunc,0};

     

     void Base2::B2Printf(int){

           cout<<"This Base2 Class Print"<<endl;

     }

     void Base2::ExeFunc(void)

     {      

                 TypeFuncLink LkLink=Base2::LkupList;

                 while(LkLink.PFmen||LkLink.MemLink)

                 {int i=0;

                       while(LkLink.PFmen[i])

                       {

                             Pfunc am=LkLink.PFmen[i];

                             (this->*am)(0);

                             i++;

                       }

                       if(LkLink.MemLink)

                             LkLink=*(LkLink.MemLink);

                       else

                             break;

                 }

     }

class Base3: public Base2{


public:

     Base3(){;};

     ~Base3(){;};

     static Pfunc Memfunc[];

     static TypeFuncLink LkupList;

     void B3Printf(int);

      virtual void ExeFunc(void);

     };


     Pfunc Base3::Memfunc[2]={(Pfunc)(&B3Printf),0};


     TypeFuncLink Base3::LkupList={Base3::Memfunc,&Base2::LkupList};

     

     void Base3::B3Printf(int){

           cout<<"This Base3 Class Print"<<endl;

     }

     void Base3::ExeFunc(void)

     {

                 

                 TypeFuncLink LkLink=Base3::LkupList;

                 while(LkLink.PFmen||LkLink.MemLink)

                 {int i=0;

                       while(LkLink.PFmen[i])

                       {

                             Pfunc am=LkLink.PFmen[i];

                             (this->*am)(0);

                             i++;

                       }

                       if(LkLink.MemLink)

                             LkLink=*(LkLink.MemLink);

                       else

                             break;

                 }

                 

                 

     }            

     void main(void)

     {

           Base3 cl;

           cl.ExeFunc();

           system("pause");

     }   

    

   

5.线程中join和detach的作用

无论在windows中,还是Posix中,主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死(部分线程保持一种

终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态),需要强调的是,线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态,但千万要记住的是,进入终止态后,为线程分配的系统资源并不一定已经释放,而且可能在系统重启之前,一直都不能释放。终止态的线程,仍旧作为一个线程实体存在于操作系统中。(这点在win和unix中是一致的。)而什么时候销毁线程,取决于线程属性。通常,这种终止方式并非我们所期望的结果,而且一个潜在的问题是未执行完就终止的子线程,除了作为线程实体占用系统资源之外,其线程函数所拥有的资源(申请的动态内存,打开的文件,打开的网络端口等)也不一定能释放。所以,针对这个问题,主线程和子线程之间通常定义两种关系:

      (1)可会合(joinable)。这种关系下,主线程需要明确执行join等待操作。在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合。这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程,Thread类中,这个操作通过在主线程的线程函数内部调用子线程对象的wait()函数实现。这也就是上面加上三个wait()调用后显示正确的原因。必须强调的是,即使子线程能够在主线程之前执行完毕,进入终止态,也必需显示执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源(线程id或句柄,线程管理相关的系统资源)也永远不会释放。

      (2)相分离(detached)。顾名思义,这表示子线程无需和主线程会合,也就是相分离的。这种情况下,子线程一旦进入终止态,系统立即销毁线程,回收资源。无需在主线程内调用wait()实现会合。Thread类中,调用detach()使线程进入detached状态。这种方式常用在线程数较多的情况,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或者不可能的。所以在并发子线程较多的情况下,这种方式也会经常使用。缺省情况下,创建的线程都是可会合的。可会合的线程可以通过调用detach()方法变成相分离的线程。但反向则不行。  

6.运算符重载

运算符重载的实质是函数重载,它提供了C++的可扩展性,也是C++最吸引人的特性之一。运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的。运算符函数定义的一般格式如下:

<返回类型说明符> operator <运算符符号>(<参数表>)

{

  <函数体>

规则:

(1)不可重载的5个运算符:类属关系运算符"."、成员指针运算符".*"、作用域运算符"::"、三目运算符"?:"  和sizeof运算符

(2)重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符

(3)重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。

(4)...

在进行对象之间的运算时,程序会调用与运算符相对应的函数进行处理,所以运算符重载有两种方式:成员函数重载方式和友元函数重载方式。成员函数的形式比较简单,就是在类里面定义了一个与操作符相关的函数。友元函数因为没有this指针,所以形参会多一个。

class A

{

public:

 A(int d):data(d){}

 A operator+(A&);//成员函数

 A operator-(A&);

 A operator*(A&);

 A operator/(A&);

 A operator%(A&);

 friend A operator+(A&,A&);//友元函数

 friend A operator-(A&,A&);

 friend A operator*(A&,A&);

 friend A operator/(A&,A&);

 friend A operator%(A&,A&);

 

bool operator == (const A& ); 

bool operator != (const A& );

bool operator <  (const A& );

bool operator <= (const A& );

bool operator >  (const A& );

bool operator >= (const A& );


bool operator || (const A& );

bool operator && (const A& );

bool operator !  ();


A& operator + ();  //这里的+、-是正负的意思,放在对象前面。 

A& operator - ();

A* operator & ();

A& operator * ();  //取对象运算


A& operator ++ ();//前置++

A operator ++ (int);//后置++    ,这个int是没有意义的,仅仅与前置做区分

A& operator --();//前置--

A operator -- (int);//后置-- ,返回不能是“引用”,因为返回的是一个右值对象




A& operator ++ ();//前置++  //++和–根据位置的不同有四种情况,都可以重载。

A operator ++ (int);//后置++

A& operator --();//前置--

A operator -- (int);//后置--


A operator | (const A& );

A operator & (const A& );

A operator ^ (const A& );

A operator << (int i);

A operator >> (int i);

A operator ~ ();


A& operator += (const A& );

A& operator -= (const A& ); 

A& operator *= (const A& );

A& operator /= (const A& );

A& operator %= (const A& );

A& operator &= (const A& );

A& operator |= (const A& );

A& operator ^= (const A& );

A& operator <<= (int i);

A& operator >>= (int i);


void *operator new(size_t size);

void *operator new(size_t size, int i);

void *operator new[](size_t size);

void operator delete(void*p);

void operator delete(void*p, int i, int j);

void operator delete [](void* p);


A& operator = (const A& );

char operator [] (int i);//返回值不能作为左值

const char* operator () ();

T operator -> ();

//类型转换符

operator char* () const;

operator int ();

operator const char () const;

operator short int () const;

operator long long () const;


friend inline ostream &operator << (ostream&, A&);//输出流   这些只能以友元函数的形式重载 

friend inline istream &operator >> (istream&, A&);//输入流

private:

 int data;

};



A& A::operator++( ) //前置

{

data++;

return *this;

}

A A::operator++( int ) //后置

{  A temp(*this);

data++;

return temp;

}


//成员函数的形式

A A::operator+(A &a)

{

 return A(data+a.data);

}

A A::operator-(A &a)

{

 return A(data-a.data);

}

A A::operator*(A &a)

{

 return A(data*a.data);

}

A A::operator/(A &a)

{

 return A(data/a.data);

}

A A::operator%(A &a)

{

 return A(data%a.data);

}

//友元函数的形式

A operator+(A &a1,A &a2)

{

 return A(a1.data+a2.data);

}

A operator-(A &a1,A &a2)

{

 return A(a1.data-a2.data);

}

A operator*(A &a1,A &a2)

{

 return A(a1.data*a2.data);

}

A operator/(A &a1,A &a2)

{

 return A(a1.data/a2.data);

}

A operator%(A &a1,A &a2)

{

 return A(a1.data%a2.data);

}


运算符函数的特点:

(1)一般都会有返回值

(2)入参一般为尽量使用“引用”

(3)尽量返回*this对象,而不要构造一个对象返回

(4)返回值是“引用”的,一般都返回*this对象

(5)第一个操作数是stream的,只能以友元的形式重载


6.c11多线程与异步任务

(1)线程创建

c11的线程创建比任何一种语言都要简单.

void foo(int x,int y)

  {

    // x = 4, y = 5.

  }

void main()

  {

    std::thread t(foo,4,5);  //foo可以是匿名函数 

    t.join(); //t.detach(); 

  }

(2)异常简单的mutex和recursive_mutex

std::mutex m;  //std::recursive_mutex

int j = 0;

void foo()

  {

  m.lock();        // 进入临界区域

  j++;

  m.unlock();      // 离开

  }

void main()

  {

  std::thread t1(foo);

  std::thread t2(foo);

  t1.join();

  t2.join();

 // j = 2;

}

(3)异常简单的线程本地存储(线程退出时,不改变该变量的值)

thread_local int j = 0;

void foo()

  {

  m.lock();

  j++; // j is now 1, no matter the thread. j is local to this thread.

  m.unlock();

  }

void main()

  {

  j = 0;

  std::thread t1(foo);

  std::thread t2(foo);

  t1.join();

  t2.join();

 // j still 0. The other "j"s were local to the threads

}

(4)异常简单的条件变量

std::condition_variable c;

// 我们使用mutex而不是recursive_mutex是因为该锁需要一次性获取和释放

std::mutex mu; 

void foo5()

{

   std::unique_lock lock(mu); 

   c.notify_one(); 

}

void main()

{

   std::unique_lock lock(mu); 

   std::thread t1(foo5);

   

   c.wait(lock); 

   t1.join();

}

5.异常简单的future实现异步任务

int GetMyAnswer()

   {

    return 10;

   }

int main()

  {

  std::future<int> GetAnAnswer = std::async(GetMyAnswer);  // GetMyAnswer starts background execution

  int answer = GetAnAnswer.get(); // answer = 10;

  // If GetMyAnswer has finished, this call returns immediately.

  // If not, it waits for the thread to finish.

  }


你也可以使用 std::promise。该对象可以提供一些 std::future以后需求的特性。如果在任何东西放入承诺(promise)之前你调用 std::future::get(),将会导致等待,直到承诺值(promised value)出现。如果 std::promise::set_exception()被调用, std::future::get()则会抛出异常。如果 std::promise销毁了,而你调用 std::future::get(),你将会产生 broken_promise 异常。 

例子:

std::promise<int> sex;

void foo()

  {

  // 在下面的调用之后,future::get()将会返回该值

  sex.set_value(1); // After this call, future::get() will return this value.

     

  // 调用之后,future::get()将会抛出这个异常

  sex.set_exception(std::make_exception_ptr(std::runtime_error("TEST")));

  }

int main()

  {

  future<int> makesex = sex.get_future();

  std::thread t(foo);

   

  // do stuff

  try

    {

    makesex.get();

    hurray();

    }

  catch(...)

    {

    // She dumped us :(

    }

  }

6.bind绑定成员函数和非成员函数

#include < functional> 


(1)绑定非成员函数

int Func(int x, int y);  

auto bf1 = std::bind(Func, 10, std::placeholders::_1);  

bf1(20); ///< same as Func(10, 20)  


(2)绑定成员函数(bind成员函数的第一个入参必须是对象本身)

class A  

{  

public:  

    int Func(int x, int y);  

};  

   

A a;  

auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);  

bf2(10, 20); ///< same as a.Func(10, 20)  

   

std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);  

bf3(10); ///< same as a.Func(10, 100)  





C程序存储分成4段:

a.代码段:存储每一个函数包括main函数的二进制代码

b.数据段:分3种

b.a只读数据段:存储只读全局变量和只读局部变量,以及所有常量

b.b.已初始化读写数据段:存储已经初始化的全局变量和局部静态变量

b.c.未初始化读写数据段:运行时才会产生该段,存储未经过初始化的数据,该段不是目标文件的一部分

c.栈:存储程序运行时的函数内部使用的变量、函数的参数以及返回值

d.堆:存储程序运行时malloc分配的空间




本文转自 a_liujin 51CTO博客,原文链接:http://blog.51cto.com/a1liujin/1652038,如需转载请自行联系原作者

相关文章
|
2月前
|
算法 C++ 容器
C++标准库(速查)总结
C++标准库(速查)总结
69 6
|
2月前
|
存储 算法 C++
C++ STL 初探:打开标准模板库的大门
C++ STL 初探:打开标准模板库的大门
102 10
|
2月前
|
算法 C++
2022年第十三届蓝桥杯大赛C/C++语言B组省赛题解
2022年第十三届蓝桥杯大赛C/C++语言B组省赛题解
42 5
|
2月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
71 5
|
2月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
58 1
|
3月前
|
编译器 API C语言
超级好用的C++实用库之跨平台实用方法
超级好用的C++实用库之跨平台实用方法
41 6
|
3月前
|
安全 C++
超级好用的C++实用库之环形内存池
超级好用的C++实用库之环形内存池
51 5
|
3月前
|
缓存 网络协议 Linux
超级好用的C++实用库之套接字
超级好用的C++实用库之套接字
34 1
|
3月前
|
存储 算法 安全
超级好用的C++实用库之sha256算法
超级好用的C++实用库之sha256算法
105 1
|
2月前
|
存储 编译器 C语言
深入计算机语言之C++:类与对象(上)
深入计算机语言之C++:类与对象(上)
下一篇
无影云桌面