string对象的定义和初始化 string s1; 默认构造函数,s1为空串 string s2(s1); 将s2初始化为s1的一个副本 string s3(“value”); 将s3初始化为一个字符串字面值的副本 string s4(n,‘c’); 将s4初始化为字符‘c’的n个副本 strings和c-strings 有三个函数可以将字符串内容转换为字符数组或c-string: data() 以字符数组的形式返回字符串内容。由于并未追加’\0’字符,所以返回类型并非有效的c-string c_str() 以c-string形式返回字符串内容,也就是在尾端添加’\0’字符 copy() 将字符串内容复制到“调用者提供的字符数组“中。不添加’\0’字符 请注意:’\0’在string之中并不具有特殊意义,’\0’和其它字符的地位完全相同,但是在c-string中却用来标识字符串的结束。还要注意的是,data() 和 c_str()返回的字符数组由该字符串拥有,调用者千万不可修改它或者释放其内存。下面是相关的一些例子: std::string s(“12345”); atoi(s.c_str()); f(s.data(), s.length()); char buffer[100]; s.copy( buffer, 100 ); 一般而言,在整个程序中应该坚持使用string,直到必须将其转换为char*类型时才把它们转换为c-string。 将string置空 有许多动作都可以将字符串设置为空字符串,例如: std::string s=”this is a example”; s = “”; s.clear(); s.erase(); substr()获取子字符串 std::string s(“interchangeability”); s.substr() //返回整个s字符串 s.substr(11) //返回“ability“ s.substr(5,6) //返回“change“ s.substr(s.find(‘c’)) //返回“changeability“ string的查找函数 string提供了很多用于搜索和查找字符以及子字符串的函数。如果配上迭代器,STL的所有搜索算法都可以派上用场。所有搜索函数的名字中都有find这个词: find() 搜索第一个与value相等的字符 rfind() 搜索最后一个与value相等的字符 find_first_of() 搜索第一个“与value中的某值相等“的字符 find_last_of() 搜索最后一个“与value中的某值相等“的字符 find_first_not_of() 搜索第一个“与value中的任何值都不相等“的字符 find_last_not_of() 搜索最后一个“与value中的任何值都不相等“的字符 所有搜索函数都返回符合搜索条件的字符区间内的第一个字符的索引。如果搜索不成功,则返回string::npos。这些搜索函数都采用下面的参数方案: 第一个参数总是被搜索的对象 第二个参数(可有可无)指出string内的搜索起点 第三个参数(可有可无)指出搜索的字符个数 例子: std::string s(“Hi Bill, I’m ill, so please pay the bill”); s.find(“il”) //返回4 s.find(“il”, 10) //返回13 s.rfind(“il”) //返回37 s.find_first_of(“il”) //返回1(第一个为’i’或者’l’的字符) s.find_last_of(“il”) //返回39 s.find_first_not_of(“il”) //返回0 s.find_last_not_of(“il”) //返回36 s.find(“hi”) //返回string::npos vector是一个类模板,我们可以定义保存string对象的vector,或者保存int值的vector,甚至可以定义保存自定义的类类型对象的vector。 vector对象的定义和初始化 vector定义了好几种构造函数,用来定义和初始化vector对象: vector v1 vector保存类型为T的对象 vector v2(v1) v2是v1的一个副本 vector v3(n, elem) v3包含n个值为elem的元素 vector v4(n) v4含有值初始化的元素的n个副本 vector v5(first,last) v5以[first,last)迭代器所指区间的元素作为初值 vector的赋值操作 v1 = v2 将v2的全部元素赋值给v1 v1.assign(n,elem) 复制n个elem,赋值给v1 v1.assign(first,last) 将迭代器所指区间[first,last)的元素赋值给v1 v1.swap(v2) 将v1和v2交换 swap(v1,v2) 同上,此为全局函数 vector中元素的存取 v.at(idx) 返回idx所标示的元素,如果idx越界,抛出out_of_range异常 v[idx] 返回idx所标示的元素,不进行范围检查 v.front() 返回第一个元素,不检查第一个元素是否存在 v.back() 返回最后一个元素,不检查最后一个元素是否存在 调用operator[]时,必须确保索引有效;调用front()或back()时必须确保容器不空,例如: std::vector ivec; //empty if(ivec.size() > 5) ivec[5] = 100; //ok if( !ivec.empty() ) cout< ivec.at(5) = 100; //抛出out_of_range异常 vector对象的操作 v.empty() 如果v为空,返回true,否则返回false v.size() 返回v中元素的个数 v.max_size() 返回可容纳的元素最大数量 v.capacity() 返回重新分配空间前所能容纳的元素最大数量 v.push_back(t) 在v的末尾增加一个值为t的元素 v[n] 返回v中位置为n的元素 v1 = v2 把v1的元素替换为v2中元素的副本 size指容器当前拥有的元素个数;而capacity则指容器在必须分配新存储空间之前可以存储的元素的总数。 避免重新分配内存的方法 我们可以使用reserve()保留适当的容量,避免一再重新配置内存。如此一来,只要保留的容量尚有富裕,就不必担心references失效。 std::vector ivec; ivec.reserve(80); 另外一种避免重新分配内存的方法是,初始化期间就向构造函数传递附加参数,构造出足够的空间。如果给出的参数是个数值,该数值将成为vector的起始大小。 std::vector svec(80); 当然,要获得这种能力,放在vector中的元素必须提供一个缺省的构造函数。请注意,如果元素比较复杂,就算提供了缺省构造函数,初始化操作也是很耗时间的。如果你这么做只是为了保留足够的内存,还不如直接使用reserve()。 安插和移除元素,都会使作用点之后的各元素的引用、指针和迭代器失效。如果安插操作甚至引起内存重新分配,那么该容器上所有的引用、指针和迭代器都会失效。 如果形参是普通的引用,则不能将const对象转递给这个形参;如果形参是const的引用,const对象或者非const对象都可以传递给这个形参。 如果形参是普通的指针,则不能将const对象的地址传递给这个指针; 不能基于指针本身是否为const来实现函数的重载。
Widget& Widget::get() { //… return *this; }
该成员函数返回类型是Widget&,指明该成员函数返回调用自己的那个对象。
this指针的类型
在普通的非const成员函数中,this的类型是一个指向类类型的const指针;在const成员函数中,this的类型是一个指向const类类型对象的const指针。不能从const成员函数返回指向类对象的普通引用。const成员函数只能返回*this作为一个const引用。
int *pf( const string &, const string & ); // 指针函数
int (*pf)( const string &, const string & );//这个语句声明了pf 是一个 指向函数的指针
int gcd( int , int );
可以如下定义pfi,它能够指向这两个函数:
int (*pfi)( int, int );
int (*testCases[10])();
将testCases 声明为一个拥有10 个元素的数组。每个元素都是一个指向函数的函数指针,该函数没有参数,返回类型为int。
像数组testCases 这样的声明非常难读,因为很难分析出函数类型与声明的哪部分相关。在这种情况下,使用typedef 名字可以使声明更为易读。例如:
// typedefs 使声明更易读
typedef int (*PFV)(); // 定义函数类型指针的typedef
PFV testCases[10];
testCases 的这个声明与前面的等价。
由testCases 的一个元素引用的函数调用如下:
const int size = 10; PFV testCases[size]; int testResults[size]; void runtests() { for ( int i = 0; i < size; ++i ) testResults[ i ] = testCases i ; //调用一个数组元素 }
函数指针的数组可以用一个初始化列表来初始化,该表中每个初始值都代表了一个与数组元素类型相同的函数。例如:
int lexicoCompare( const string &, const string & ); int sizeCompare( const string &, const string & ); typedef int ( *PFI2S )( const string &, const string & ); PFI2S compareFuncs[2] = { lexicoCompare, sizeCompare };
我们也可以声明指向compareFuncs 的指针。这种指针的类型是“指向函数指针数组的指针”。声明如下:
PFI2S (*pfCompare)[2] = &compareFuncs;
声明可以分解为:
(pfCompare)
解引用操作符 把pfCompare 声明为指针,后面的[2]表示pfCompare 是指向两个元素数组的指针:
(*pfCompare)[2]
typedef PFI2S 表示数组元素的类型,它是指向函数的指针,该函数返回int,有两个const string&型的参数。数组元素的类型与表达式&lexicoCompare 的类型相同,也与compareFuncs的第一个元素的类型相同。此外,它还可以通过下列语句之一获得:
compareFuncs[0];
(*pfCompare)[0];
要通过pfCompare 调用lexicoCompare,程序员可用下列语句之一:
// 两个等价的调用
pfCompare[0]( string1, string2 ); // 编写
((*pfCompare)[0])( string1, string2 ); // 显式
参数和返回类型
现在我们回头看一下本节开始提出的问题,在那里给出的任务要求我们写一个排序函数,怎样用函数指针写这个函数呢?因为函数参数可以是函数指针,所以我们把表示所用比较操作的函数指针作为参数传递给排序函数:
int sort( string*, string*,
int ()( const string &, const string & ) );
我们再次用typedef 名字使sort()的声明更易读:
typedef int ( PFI2S )( const string &, const string & );
int sort( string, string, PFI2S );
因为在多数情况下使用的函数是lexicoCompare(),所以我们让它成为缺省的函数指针参数:
int lexicoCompare( const string &, const string & );
int sort( string*, string*, PFI2S = lexicoCompare );
sort()函数的定义可能像这样:
void sort( string *s1, string *s2, PFI2S compare = lexicoCompare ) { // 递归的停止条件 if ( s1 < s2 ) { string elem = *s1; string *low = s1; string *high = s2 + 1; for (;? { while ( compare( *++low, elem ) < 0 && low < s2) ; while ( compare( elem, *–high ) < 0 && high > s1) ; if ( low < high ) low->swap(*high); else break; } // end, for(;? s1->swap(*high); sort( s1, high - 1, compare ); sort( high + 1, s2, compare ); } // end, if ( s1 < s2 ) }
sort()是C.A.R.Hoare 的快速排序算法的一个实现。让我们详细查看该函数的定义。该函数对s1 和s2 之间的数组元素进行排序。数组元素的比较通过调用compare 指向的函数来完成。
注意,除了用作参数类型之外,函数指针也可以被用作函数返回值的类型。例如:
int (ff( int ))( int, int );
该声明将ff()声明为一个函数,它有一个int 型的参数,返回一个指向函数的指针,类型为:
int () ( int, int );
同样,使用typedef 名字可以使声明更容易读懂。例如,下面的typedef PF 使得我们能更容易地分解出ff()的返回类型是函数指针:
typedef int (PF)( int, int );
PF ff( int );
函数不能声明返回一个函数类型。例如,函数ff()不能如下声明:
typedef int func( int*, int );
func ff( int ); // 错误: ff()的返同类型为函数类型
一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。
我们如何有效地阻止编译器自动生成复制构造函数和赋值操作符呢?
可以将复制构造函数和赋值操作符声明为private,只是声明,没有对应的实现代码。因为声明了复制构造函数和赋值操作符,这就阻止了编译器自动生成对应的复制构造函数和赋值操作符;而令这些函数为private,可以阻止类的用户对复制构造函数和赋值操作符的调用。因为只有声明而没有对应的定义,所以类内部的成员函数或友元函数调用复制构造函数或赋值操作符时,会导致连接错误。
在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的
循环放在最外层,以减少CPU 跨切循环层的次数
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建
示例 7-3-1 中,字符数组a 的容量是6 个字符,其内容为hello\0。a 的内容可以改变,
如a[0]= ‘X’。指针p 指向常量字符串“world”(位于静态存储区,内容为world\0),常
量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句p[0]= ‘X’有什么
不妥,但是该语句企图修改常量字符串的内容而导致运行错误。
char a[] = “hello”; a[0] = ‘X’; cout << a << endl; char *p = “world”; // 注意p 指向常量字符串 p[0] = ‘X’; // 编译器不能发现该错误 cout << p << endl; void GetMemory(char *p, int num) { p = (char *)malloc(sizeof(char) * num); }
在本例中,p 的副本是 _p,编译器使 _p = p。_p 申请了新的内存,只是把_p 所指的内存地址改变了,但是p 丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory 就会泄露一块内存,因为没有用free 释放内存。
Obj *objects = new Obj100;// 创建100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
#include <iostream.h> class Base { public: virtual void f(float x){ cout << "Base::f(float) " << x << endl; } void g(float x){ cout << "Base::g(float) " << x << endl; } void h(float x){ cout << "Base::h(float) " << x << endl; } }; class Derived : public Base { public: virtual void f(float x){ cout << "Derived::f(float) " << x << endl; } void g(int x){ cout << "Derived::g(int) " << x << endl; } void h(float x){ cout << "Derived::h(float) " << x << endl; } }; void main(void) { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 (surprise!) // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 (surprise!) pd->h(3.14f); // Derived::h(float) 3.14 }
若想弗雷函数完全当接口不适用,就应该在函数前加virtual,自类的函数中也加virtual,否则父类引用,子类对象时会调用父类的函数!!
隐藏规则引起了不少麻烦。示例8-2-3 程序中,语句pd->f(10)的本意是想调用函
数Base::f(int),但是Base::f(int)不幸被Derived::f(char *)隐藏了。由于数字10
不能被隐式地转化为字符串,所以在编译时出错。
class Base { public: void f(int x); }; class Derived : public Base { public: void f(char *str); }; void Test(void) { Derived *pd = new Derived; pd->f(10); // error }
成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序。
类String 拷贝构造函数与普通构造函数(参见9.4 节)的区别是:在函数入口处无需与NULL 进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。
析构函数绝对不要抛出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或者干脆结束程序。
在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时要求前期bind,然而虚函数是动态绑定的。
基类实现纯虚函数是因为,一部分派生类必须要重新定义这个虚函数,而一部分派生类可以直接通过Base::pure_vitual_func()来使用基类默认实现的方法。
友元的使用提高了程序的运行效率(即减少了类型检查和安全性检查等都需要的时间开销),但它破坏了类的封装性和隐藏性,使得友元可以访问类的私有成员。
Array 对象属性
concat() 连接两个或更多的数组,并返回结果。
copyWithin() 从数组的指定位置拷贝元素到数组的另一个指定位置中。
entries() 返回数组的可迭代对象。
every() 检测数值元素的每个元素是否都符合条件。
fill() 使用一个固定值来填充数组。
filter() 检测数值元素,并返回符合条件所有元素的数组。
find() 返回符合传入测试(函数)条件的数组元素。
findIndex() 返回符合传入测试(函数)条件的数组元素索引。
forEach() 数组每个元素都执行一次回调函数。
from() 通过给定的对象中创建一个数组。
includes() 判断一个数组是否包含一个指定的值。
indexOf() 搜索数组中的元素,并返回它所在的位置。
isArray() 判断对象是否为数组。
join() 把数组的所有元素放入一个字符串。
keys() 返回数组的可迭代对象,包含原始数组的键(key)。
lastIndexOf() 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
map() 通过指定函数处理数组的每个元素,并返回处理后的数组。
pop() 删除数组的最后一个元素并返回删除的元素。
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
reduce() 将数组元素计算为一个值(从左到右)。
reduceRight() 将数组元素计算为一个值(从右到左)。
reverse() 反转数组的元素顺序。
shift() 删除并返回数组的第一个元素。
slice() 选取数组的的一部分,并返回一个新数组。
some() 检测数组元素中是否有元素符合指定条件。
sort() 对数组的元素进行排序。
splice() 从数组中添加或删除元素。
toString() 把数组转换为字符串,并返回结果。
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
valueOf() 返回数组对象的原始值。