540 likes | 766 Views
The C++ Programming Language. Week 3. Could we always trust others? – A better version. Users may not be trustworthy, assumption is dangerous Be doubtful. bool fibon_elem(int iPos, int &iElem) { int n2 = 1, n1 = 1; if (iPos <= 0 || iPos >= 1024) {
E N D
The C++ Programming Language Week 3
Could we always trust others? – A better version • Users may not be trustworthy, assumption is dangerous • Be doubtful bool fibon_elem(int iPos, int &iElem) { int n2 = 1, n1 = 1; if (iPos <= 0 || iPos >= 1024) { iElem = 0; return false; } iElem = 1; for (int iX = 3; iX <= iPos; iX++) { iElem = n2 + n1; n2 = n1; n1 = iElem; } return true; } 面向对象程序设计
How about this? • Calculating elements in every calling is not so efficient • We need only calculate them once, then pick the corresponding one vector<int>* fibon_seq(int iLength) { if (iLength <= 0 || iLength >= 1024) { cerr << “Length ” << iLength << “not supported, reset to 8” << endl; iLength = 8; } vector<int> Elems( iLength ); for (int iX = 0; iX < iLength; iX++) { if (iX == 0 || iX == 1) { Elems[iX] = 1; } else { Elems[iX] = Elems[iX-1] + Elems[iX-2]; } } return &Elems; } Problem: 不要用return 返回指向“栈内存”的指针! 面向对象程序设计
对象的局部性 Problem: • Elems是函数内的局部对象 • 局部对象的内存是分配在程序栈上的 • 局部对象随着函数的执行被自动创建,当函数运行结束会被自动销毁 • 局部对象的作用域仅限于函数内,在函数外不可见 • 因此,主调函数在其返回后,得到了一个已经“死亡”的对象的指针,通过指针取用该对象就是非常危险的 • 取用一个已经消亡的对象的内容, C/C++标准对于该行为未定义结果 • 结论:在函数内返回局部对象的指针或引用,都是非常危险的动作! 面向对象程序设计
对象的全局性 解决前述函数问题的办法之一——全局对象 • 将Elems的定义放在函数之外,即为全局对象 • 全局对象的作用范围为自定义点开始,直到该文件结束 • 全局对象其所占内存不在程序栈,而在全局和静态数据区;它们会在执行main函数之前就已经创建,而直到main函数执行结束后才被销毁 • 返回全局对象的指针或者引用是安全的! vector<int> Elems( iLength ); … vector<int>* fibon_seq(int iLength) { … return &Elems; } 面向对象程序设计
对象的静态性 • 前述函数的问题 • 依赖全局对象,会破坏函数的独立性,增加函数与其它函数联系的复杂性 • 增加模块间的耦合度,不利于维护 • 解决的办法之二——局部静态对象 • 将Elems的定义为局部的静态对象 • 静态对象的生命周期与全局对象相同,存放区域也相同 • 但其局部性保证了该对象对其他无关的函数是不可见的;除了得到其指针的函数外,其他函数无法使用它 • 返回局部静态对象的指针或者引用是安全的! vector<int>* fibon_seq(int iLength) { static vector<int> Elems; … return &Elems; } 面向对象程序设计
A corrected version const vector<int>* fibon_seq(int iLength) { static vector<int> Elems; if (iLength <= 0 || iLength > 1024) { cerr << “Length ” << iLength << “not supported” << endl; return 0; } //existing elements won’t be calculated for (int iX = Elems.size(); iX < iLength; iX++) { if (iX == 0 || iX == 1) { Elems.push_back(1); } else { Elems.push_back(Elems[iX-1] + Elems[iX-2]); } } return &Elems; } 面向对象程序设计
对象的动态性 解决办法之三——创建动态生命周期的对象 • 前述的局部(静态)对象、全局对象都为自动对象 • 即它们的创建和销毁都由系统完成,无需程序员操心 • 也可利用动态对象完成相同的需求 • 所谓的动态对象,即由程序员用new/delete人工管理其生命周期的对象 int main() { vector<int>* pv = fibon_seq(10); … delete pv; } vector<int>* fibon_seq(int iLength) { vector<int>* p = new vector<int>; … return p; } 面向对象程序设计
Extent (cont.) • “Dynamic Extent” • 在函数中用 new and delete运算符 • 内存是从堆上分配的 • 由编程人员自己管理 • Example int* funcA() void funcB(int* p) { { int* p = new int[1024]; cout << p[ 1 ] << endl; return p; delete [] p; } } 面向对象程序设计
C++的用途 • 主流的3种操作系统Windows,Linux,Unix,内核都是用C语言和汇编写的,上层高级特性是用C++写的 • 创造新的语言,例如C#, Java, Python等 • 游戏,《魔兽世界》等几乎所有的网络游戏 • 科学研究 • 通讯(中国移动 C#) • 大型软件,例如3Dmax, CAD, Photoshop, QQ, Office 等 • 病毒 • 百度, google, 雅虎等搜索引擎 • 写解释器,例如XML, HTML等解释器 • 有各式各样开发工具 面向对象程序设计
C++的用途 (cont.) • C++之父Bjarne Stroustrup列举的C++应用: • Amazon.com:Software for large-scale e-commerce. • Apple: OS X is written in a mix of language, but a few important parts are C++. • AT&T: The largest US telecommunications provider. • Autodesk: A large number of major number of application in the CAD domain • Ericsson: server platform. • Google: web search engine, etc. • HP, IBM, Intel, Nokia • JPL (Jet Propulsion Lab, NASA): Mars rover autonomous driving system (incl. scene analysis and route planning). C++ on Mars! Also lots of supporting software "on the ground" (i.e. Earth). 面向对象程序设计
C++的用途 (cont.) • C++之父Bjarne Stroustrup列举的C++应用: • Microsoft:o Windows XP o Windows NT (NT4 and 2000) o Windows 9x (95, 98, Me) o Microsoft Office (Word, Excel, Access, PowerPoint, Outlook) o Internet Explorer (including Outlook Express) o Visual Studio o SQL • Mozilla: Firefox browser and Thunderbird mail client (open source) • MySQL: MySQL Server (about 250,000 lines of C++) and MySQL Cluster. Arguably the world's most popular open source database • telephone systems I think it would be almost easier to list the systems which aren't written in C++ 面向对象程序设计
内存(memory) 伟大的Bill Gates 曾经失言: 640K ought to be enough for everybody — Bill Gates 1981 • 内存分配方式 • 静态存储区(程序编译的时候已经分配好,这块内存在程序的整个运行期间都存在。如全局变量, static变量) • 栈stack(函数内局部变量在栈上创建,函数执行结束时自动释放。效率高,但分配的内存容量有限) • 堆heap(动态分配,用malloc/new申请任意多少内存,程序员自己负责在何时free/delete 释放内存。生存期由程序员决定,使用灵活,但是问题也多) • 文字常量区 • 程序代码区 面向对象程序设计
动态内存分配 • 推荐 new & delete • 分配和释放内存 • 不要混用 new & delete 和 malloc & free • new and array new, delete and array delete string* stringPtr1 = new string; string* stringPtr2 = new string[100]; … delete stringPtr1; delete [] stringPtr2; 面向对象程序设计
1000 静态区 堆(heap) 2000 2004 堆(heap) FFFF 栈(stack) 全局(静态)区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放; 栈:由编译器自动分配释放,存放函数的参数值,局部变量的值等; 堆:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收; 面向对象程序设计
//main.cppint a = 0; 全局初始化区char *p1; 全局未初始化区main(){ int b; //栈char s[] = “abc”; //栈char *p2; 栈char *p3 = “123456”; //123456\0在常量区,p3在栈上static int c =0; //全局(静态)初始化区p1 = new char[10]; p2 = new char[20]; //堆区strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方} 面向对象程序设计
存取效率比较 #include void main() {char a = 1;char c[] = "1234567890";char *p ="1234567890";a = c[1];a = p[1];return; } • c是运行时赋值(栈) p是编译时就确定的(堆) • 在存取中,在栈上的数组比指针所指向的字符串快 面向对象程序设计
Class MyClass { public: inline int GetState(); private: int m_iState; } int inline MyClass::GetState() { return m_iState; } Class MyClass { public: int GetState(); private: int m_iState; } int MyClass::GetState() { return m_iState; } 内联函数 Class MyClass { private: int m_iState; } 面向对象程序设计
本讲内容 • 泛型开发与STL的概念 • Iterator • 泛型容器 • 泛型算法及其实现 面向对象程序设计
Motivations(1) • 两个数的加法,要考虑很多类型:int add(int a,int b) { return a+b; }float add(float a,float b) { return a+b; }double add(double a, double b) { return a+b; } …… • 函数重载 • 反复写相同函数辛苦 • 运行占用过多内存 • 模板函数 面向对象程序设计
模板函数 template<typename T> // 模板声明 T add(T a,T b) { return a+b; }// 注意形参和返回值的类型 这时C++编译器会根据add函数的参数类型来生成一个与之对应的带具体参数类型的函数并调用。 例如: #include <iostream>using namespace std;template <typename T>T add(T a,T b) //注意形参和返回类型{ return a+b;} void main(){ int num1, num2, sum; cin>>num1>>num2; //用int匹配模版参数T,若sum,num1,num2类型不一致则无法匹配。 sum=add(num1,num2); cout<<sum;} 面向对象程序设计
Motivations(2) #include <stdlib.h> #include <iostream.h> int compare(const void *arg1, const void *arg2); void main(void) { const int max_size = 10; int num[max_size]; int n; for (n = 0; cin >> num[n]; n ++); qsort(num, n, sizeof(int), compare); for (int i = 0; i < n; i ++) cout << num[i] << "\n"; } int compare(const void *arg1, const void *arg2) { return (*(int *)arg1 < *(int *)arg2) ? -1 : (*(int *)arg1 > *(int *)arg2) ? 1 : 0; } 输入:0 9 2 1 5 输出:0 1 2 5 9 void qsort(void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) ); 面向对象程序设计
Motivations(3) #include <iostream> #include <vector> #include <algorithm> using namespace std; void main(void) { vector<int> num; int element; while (cin >> element) num.push_back(element); sort(num.begin(), num.end()); for (int i = 0; i < num.size(); i ++) cout << num[i] << "\n"; } 面向对象程序设计
完美主义者 • #include <iostream> • #include <vector> • #include <algorithm> • #include <iterator> • using namespace std; • void main(void) • { • typedef vector<int> int_vector; • typedef istream_iterator<int> istream_itr; • typedef ostream_iterator<int> ostream_itr; • typedef back_insert_iterator< int_vector > back_ins_itr; • int_vector num; • copy(istream_itr(cin), istream_itr(), back_ins_itr(num)); • sort(num.begin(), num.end()); • copy(num.begin(), num.end(), ostream_itr(cout, "\n")); • } 面向对象程序设计
泛型开发思想 • 泛型开发(Generic Programming)的核心思想——三个独立 • 数据结构与数据类型相互独立 • 数据结构与算法相互独立 • 算法与数据类型相互独立 • 标准模板库STL,就是一个实现了泛型开发思想的很好的产品,是C++标准的一部分 • 包括三个组成部分 • 容器: vector, list, map, set,… (作为基本的数据结构) • 泛型算法: find, sort, merge,… (常用操作) • 迭代器:(确定输入范围) 面向对象程序设计
STL和C++标准函数库 面向对象程序设计
泛型开发思想 • 容器的概念支持了数据结构与数据类型的无关性 list<int>, list<double> • Iterators的概念支持了数据结构与算法的无关性 sort(vec.begin(), vec.end()); • 函数模板的概念支持了算法与数据类型的无关性 template <typename ElemType> binary_search(const vector<ElemType>&, ElemType&); 面向对象程序设计
从指针到Iterator 问题1 给定一个整型vector和一个整型数值,请实现一个find函数,若vector中有该数值,则返回指向vector中该元素的指针;若无则返回空指针 int* find(const vector<int>& vec, int iValue) { for (int iX = 0; iX < vec.size(); iX++) { if (vec[iX] == iValue) { return &vec[iX]; } } return 0; } 面向对象程序设计
从指针到Iterator 问题2 请改造前面实现的find函数,使它能支持任意数据类型,只要在该数据类型上==算符有定义 template <typename ElemType> ElemType* find(const vector<ElemType> &vec, const ElemType &Value) { for (int iX = 0; iX < vec.size(); iX++) { if (vec[iX] == Value) { return &vec[iX]; } } return 0; } 面向对象程序设计
从指针到Iterator • 问题3 • 请改造前面实现的find函数,使它能支持在数组中的数据元素中操作 • 当然,写另一重载函数能解决,但有没有只用一个函数就能解决所有问题的办法? • 此问题的难点在于,有两种数据结构 • 解决的思路在于:能否让函数在处理数组的时候,没有意识到当前操作的是个数组;在处理vector的时候,也意识不到正在对vector进行操作? 面向对象程序设计
从指针到Iterator • 对于数组而言,常用的访问方式是利用首地址和[]运算符 template <typename ElemType> ElemType* find(const ElemType* array , int iSize, const ElemType &Value) { if ((! array) || size < 1) return 0; for (int iX = 0; iX < iSize; iX++) { if (array[iX] == Value) { return &array[iX]; } } return 0; } 面向对象程序设计
从指针到Iterator • 另一种思路 • 增加一个指向最后一个元素下一地址的监视(哨兵)指针,来替换数组大小的参数 • 使用指针的*运算符,来替换数组典型的[]运算符 template <typename ElemType> ElemType* find(const ElemType* first , const ElemType* last , const ElemType &Value) { if ((! first) || (! last)) return 0; for (; first != last; first++) { if (*first == Value) return first; } return 0; } 面向对象程序设计
从指针到Iterator • 对于vector,前述的方法同样能够适用 • 但由此一个小问题:由于vector的可空特性,前面的函数在如下情况下会出问题 vector<string> svec; find(&svec[0], &svec[svec.size()], search_value); • 因此我们需要增加两个辅助函数以策安全,这样就能以如下方式调用 find(begin(svec), end(svec), search_value); template <typename ElemType> inline ElemType* begin(const vector<ElemType> &vec) { return vec.empty() ? 0 :&vec[0]; } template <typename ElemType>; inline ElemType* end(const vector<ElemType> &vec) { return vec.empty() ? 0 : ++(&vec[vec.size() – 1]); } 面向对象程序设计
从指针到Iterator • 问题4 • 请改造前面实现的find函数,使它也能支持STL中的list容器 • 前面实现的函数无法继续适用,因为寻址方式发生了变化 • vector和array使用连续的内存单元存储(++, --) • list使用链式的内存单元存储(->next, ->prev) • 要解决现在的问题,只能是增加一个用来封装底层物理指针行为的抽象层,避免用户直接去操纵底层指针 • Iterator就是这样的封装后的抽象指针,用来操作容器 面向对象程序设计
Iterator • Iterator(泛型指针)本质上是对象 • 由于它是重载了内部算符(如++, *, ==, !=等)的类的对象,使得我们能以同指针一样的方式去使用它 • STL中每一种容器都提供了作为其嵌套类型的iterator,并提供了一些成员函数来操纵这些泛型指针 面向对象程序设计
Iterator • Iterator的定义与使用 • 定义 vector<string> svec; vector<string>::iterator it; //普通iterator vector<string>::const_iterator conit; //只读iterator vector<string>::reverse_iterator rit; //反向iterator • 使用 for (it = svec.begin(); it != svec.end(); it++) cout << *it << ‘ ’; cout << it->size() << endl; 面向对象程序设计
Iterator • 实现问题4的需求 template <typename IteratorType, typename ElemType> IteratorType find(IteratorType first , IteratorType last , const ElemType& Value) { for (; first != last; first++) { if (*first == Value) return first; } return last; } 面向对象程序设计
Iterator 以及,该函数的使用方式 const int asize = 8; int ia[asize] = { 1, 1, 2, 3, 5, 8, 13, 21}; vector<int> ivec(ia, ia + asize); //using the array to initialize the vector list<int> ilist(ia, ia + asize); //using the array to initialize the list int *pa = find(ia, ia + asize, 1024); if (pa != ia + asize) //found… vector<int>::iterator it_v = find(ivec.begin(), ivec.end(), 1024); if (it_v != ivec.end()) //found… list<int>::iterator it_l = find(ilist.begin(), ilist.end(), 1024); if (it_l != ilist.end()) //found… 面向对象程序设计
泛型容器 • STL中有两种类型的容器(Container) • 序列式容器: vector, list, deque • 关联式容器: map, set, multimap, multiset • 容器都有这样一些共通的操作 • ==, != • = • empty(), size() • begin(), end() • insert(), erase(), clear() 面向对象程序设计
序列式容器 • vector – 连续内存单元存储 • 随机访问和尾端元素操作效率很高 • 内部元素插入和删除操作较慢(需涉及移动) • 常用作栈 • list – 链式内存单元存储 • 任意位置元素的插入和删除操作效率很高 • 随机访问较慢(需涉及遍历) • 常用作链表 • deque – 连续内存单元存储 • 随机访问以及头、尾端元素操作效率很高 • 内部元素插入和删除操作较慢 • 常用作队列 面向对象程序设计
序列式容器 序列式容器重要的成员函数 • 构造函数与初始化 • push_back(), pop_back(), back() (三种容器都有) • push_front(), pop_front(), front() (仅list与deque) • insert() and erase() list<string> slist; //产生空容器 list<string> slist(1024); //产生具有指定数目元素的容器,每元素为该类型缺省值 list<string> slist(1024, “Hi”); //产生具有指定数目元素的容器,每元素为指定的初值 int a[6] = {1, 2, 3, 4, 5, 6}; list<int> ilist(a, a+6); //用两个 iterators标明用来初始化的元素的区间 list<string> slist1(1024, “Hi”); list<string> slist2(slist1); //从一个已有容器复制产生一个新容器 面向对象程序设计
序列式容器 iterator insert(iterator position); //在 position指定的位置之前插入一个缺省值 iterator insert(iterator position, ElemType Value); //插入一个特定值 void insert(iterator position, int Count, ElemType Value);//插入若干个特定值 void insert(iterator1 position, iterator2 first, iterator2 last); //插入一个区段的元素 iterator erase(iterator position); iterator erase(iterator first, iterator last); 面向对象程序设计
K V K V K V K V K K K K 关联式容器 • map, multimap • 每一个条目都是一个形如<key, value>的二元组, key作为查找的索引 • 内部是以查找树实现,因此效率较高 • 对于map来说,其中每项的key必须唯一;对multimap而言,key可以出现重复的值 • set, multiset • 每一个条目仅仅为一个key • 对于set,key必须唯一;对multiset而言,可以有重复值 面向对象程序设计
关联式容器 map应用举例 #include<map> map<string, int> word; //定义map map<string, int>::iterator it; //向word 插入元素 ("a",9) word.insert (map<string,int>::value_type("a",9)); //查找 键是"a"的元素,返回指向元素的指针。it=word.find ("a"); //如果元素不存在,指针指向word.end(). if(it!=word.end ())cout<<it->second<<endl; //输出元素的值 //查找 键是"a"的元素,int result=word.count ("a"); //如果键存在返回1,否则返回0if(result) cout<<word["a"]<<endl; //输出元素的值 面向对象程序设计
泛型算法 STL中提供了超过70种与容器和数据类型无关的泛型算法(Generic Algorithm) • Search: find(), count(), adjacent_find(), find_if(), count_if(), binary_search(), find_first_of() • Sorting & Ordering: merge(), partial_sort(), partition(), random_shuffle(), reverse(), rotate(), sort() • Copy, Deletion & Substitution: copy(), remove(), remove_if(), replace(), replace_if(), swap(), unique() • Relational: equal(), includes(), mismatch() • Generation & Mutation: fill(), for_each(), generate(), transform() • Numeric: accumulate(), adjacent_difference(), partial_sum(), inner_product() • Set: set_union(), set_difference() 面向对象程序设计
泛型算法 泛型算法应用举例 #include<algorithm> #include<vector> bool Have_Elem(const vector<int> &vec, int search_value) { int max_value = max_element(vec.begin(), vec.end()); if (max_value < search_value) { cout << “No enough elements” << endl; return false; } vector<int> temp(vec.size()); copy(vec.begin(), vec.end(), temp.begin()); //为排序复制一个新的vector sort(temp.begin(), temp.end()); //对分查找要求序列有序 return binary_search(temp.begin(), temp.end(), search_value); } 面向对象程序设计
设计一个泛型算法 问题5 • 如何改造现有的filter函数,使其能让用户指定比较方式,如<, >, <=等 vector<int> filter(const vector<int> &vec, int threshold) { vector<int> nvec; for (int iX = 0; iX < vec.size(); iX++) if (vec[iX] < threshold) nvec.push_back(vec[iX]); return nvec; } 面向对象程序设计
设计一个泛型算法 第一种解法——使用函数指针 vector<int> filter_ver1(const vector<int> &vec, int threshold, bool (*pred)(int, int)) { vector<int> nvec; for (int iX = 0; iX < vec.size(); iX++) if ( pred(vec[iX], threshold) ) nvec.push_back(vec[iX]); return nvec; } bool less_than(int v1, int v2) { return v1 < v2 ? true : false; } bool greater_than(int v1, int v2) { return v1 > v2 ? true : false; } 调用: vector<int> lt_10 = filter_ver1(ivec, 10, less_than); for循环与顺序寻址方式相关 面向对象程序设计
设计一个泛型算法 函数对象(Function Object) • 函数对象是类的对象,是某些重载了函数调用算符()的特殊的类的对象 • 函数对象为inline方式实现,比我们自己实现效率要高 • 预定义的函数对象: • 算术6种: plus<type>, minus<type>, negate<type>, multiplies<type>, devides<type>, modules<type> • 关系6种: less<type>, less_equal<type>, greater<type>, greater_equal<type>, equal_to<type>, not_equal_to<type> • 逻辑3种: logical_and<type>, logical_or<type>, logical_not<type> 面向对象程序设计