C高级编程课程讲义.ppt_第1页
C高级编程课程讲义.ppt_第2页
C高级编程课程讲义.ppt_第3页
C高级编程课程讲义.ppt_第4页
C高级编程课程讲义.ppt_第5页
已阅读5页,还剩83页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C+高级编程,Overview,Module 1 : 面向过程的C+ Module 2 : 面向对象的C+ Module 3 : C+设计模式,Module 1 : 面向过程的C+,从C到C+ 内存管理 数组 字符集和字符串 指针和引用 函数 其它,从C到C+,语法增强 新的运算符 变量声明更加灵活 函数重载 引用 类型安全性 面向对象 封装 继承 多态,内存管理,在stack上分配内存 简单数据类型 不使用new创建对象实例 函数调用完成自动销毁 线程堆栈的限制 在Heap上分配内存 malloc 还是 new ? 检测内存泄漏 防止”野指针”:回收内存后指针会自动为NULL吗? Demo: 动态扩充的字符串缓冲区,数组 (1) 声明及初始化,int a=1,2,3; int b3=1,2,3; int c5=1,2,3; / c3和c4的值默认为0,int a123=1,2,3,4,5,6; int a223=1,; / 初始化,第一行为1,0,0;第二行为0,0,0 int a33=,1,2,3; int a42=1,2,3,4,5,6; / 错误。必须声明第二维的数目 int a53=1,2,3,4,5,6; / 正确,数组 (2) 数组的内存形式,数组在内存中的存放 全局数组在静态存储区域中存放 局部数组,一般在堆栈上存放 在堆上动态分配内存 线程堆栈的限制:double arr500300 ?,double *p = new double10; int n = 10; double *p = new doublen; / n不必是常量 double *p = new double400300; / 错误 double *p = new double400300; / 错误,数组 (3) 指针表现形式,一维数组指针表示 二维数组指针表示 行地址和列地址,const int ARRAY_SIZE = 5; int aARRAY_SIZE = 1,2,3,4,5; int *p = a; 考察: (1) a+i, p+i, *(a+i), *a+i, pi 的含义 (2) p+偏移几个字节?a+呢?,int a 3 = 1,2,3,4,5,6; 考察:a, a+i, *(a+i), *(a+i)+j, *(*(a+i)+j), &a i j 的含义,数组 (4) 二维数组的指针表示,使用一级指针访问二维数组 使用指向一维数组的指针来访问二维数组,int a 3 = 1,2,3,4,5,6; int *p=a0; / 将二维数组展开成一维数组的方式访问 int *p = a; / 错误! 考察: p+i, *(p+i), *p+i的含义,int (*p)3=a; / 也可写成:int (*p)3(a); int *p3=a; / 错误! Int* p3; 考察:p, p+i, *(p+i), *p+i, *(p+i)+j, *(*(p+i)+j)的含义,数组 (5) 动态创建复杂数组,二维数组 交错数组,int m = 2; int n = 3; int (*p)3 = new int23; delete p;,int* pm; / p数组包含两个元素,每个元素都是int*类型 p1 = new int3; / 通过p i 来操作每个数组 delete p1; / 不能使用 delete p或者delete p,字符集和字符串 (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 p210; int len = wcstombs(p2, p1, sizeof(p1); / len = 4 0字符不计入拷贝字符数目 wchar_t *p3 = (wchar_t*)malloc( 100); mbstowcs(p3, p2, 100);,字符集和字符串 (2) 兼容字符集,Windows平台与字符集 Visual C+编译器字符集设置选项 _UNICODE _MBCS 兼容字符集 TCHAR, _tcslen, “_T”,TCHAR t = _T(“aaa“); / 如果编译器定义了_UNICODE,sizeof(t)=8, / 如果定义了_MBCS,sizeof(t)=4,字符集和字符串 (3) 声明字符串,使用指针 使用数组,char *p1 = “abcd“; char *p2 = “abcd“; 考察:内存中有几个”abcd”存在, p1 = p2是否成立?,char p110 = a,b,c,d,0; / 注意结尾的0 char p2 = “abcd”; / 自动添加0结尾 char p3 = “abcd”; char p4 = a,b,c,d; / 仅仅声明一个字符数组而非字符串 考察 (1) sizeof(p), strlen(p)的值分别是多少? (2) p2 = p3 ?,字符集和字符串 (4) 字符串操作函数,修改单个字符 字符串长度 模拟编写strcpy方法 为何采用const作为参数 为何返回char* 0会被拷贝吗? Demo,char p110 = “abcd”; char* p2 = “abcd”; / p2指向常量字符串 p10 = x / OK p20 = x / 错误,常量字符串不可修改,char p1 = “atnc“; 考察: strlen(p1)和sizeof(p1),指针与引用 (1) 概念,指针与指针变量 引用的定义 引用必须在声明时立即初始化,不允许空引用 引用一旦初始化,就不能再引用其它数据 引用和被引用的变量实际上代表同一个内存的数据,double a = 10; double* p = 考察: (1)p是指针变量,则sizeof(p)=? (2)&p含义是什么?,int a = 100; int 考察: (1)&a与&b的关系? (2) a=?,指针与引用 (2) 混合使用,指针指向某个引用 引用一个地址,double a = 100; double 考察: (1) &a, p和&b的关系 (2)修改a, b或*p的值,另两个是否联动,double a = 100; double / p和rp是完全相同的,且都是变量a的地址 考察:sizeof(b)=? sizeof(rp) = ?,指针与引用 (3) 作为参数传递,按值传递 按地址传递 按引用传递,void Increase(int ,指针与引用 (4) 指针与常量,指向常量的指针 指针常量,int a =100; int b = 200; const int* pci = 是否成立?,int a =100; int b = 200; int* const pci = / 错误 考察:如果有 const int* const pci = &a, 则情况如何?,指针与引用 (4) 引用与常量,将一个引用设置为常量后,不能通过该引用修改数据;但仍可通过被引用的变量来改变: 被引用的数据是常量,引用本身也必须是常量:,int a = 100; const int / OK,const int a = 100; const int / 错误,指针与引用 (5) Practice,char s1 = “abcdefg“; char s2 = “1234567“; char* p1 = s1; const char* p2 = s1; char* const p3 = s1; const char* const p4 = s1; 考察:下列哪些语句有效: p10=k; p20=k; p30=k; p1=s2; p2=s2; p3=s2; p4=s2;,函数 (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); ,函数 (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);,函数 (3) 函数指针,设有函数定义: int myequal1(char* s1,char* s2) . 类型定义: typedef int (*equal)(char*,char*); 则可以声明: equal eq=myequal1; 并且调用: (*eq)(s1,s2);,函数 (4) 缺省参数,注意 缺省参数必须出现在所有非缺省参数的后面 防止函数重载时的二义性,void f(int i, int j=10, int k=20) 调用: f(1,20),相当于:f (1,20,20) 而: f(1), 相当于:f(1,10,20),函数 (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 ); ,其它,sizeof 针对字符串的sizeof 针对数组和指针的sizeof 针对结构体的sizeof 变量赋值 a=b=c=3 ? int *x, y ? int* x, y const 和 define 作用域限定运算符 无名共用体 枚举,Module 2 : 面向对象的C+,构造和销毁 参数和返回值 const修饰符 运算符重载 虚函数 友元 模板 其它,构造和销毁 (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;,构造和销毁 (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; ;,成员变量较少的情况下尽量使用初始化列表 各变量在初始化列表中出现的顺序最好与变量声明的顺序一致,构造与析构 (3) 拷贝构造函数,函数原型 调用场合 默认拷贝构造函数 成员变量之间的“值”拷贝,A ( const A& other) ,A a1(10); / 构造函数 A a2 = a1; / 拷贝构造函数 A a3(a1); / 拷贝构造函数,构造与析构 (4) 拷贝构造函数,编写拷贝构造函数的必要性,Class A private: char* name; public: A(const char* data) name = new charstrlen(data) + 1; strcpy (name, data); A(const A 如果未定义拷贝构造函数,会有何种后果?,构造与析构 (5) 赋值函数,赋值函数原型 调用场合 编写赋值函数的必要性,A& operator =( const A& other) ,A a1(10); / 为a1调用构造函数 A a2; / 为a2调用默认构造函数 a2 = a1; / 为a2调用赋值函数。 区别 A a2=a1;,A有何局限?,构造与销毁 (5) 初始化列表与拷贝构造函数,子对象在构造函数列表中初始化 直接调用拷贝构造函数 子对象在构造函数中赋值 先调用子对象的默认构造函数 调用赋值函数为子对象重新赋值,class A B b; public: A(B b1) b = b1; /*先调用默认构造函数,再调用赋值函数 */ A(B b1) : b(b1) /*调用拷贝构造函数*/ ,构造和销毁 (6) 构造和析构顺序,调用基类构造函数 为各子对象调用构造函数 如果有多个子对象,则各个子对象构造顺序与声明顺序一致,而不依赖初始化列表中出现的顺序 本类构造函数 析构函数的顺序与构造函数相反 如果没有显式调用基类或子对象构造函数,那么将调用其默认构造函数;如果默认构造函数不可访问,则编译错误,构造与销毁 (7) 内存回收,析构函数 作用 何时调用 通过指针使用对象 new-delete NULL指针与野指针 Demo 构造函数、拷贝构造函数、赋值函数、析构函数综合使用实例分析,参数和返回值 (1) 传递数组,void f(char* arr) arr0 = A; / 修改可以成功 void f(char* arr) arr = new char10; / 修改无效 strcpy(arr, “12345“); void f(char* arr) *arr = new char10; / 修改可以成功 strcpy(*arr, “12345“); ,参数和返回值 (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 ,参数和返回值 (3) 传递对象引用或指针,省去了参数对象之间的拷贝,提高效率,void f(A / 修改obj就是修改a ,void f(A* pobj) / pobj指向a所在的地址 pobj-age = 10; / 通过指针修改a ,参数和返回值 (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,理解隐含的构造函数、拷贝构造函数、赋值函数调用,参数和返回值 (5) 返回对象指针,返回无效的对象指针,A* CreateA() A obj(); obj.age = 10; return / 没有临时对象产生,没有拷贝构造函数和赋值函数调用 考察:既然p得到的地址没有意义,为什么有些情况下通过p来 访问对象的数据,仍然可以成功呢?,参数和返回值 (6) 返回对象指针,返回有效的对象指针,A* CreateA() A *pObj = new A(); pObj-age = 10; return pObj; / 由于pObj分配再堆而不是堆栈上,因此不会被自动销毁 调用: A *p = NULL; / 这不会引起调用A的默认构造函数 p = CreateA(); delete p; / 释放内存,参数和返回值 (7) 返回对象引用,返回对象引用可以避免临时对象的产生 但是要特别注意,不要返回无效的引用,A / b引用obj,但obj已经销毁,因此b无效 考察:为什么赋值函数中可以并且应该返回引用?,参数和返回值 (8) 编写高效的return,直接在return语句中构造对象将有助于减少临时对象的创建和销毁,A GetA() /A a(10); / 创建临时对象 /return a; / 调用拷贝构造函数将临时对象赋给obj,然后销毁临时对象 return A(10); / 直接把临时对象创建在obj的内存单元中 A obj = GetA();,const修饰符 (1) 修饰参数和返回值,修饰参数 编译器确保该参数在函数内不会被修改 修饰返回值 返回结果是常量,不能修改,考察: f(int i) 与 f(const int i)有何区别?,const complex operator+(const complex 考察: (1)c1和c2声明为常量引用有什么好处? (2)设obj1,obj2,obj3都是complex,则(obj3+obj2) = obj1是否合法? (3) 函数为什么不返回一个引用?,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,运算符重载 (1) 实现方式,重载为类实例成员函数 重载为友元函数 考察:重载了”+”后,还需要重载”+=“吗?,Complex ,friend Complex operator +(Complex ,运算符重载 (2) +操作符,class Count public: int num; Count():num(0) Count,重载运算符 (3) 编写类型转换函数,Complex operator=(double d) / 允许double隐式转换成Complex this-real = 0.0; this-image=0.0; operator double() return real; /允许Complex隐式转换成double 调用:Complex c; c = 2.0; double d = c; 考察: (1)能否写成:Complex c = 2.0 ?如何允许这种写法? (2) 区别隐式转换与赋值函数,运算符重载Demo: 复数类,虚函数 (1) 多态,class Shape public: void Draw() ; / 基类 class Rectangle : public Shape class Ellipse : public Shape public: void Draw() ; public: void Draw() ;,不应用多态:考察:如果有N个子类? void Draw(Rectangle* pRec) pRec-Draw(); void Draw(Ellipse* pEll) pEll-Draw();,应用多态: void Draw(Shape *pShp) pShp-Draw();,虚函数 (2) virtual关键字,如果基类函数没有virtual关键字? 静态联编 动态联编 子类函数还需要编写virtual吗? 为什么析构函数要写成virtual? 纯虚函数和抽象类,虚函数 (3) 区分重载/重写/隐藏,Overload 同一个类中同名但不同参数的函数 Override 子类重新实现基类继承的函数 子类函数与基类函数原型完全一致 Hide 子类与基类定义了同名函数但原型不同,则子类隐藏基类的函数 Demo: 隐藏示例,虚函数 (4) 多重继承,为什么需要多重继承 如何避免多重继承重的二义性 虚继承 MFC中多重继承的例子,友元 (1) 友元函数,友元函数可以做什么? 友元函数可以是全局函数 也可以是类成员函数 不要使用访问限定符来修饰friend语句,friend int global_func(.);,friend int someclass:class_func(.); / class_func本身可有访问限定符,友元 (2) 友元类,类A是类B的友元类,则类A中所有函数都可以访问类B中的任何成员 声明友元类 Demo: 一个链表维护类,friend class someclass;,模板 (1) 模板的必要性,void swap (int& i, int& j) void swap (float& i, float& j) void swap (uint& i, uint& j) ,不使用模板,使用模板,template void swap(T swap(a,b),模板 (2) 模板函数,单类型模板 多类型模板 模板函数代码在哪里编写?,template void Swap(T1& a,T2& b),模板 (3) 模板类,类声明 类中相关变量、参数和返回值类型用T代替 函数实现 可以在类声明内或外部实现 .h文件还是.cpp文件? 使用,template class Stack void push (T a);.;,template void Stack:push (T a).,Stack s;,模板 (4) 模板应用实例,C+标准模板库 ATL活动模板库,其它 (1) 局部类和嵌套类,局部类 在一个函数的内部定义 所有函数体都在类声明内;不能有静态变量 可访问函数中已经声明的局部变量 不可直接访问函数所在类的成员 嵌套类 在包容类的内部定义 嵌套类中可直接访问包容类的任何成员 包容类可以访问嵌套类的共有成员;不能访问非公成员 从包容类外部访问嵌套类成员时,访问限定取最严格集 Demo: MFC使用嵌套类实现COM多接口,其它 (2) 内联函数和宏,宏定义的不足 编译器简单替换,不进行类型检查 容易造成歧义 内联函数 函数体直接写在类声明内 也可用inline关键字在类声明外编写 (建议),#define max(a,b) (a) (b) ? (a) : (b) 考察:i=5, j=0, 计算max(+i, j),inline int max(int a, int b) return a b ? a : b; ,其它 (3) 强制类型转换,static_cast 可用于简单数据类型之间以及对象指针的转换 运行时不进行类型检查,因此不能保证转换的安全性。即使转换失败,也会返回某个地址 程序员应确保转换确实可行 dynamic_cast 不能用于简单数据类型之间的转换 运行时如果转换失败,则返回NULL,ch = static_cast(i); / int to char B* pb = new D; D* pd = dynamic_cast(pb); / ok: pb actually points to a D,其它 (4) 异常处理,try char* buf = new char512; if( buf = 0 ) throw “Memory allocation failure!“; catch( char * str ) cout “Exception raised: “ str n; ,抛出和处理简单异常,throw CMyException(); catch(CMyexception ex) ,抛出自定义异常类,处理多个异常,try catch(char* str) catch(int code) catch() ,其它 (5),对象数组 对象数组的初始化问题 Demo 面向对象的C+实现:Stack类 面向对象的二叉树实现 使用多态性构造的有限状态自动机,C+设计模式,核心设计模式 创建型 结构型 行为型 为什么要使用设计模式 合理使用设计模式,创建型模式 (1) 简单工厂,由一个工厂类决定创建那些类的实例 该工厂类可以创建多个类别的对象,创建型模式 (2) 工厂方法,工厂定义创建产品对象的接口,实际的创建工作在工厂子类中完成 每个子类一般专门负责创建一种产品。因此该子类只需要了解它所需要创建的产品类型即可 COM的类工厂,创建型模式 (3) 抽象工厂,抽象工厂为创造一系列产品提供一个抽象接口,由子类实现具体对象的创建 每个工厂一般负责创建一系列(多种)产品,创建型模式 (4) 生成器模式,将对象的创建步骤和创建细节分开 构造器负责产品各个部分构造 导航器负责组装产品,创建型模式 (5) 原型模式,对象通过赋值自己创建新

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论