1 / 92

C++ 高级编程

C++ 高级编程. Overview. Module 1 : 面向过程的 C++ Module 2 : 面向对象的 C++ Module 3 : C++ 设计模式. Module 1 : 面向过程的 C++. 从 C 到 C++ 内存管理 数组 字符集和字符串 指针和引用 函数 其它. 从 C 到 C++. 语法增强 新的运算符 变量声明更加灵活 函数重载 引用 类型安全性 面向对象 封装 继承 多态. 内存管理. 在 stack 上分配内存 简单数据类型 不使用 new 创建对象实例 函数调用完成自动销毁 线程堆栈的限制

carter-boyd
Download Presentation

C++ 高级编程

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. C++高级编程

  2. Overview • Module 1 : 面向过程的C++ • Module 2 : 面向对象的C++ • Module 3 : C++设计模式

  3. Module 1 : 面向过程的C++ • 从C到C++ • 内存管理 • 数组 • 字符集和字符串 • 指针和引用 • 函数 • 其它

  4. 从C到C++ • 语法增强 • 新的运算符 • 变量声明更加灵活 • 函数重载 • 引用 • 类型安全性 • 面向对象 • 封装 • 继承 • 多态

  5. 内存管理 • 在stack上分配内存 • 简单数据类型 • 不使用new创建对象实例 • 函数调用完成自动销毁 • 线程堆栈的限制 • 在Heap上分配内存 • malloc 还是 new ? • 检测内存泄漏 • 防止”野指针”:回收内存后指针会自动为NULL吗? • Demo: 动态扩充的字符串缓冲区

  6. 数组 (1) 声明及初始化 int a[]={1,2,3}; int b[3]={1,2,3}; int c[5]={1,2,3}; // c[3]和c[4]的值默认为0 int a1[2][3]={{1,2,3},{4,5,6}}; int a2[2][3]={{1},{}}; // 初始化,第一行为1,0,0;第二行为0,0,0 int a3[][3]={{},{1,2,3}}; int a4[2][]={{1,2,3},{4,5,6}}; // 错误。必须声明第二维的数目 int a5[][3]={1,2,3,4,5,6}; // 正确

  7. 数组 (2) 数组的内存形式 • 数组在内存中的存放 • 全局数组在静态存储区域中存放 • 局部数组,一般在堆栈上存放 • 在堆上动态分配内存 • 线程堆栈的限制:double arr[500][300] ? double *p = new double[10]; int n = 10; double *p = new double[n]; // n不必是常量 double *p = new double[400][300]; // 错误 double **p = new double[400][300]; // 错误

  8. 数组 (3) 指针表现形式 const int ARRAY_SIZE = 5; int a[ARRAY_SIZE] = {1,2,3,4,5}; int *p = a; 考察: (1) a+i, p+i, *(a+i), *a+i, p[i] 的含义 (2) p++偏移几个字节(4个)?a++呢?//a++是非法操作 • 一维数组指针表示 • 二维数组指针表示 • 行地址和列地址 int a[ ][3] = {{1,2,3},{4,5,6}}; 考察:a, a+i, *(a+i), *(a+i)+j, *(*(a+i)+j),&a[ i ][ j ]的含义//a+i和*(a+i)值相同,但是 含义不同,前者是第i行地址,后者是第i行第1个元素的地址. 因此a+i+j和*(a+i)+j也不同,前者是第i+j行地址,后者是第i行第j个元素的地址

  9. 数组 (4) 二维数组的指针表示 • 使用一级指针访问二维数组 • 使用指向一维数组的指针来访问二维数组 int a[ ][3] = {{1,2,3},{4,5,6}}; int *p=a[0]; // 将二维数组展开成一维数组的方式访问 int *p = a; // 错误! 考察: p+i, *(p+i), *p+i的含义 int (*p)[3]=a; // 也可写成:int (*p)[3](a); int *p[3]=a; // 错误! Int* p[3]; 考察:p, p+i, *(p+i), *p+i, *(p+i)+j, *(*(p+i)+j)的含义 //

  10. 数组 (5) 动态创建复杂数组 • 二维数组 • 交错数组 int m = 2;int n = 3; int (*p)[3] = new int[2][3]; delete[] p; int* p[m]; // p数组包含两个元素,每个元素都是int*类型 p[1] = new int[3]; // 通过p[ i ]来操作每个数组 delete p[1]; // 不能使用 delete p或者delete[] p

  11. 字符集和字符串 (1) 字符编码 • ANSI、UNICODE和UTF-8 • MultiByte和WideChar • wchar_t, “L”前缀, wcslen, wcscpy, wcslen • 字符集的转换 • mbstowcs_s和wcstombs_s • MultiByteToWideChar, WideCharToMultiByte wchar_t p1[] = L“abcd”;考察:wcslen(p1)和sizeof(p1)的结果 wchar_t p1[] = L"abcd"; char p2[10]; int len = wcstombs(p2, p1, sizeof(p1)); // len = 4 ’\0’字符不计入拷贝字符数目 wchar_t *p3 = (wchar_t*)malloc( 100); mbstowcs(p3, p2, 100);

  12. 字符集和字符串 (2) 兼容字符集 • Windows平台与字符集 • Visual C++编译器字符集设置选项 • _UNICODE • _MBCS • 兼容字符集 • TCHAR, _tcslen, “_T” TCHAR t[] = _T("aaa"); // 如果编译器定义了_UNICODE,sizeof(t)=8, // 如果定义了_MBCS,sizeof(t)=4

  13. 字符集和字符串 (3) 声明字符串 char *p1 = "abcd"; char *p2 = "abcd"; 考察:内存中有几个”abcd”存在, p1 == p2是否成立?//相等 • 使用指针 • 使用数组 char p1[10] = {‘a’,‘b’,‘c’,‘d’,‘\0’}; // 注意结尾的’\0’ char p2[] = {“abcd”}; // 自动添加’\0’结尾 char p3[] = {“abcd”}; char p4[] = {'a','b','c','d'}; // 仅仅声明一个字符数组而非字符串 考察(1) sizeof(p), strlen(p)的值分别是多少?//sizeof(p1)=10,strlen(p1)=4 (2) p2 == p3 ? 不等

  14. 字符集和字符串 (4) 字符串操作函数 char p1[10] = “abcd”; char* p2 = “abcd”; // p2指向常量字符串 p1[0] = ‘x’ // OK p2[0] = ‘x’ // 错误,常量字符串不可修改//捕获异常?? • 修改单个字符 • 字符串长度 • 模拟编写strcpy方法 • 为何采用const作为参数 • 为何返回char* • ‘\0’会被拷贝吗?//自己控制呗 • Demo char p1[] = "a\t\nc";考察: strlen(p1) 和sizeof(p1) 4,5

  15. const • Const常量有数据类型,编译器会进行类型安全检查. • 需要公开的常量放在头文件中,否则放在自定义的文件中 • C++严格区分定义和声明,所以类中的常量赋初值在构造函数的初始化列表而不是在函数体中.只有静态常量例外static const ,它可以赋初值 Class A { Const Int SIZE; A(int size): }; A::A(int size):SIZE(size) { }

  16. const Class A { Const Int SIZE; A(int size):}; • 建立在整个类中都恒定的常量,用枚举 枚举常量不会占用对象的存储空间,它们在编译时被全部求值。 枚举常量的 缺点是:它的隐含数据类型是整数

  17. 指针与引用 (1) 概念 double a = 10; double* p = &a; 考察: (1)p是指针变量,则sizeof(p)=?//指针都占4个字节 (2)&p含义是什么? • 指针与指针变量 • 引用的定义 • 引用必须在声明时立即初始化,不允许空引用 • 引用一旦初始化,就不能再引用其它数据 • 引用和被引用的变量实际上代表同一个内存的数据 int a = 100; int& b = a; b = 1000; 考察: (1)&a与&b的关系?//相等对应于同一个地址 (2) a=?

  18. 指针与引用 (1) 概念 • 引用的主要功能 传递函数的参数和返回值.C++中常用的方式有三种 • 值传递,指针传递和引用传递 • 引用传递的性质象指针传递,书写形式象值传递, • 理由:如果只需要借用一下别名,就没必要用指针,. • 用适当的工具做恰如其份的事情 Void f(int &x) {X++;} Void main() { int n=10; f(n);}

  19. 指针与引用 (2) 混合使用 double a = 100; double& b = a; double *p = &b; 考察:(1)&a, p和&b的关系 //相等(2)修改a, b或*p的值,另两个是否联动 //联动 • 指针指向某个引用 • 引用一个地址 double a = 100; double& b = a; double *p = &b; double* &rp = p; // p和rp是完全相同的,且都是变量a的地址 考察:sizeof(b)=? 8 sizeof(rp) = ? 4

  20. 指针与引用 (3) 作为参数传递 • 按值传递 • 按地址传递 • 按引用传递 void Increase(int& data) {data++; } void main() { int mydata = 10; Increase(mydata); }

  21. 指针与引用 (3) 作为参数传递 • 如果参数是指针,且只作输入用,则应在类型前加const.以防止该指针在函数体内被意外修改. • 如果参数是以值传递,改成用const &,可以省去临时对象的构造和析构 • 有时函数返回是为了增加灵活性,如支持链式表达,可以附加返回值. • Return语句不可返回指向”栈内存”的指针或引用,因为该内存在函数结束时被自动销毁. • Assert是仅在debug版本起作用的宏,当参数为 假的时候,终止.需加头文件assert.h

  22. 指针与引用 (4) 指针与常量 int a =100; int b = 200; const int* pci = &a; // pci不能直接修改a地址中的数据 a = 1000; // 可以通过修改a的数据从而间接修改*pci *pci = 1000; // 错误 pci = &b; // OK. Pci可以指向另一个地址,不再与a相关 考察: 如果a被定义为:const int a = 100,那么: int* pci = &a; 是否成立?//不 成立 • 指向常量的指针 • 指针常量 inta =100; int b = 200; int* const pci = &a; // pci不能再指向别的地址 *pci = 1000; // 可以通过修改*pci来修改a的数据 pci = &b; // 错误 考察:如果有 const int* const pci = &a, 则情况如何?

  23. 指针与引用 (4) 引用与常量 • 将一个引用设置为常量后,不能通过该引用修改数据;但仍可通过被引用的变量来改变: • 被引用的数据是常量,引用本身也必须是常量: int a = 100; const int& b = a; // b与a是同一内存,但不能直接修改b b = 1000; // 错误 a = 1000; // OK const int a = 100; const int& b = a; // OK int& b = a; // 错误

  24. 指针与引用 (5) Practice char s1[] = "abcdefg"; char s2[] = "1234567"; char* p1 = s1; const char* p2 = s1; char* const p3 = s1; const char* const p4 = s1; 考察:下列哪些语句有效: p1[0]=’k’; p2[0]=’k’; p3[0]=’k’; p1=s2; p2=s2; p3=s2; p4=s2;

  25. 函数 (1) Extern • C语言中的Extern和Static • C++中的extern “C” • 允许在C++中调用C编写的函数 • 允许在C中调用C++编写的函数 • Demo: 如何在C++和C之间实现互操作? cfile.h文件: extern int add(int x, int y) cfile.c文件: int add(int x, int y) {return x + y;} cpp.cpp: extern “C” { #include “cfile.h”} …… 其它格式: extern “C” int add(int x, int y); extern “C” { int add(int x, int y); int sub(int a); }

  26. 函数 (2) 函数指针 int (*fun)(int x,int y); 或者 int (*fun)(int,int); • 声明原型 • 指针赋值 • 函数调用 • 函数指针作为参数传递 设存在函数 int max(int i, int j) {… …} int (*fun)(int,int)(max); 或者fun=max; (*fun)(10,20); void process(int x, int y, int (*f)(int,int)) { … …} process(10,20,max);

  27. 函数 (3) 函数指针 设有函数定义:intmyequal1(char* s1,char* s2) {...} 类型定义:typedef int (*equal)(char*,char*); 则可以声明:  equal eq=myequal1; 并且调用: (*eq)(s1,s2);

  28. 函数 (4) 缺省参数 • 注意 • 缺省参数必须出现在所有非缺省参数的后面 • 防止函数重载时的二义性 void f(int i, int j=10, int k=20) { … … } 调用: f(1,20),相当于:f (1,20,20) 而: f(1), 相当于:f(1,10,20)

  29. 函数 (5) 参数个数不定 • 回顾 printf(“%s”, ……) • 编写自定义得可变参数个数函数 (stdarg.h) int average( int first, ... ) { int count = 0, sum = 0, i = first; va_list marker; va_start( marker, first ); while( i != -1 ) // 为方便起见,假定参数以-1结尾 { // 实际工作中应通过某种方式确定参数的个数 sum += i; // 例如printf就是通过格式化串中的%确定的 count++; i = va_arg( marker, int); } va_end( marker ); return( sum ? (sum / count) : 0 ); }

  30. 其它 • sizeof • 针对字符串的sizeof • 针对数组和指针的sizeof • 针对结构体的sizeof • 变量赋值 • a=b=c=3 ? • int *x, y ? int* x, y • const 和 define • 作用域限定运算符 • 无名共用体 • 枚举

  31. Module 2 : 面向对象的C++ • 构造和销毁 • 参数和返回值 • const修饰符 • 运算符重载 • 虚函数 • 友元 • 模板 • 其它

  32. 构造和销毁 (1) 数据声明和初始化 class A {public: A():d2(10) { /* d2 = 10 error! */ } static int d1; // static int d1 = 100; error! const int d2; // const int d2 = 10; error! static const int d3 = 200; }; int A::d1 = 100; // const int d3 = 200;

  33. 构造和销毁 (2) 初始化列表 class B class C : public B {public: {public: B(){ i = 0;} C() { j = 0;} B(int i):i(i){} C(int i, int j) : B(i), j(j){} int i; }; int j; }; • 成员变量较少的情况下尽量使用初始化列表 • 各变量在初始化列表中出现的顺序最好与变量声明的顺序一致

  34. 构造与析构 (3) 拷贝构造函数 • 函数原型 • 调用场合 • 默认拷贝构造函数 • 成员变量之间的“值”拷贝 A ( const A& other) { … … } A a1(10); // 构造函数 A a2 = a1; // 拷贝构造函数 A a3(a1); // 拷贝构造函数

  35. 构造与析构 (4) 拷贝构造函数 Class A { private: char* name; public: A(const char* data) { name = new char[strlen(data) + 1]; strcpy (name, data);} A(const A& other) { name = new char[strlen(other.name) + 1]; strcpy(name, other.name);} }; 考察:char* data = “abcd”; A a1(data); A a2 = a1; 如果未定义拷贝构造函数,会有何种后果? • 编写拷贝构造函数的必要性

  36. 构造与析构 (5) 赋值函数 A& operator =( const A& other) { … … } • 赋值函数原型 • 调用场合 • 编写赋值函数的必要性 A a1(10); // 为a1调用构造函数 A a2; // 为a2调用默认构造函数 a2 = a1; // 为a2调用赋值函数。 区别 A a2=a1; A& operator =(const A &a){ if (&a == this) return *this; //... 具体赋值操作 return *this;} 考察: (1)为何首先检查同一性? (2) (a=b)=c或者a=(b=c)是否合法 (3)若定义为void operator =(const A &a)有何局限? (4)若定义为A operator =(const A &a){...return *this;}有何局限?

  37. 构造与销毁 (5) 初始化列表与拷贝构造函数 • 子对象在构造函数列表中初始化 • 直接调用拷贝构造函数 • 子对象在构造函数中赋值 • 先调用子对象的默认构造函数 • 调用赋值函数为子对象重新赋值 class A{ B b; public: A(B b1){ b = b1; /*先调用默认构造函数,再调用赋值函数 */ } A(B b1) : b(b1) /*调用拷贝构造函数*/} }

  38. 构造和销毁 (6) 构造和析构顺序 • 调用基类构造函数 • 为各子对象调用构造函数 • 如果有多个子对象,则各个子对象构造顺序与声明顺序一致,而不依赖初始化列表中出现的顺序 • 本类构造函数 • 析构函数的顺序与构造函数相反 • 如果没有显式调用基类或子对象构造函数,那么将调用其默认构造函数;如果默认构造函数不可访问,则编译错误

  39. 构造与销毁 (7) 内存回收 • 析构函数 • 作用 • 何时调用 • 通过指针使用对象 • new-delete • NULL指针与野指针 • Demo • 构造函数、拷贝构造函数、赋值函数、析构函数综合使用实例分析

  40. 参数和返回值 (1) 传递数组 void f(char* arr) { arr[0] = ‘A’; }// 修改可以成功 void f(char* arr){ arr = new char[10]; // 修改无效 strcpy(arr, "12345"); } void f(char** arr){ *arr = new char[10]; // 修改可以成功 strcpy(*arr, "12345"); }

  41. 参数和返回值 (2) 传递对象实例 • 实参对象拷贝给形参对象 • 实参与形参不是同一个对象 class A{ public: int age; }; void f(A obj) // obj调用拷贝构造函数将实参a拷贝给自己 { obj.age = 10; // 实际修改的是obj对象的值,而非实参a对象 } void main() { A a; a.age = 20; f(obj); // a.age仍然是20 }

  42. 参数和返回值 (3) 传递对象引用或指针 void f(A& obj) // obj是对实参a的引用,二者同一个地址 { obj.age = 10; // 修改obj就是修改a } • 省去了参数对象之间的拷贝,提高效率 void f(A* pobj) // pobj指向a所在的地址 { pobj->age = 10; // 通过指针修改a }

  43. 参数和返回值 (4) 返回对象实例 • 理解隐含的构造函数、拷贝构造函数、赋值函数调用 A CreateA() { A obj; // 在堆栈创建局部对象 obj.age = 10; return obj; // obj被拷贝给一个临时对象tmp } // 函数结束,堆栈弹出,局部对象obj被销毁 调用: A a; // 调用A的默认构造函数 a = CreateA(); // 调用a的赋值函数将tmp对象赋给a A b = CreateA(); // 情况会有所不同,obj直接通过拷贝构造函数拷贝给b

  44. 参数和返回值 (5) 返回对象指针 A* CreateA() { A obj(); obj.age = 10; return &obj; } // obj将被销毁,因此返回的地址实际上没有意义 调用: A *p = NULL; // 这不会引起调用A的默认构造函数 p = CreateA(); // 没有临时对象产生,没有拷贝构造函数和赋值函数调用 考察:既然p得到的地址没有意义,为什么有些情况下通过p来 访问对象的数据,仍然可以成功呢? • 返回无效的对象指针

  45. 参数和返回值 (6) 返回对象指针 A* CreateA() { A *pObj = new A(); pObj->age = 10; return pObj; } // 由于pObj分配再堆而不是堆栈上,因此不会被自动销毁 调用: A *p = NULL; // 这不会引起调用A的默认构造函数 p = CreateA(); delete p; // 释放内存 • 返回有效的对象指针

  46. 参数和返回值 (7) 返回对象引用 • 返回对象引用可以避免临时对象的产生 • 但是要特别注意,不要返回无效的引用 A& CreateA() { A obj; obj.age = 10; return obj; // obj将被销毁 } A& b = CreateA(); // b引用obj,但obj已经销毁,因此b无效 考察:为什么赋值函数中可以并且应该返回引用?

  47. 参数和返回值 (8) 编写高效的return • 直接在return语句中构造对象将有助于减少临时对象的创建和销毁 A GetA() { //A a(10); // 创建临时对象 //return a; // 调用拷贝构造函数将临时对象赋给obj,然后销毁临时对象 return A(10); // 直接把临时对象创建在obj的内存单元中z } A obj = GetA();

  48. const修饰符 (1) 修饰参数和返回值 • 修饰参数 • 编译器确保该参数在函数内不会被修改 • 修饰返回值 • 返回结果是常量,不能修改 考察: f(int i) 与 f(const int i)有何区别? const complex operator+(const complex& c1, const complex& c2) { return complex(c1.real+c2.real,c1.image+c2.image); } 考察: (1)c1和c2声明为常量引用有什么好处? (2)设obj1,obj2,obj3都是complex,则(obj3+obj2) = obj1是否合法? (3) 函数为什么不返回一个引用?

  49. const修饰符 (2) 常函数 • 对于一个不会修改数据成员的类成员函数,建议声明为常函数 • 常量实例只能调用函数的const版本 • 非常量实例一般调用函数的非const版本 • 如果一个函数只有const版本而没有非const版本,那么非常量实例可以调用const版本 • Const函数内部不能再调用非const函数 int GetModule() {return real*real + image*image;} int GetModule() const {return real*real + image*image;} 如果:complex c1(1,2); c1.GetModule(),则调用非const const complex c2(1,2); c2.GetModule(),则调用const

  50. 运算符重载 (1) 实现方式 Complex& operator +=(Complex c){ this.real += c.real; this.image += c.image; return *this; } • 重载为类实例成员函数 • 重载为友元函数 • 考察:重载了”+”后,还需要重载”+=“吗? friend Complex operator +(Complex& c1,Complex& c2){ // 友元是全局函数,应该生成新对象返回,而不能返回*this return Complex(c1.real+c2.real,c1.image+c2.image); }

More Related