-
C中的命名空间:在C语言中只有一个全局作用域,C语言中所有的全局标识符共享同一个作用域,标识符之间可能发生冲突 ;
-
C++ 中提出了命名空间的概念:命名空间将全局作用域分成不同的部分,不同命名空间中的标识符可以同名而不会发生冲突,命名空间可以相互嵌套,全局作用域也叫默认命名空间;
-
C++ 命名空间的定义:
namespace name { … }
-
C++ 命名空间的使用:
-
使用整个命名空间:using namespace name;
-
使用命名空间中的变量:using name::variable;
-
使用默认命名空间中的变量:::variable,默认情况下可以直接使用默认命名空间中的所有标识符
-
-
C语言中无法取得register变量地址,在C++ 中依然支持register关键字,C++ 编译器有自己的优化方式,不使用register也可能做优化,C++ 中可以取得register变量的地址,C++ 编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效;
-
C语言的struct定义了一组变量的集合,C编译器并不认为这是一种新的类型,C++ 中的struct是一个新类型的定义声明
-
C++ 在C语言的基本类型系统之上增加了bool,C++ 中的bool可取的值只有true和false,理论上bool只占用一个字节, 如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现,true代表真值,编译器内部用1来表示,false代表非真值,编译器内部用0来表示, bool类型只有true(非0、false(0)两个值, C++ 编译器会在赋值时将非0值转换为true,0值转换为false;
-
C++ 中三目运算结果可以当左值;
-
C++ 中对const增强,不再能通过指针间接修改const修饰的值;当碰见常量声明时,在符号表中放入常量 =è问题:那有如何解释取地址,编译过程中若发现使用常量则直接以符号表中的值替换,编译过程中若发现对const使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)
-
C语言中的const变量:const变量是只读变量,有自己的存储空间;
-
C++ 中的const常量:可能分配存储空间,也可能不分配存储空间 ,当const常量为全局,并且需要在其它文件中使用,会分配存储空间,当使用&操作符,取const常量的地址时,会分配存储空间,当const int &a = 10; const修饰引用时,也会分配存储空间;
-
C++ 中的const修饰的,是一个真正的常量,而不是C中变量(只读)。在const修饰的常量编译期间,就已经确定下来了;
-
const常量是由编译器处理的,提供类型检查和作用域检查,宏定义由预处理器处理,单纯的文本替换
-
练习
void fun1() { #define a 10 const int b = 20; //#undef a # undef 卸载宏,可以卸载所有宏,也可以卸载某个宏 } void fun2() { printf("a = %d\n", a); //printf("b = %d\n", b); } int main() { fun1();
-
引用是C++ 的概念,C语言中没有引用的概念,不能用C的语法考虑C++ 中的引用;
-
引用的一般语法
Type& name = var;
-
普通引用在声明时必须用其它的变量进行初始化,引用作为函数参数声明时不进行初始化;
-
引用作为其它变量的别名而存在,因此在一些场合可以代替指针,引用相对于指针来说具有更好的可读性和实用性
-
引用类似于定义一个常指针指向变量,指针的指向只能指向引用变量的地址不能变;
-
C++ 编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同;
-
当函数返回值为引用时,若返回栈变量,不能成为其它引用的初始值,不能作为左值使用,但是返回静态变量或全局变量,可以成为其他引用的初始值,既可作为右值使用,也可作为左值使用;
-
用引用去接受函数的返回值,是不是乱码,关键是看返回的内存空间是不是被编译器回收了;
-
指针的引用
struct Teacher { char name[64]; int age; }; int getTe(Teacher* &myp) { myp = (Teacher *)malloc(sizeof(Teacher)); myp->age = 34; return 0; } void main() { Teacher *p = NULL; getTe2(p); printf("age:%d \n", p->age); system("pause"); }
-
常引用:const引用让变量拥有只读属性
案例1: int main() { int a = 10; const int &b = a; //int *p = (int *)&b; b = 11; //err //*p = 11; //只能用指针来改变了 cout<<"b--->"<<a<<endl; printf("a:%d\n", a); printf("b:%d\n", b); printf("&a:%d\n", &a); printf("&b:%d\n", &b); system("pause"); return 0; } 案例2: void main41() { int a = 10; const int &b = a; //const引用 使用变量a初始化 a = 11; //b = 12; //通过引用修改a,对不起修改不了 system("pause"); } struct Teacher1 { char name[64]; int age; }; void printTe2(const Teacher1 *const pt) { } //const引用让变量(所指内存空间)拥有只读属性 void printTe(const Teacher1 &t) { //t.age = 11; } void main42() { Teacher1 t1; t1.age = 33; printTe(t1); system("pause"); }
-
用变量对const引用初始化,用常量对const引用初始化
void main() { //普通引用 int a = 10; int &b = a; //常量引用 :让变量引用只读属性 const int &c = a; //常量引用初始化 分为两种 //1 用变量 初始化 常量引用 { int x = 20; const int& y = x; printf("y:%d \n", y); } //2 用常量 初始化 常量引用 { //int &m = 10; //引用是内存空间的别名 字面量10没有内存空间 没有方法做引用 const int &m = 10; } cout<<"hello..."<<endl; system("pause"); return ; }
-
引用的有关结论
//Const & int e 相当于 const int * const e //普通引用 相当于 int *const e1 //当使用常量(字面量)对const引用进行初始化时,C++ 编译器会为常量值分配空间,并将引用名作为这段空间的别名 //使用字面量对const引用初始化后,将生成一个只读变量
-
综合练习
int& j() { static int a = 0; return a; } int& g() { int a = 0; return a; } int main() { int a = g(); int& b = g(); j() = 10; printf("a = %d\n", a); printf("b = %d\n", b); printf("f() = %d\n", f()); system("pause"); return 0; }
-
C++中的const常量可以替代宏常数定义,推荐使用内联函数替代宏代码片段,使用inline关键字声明内联函数,内联函数声明时inline关键字必须和函数定义结合在一起,否则编译器会直接忽略内联请求;
-
内联函数在最终生成的代码中是没有定义的,C++编译器直接将函数体插入在函数调用的地方,内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
-
C++ 编译器不一定准许函数的内联请求,内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等),内联函数是对编译器的一种请求,因此编译器可能拒绝这种请求,内联函数由编译器处理,直接将编译后的函数体插入调用的地方,宏代码片段 由预处理器处理, 进行简单的文本替换,没有任何编译过程,现代C++ 编译器能够进行编译优化,因此一些函数即使没有inline声明,也可能被编译器内联编译;
-
一些现代C++ 编译器提供了扩展语法,能够对函数进行强制内联
__attribute__((always_inline))//g++属性
-
C++中内联编译的限制:不能存在任何形式的循环语句 ,不能存在过多的条件判断语句,函数体不能过于庞大,不能对函数进行取址操作,函数内联声明必须在调用语句之前;
-
编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销,因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义;
-
内联函数解决了带参数宏++,–操作一系列的问题
-
C++中可以在函数声明时为参数提供一个默认值,当函数调用时没有指定这个参数的值,编译器会自动用默认值代替;
-
函数默认参数的规则:只有参数列表后面部分的参数才可以提供默认参数值,一旦在一个函数调用中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数;
-
函数占位参数:函数调用时,必须写够参数,占位参数只有参数类型声明,而没有参数名声明;
-
函数占位参数和默认参数结合起来使用
int func2(int a, int b, int = 0) { return a + b; } void main() { //如果默认参数和占位参数在一起,都能调用起来 func2(1, 2); func2(1, 2, 3); system("pause"); }
-
如果默认参数和占位参数在一起,都能调用起来;
-
函数重载:用同一个函数名定义不同的函数,当函数名和不同的参数搭配时函数的含义不同;
-
函数重载的判断标准:函数重载至少满足下面的一个条件:
- 参数个数不同
- 参数类型不同
- 参数顺序不同
-
函数返回值不是函数重载的判断标准;
-
编译器调用重载函数的准则:
- 将所有同名函数作为候选者
- 尝试寻找可行的候选函数
- 精确匹配实参
- 通过默认参数能够匹配实参
- 通过默认类型转换匹配实参
- 匹配失败
- 最终寻找到的可行候选函数不唯一,则出现二义性,编译失败。
- 无法匹配所有候选者,函数未定义,编译失败
-
重载函数在本质上是相互独立的不同函数(静态链编);
-
函数重载与默认参数在一起
void myfunc(int a, int b, int c = 0) { printf("a:%d b:%d c:%d \n", a, b, c); } void myfunc(int a, int b) { printf("a:%d b:%d\n", a, b); } void myfunc(int a) { printf("a:%d\n", a); } void main() { //myfunc(1, 2); //函数调用时,会产生二义性 myfunc(1); cout<<"hello..."<<endl; system("pause"); return ; }
-
函数重载与函数指针在一起:当使用重载函数名对函数指针进行赋值时,根据重载规则挑选与函数指针参数列表一致的候选者,严格匹配候选者的函数类型与函数指针的函数类型;
int func(int x) // int(int a) { return x; } int func(int a, int b) { return a + b; } int func(const char* s) { return strlen(s); } typedef int(*PFUNC)(int a); // int(int a) int main() { int c = 0; PFUNC p = func; c = p(1); printf("c = %d\n", c); printf("Press enter to continue ..."); getchar(); return 0; }
-
面向对象三大概念:封装、继承、多态;
-
封装的两层含义:把属性和方法进行封装,对属性和方法进行访问控制;
-
Public修饰成员变量和成员函数可以在类的内部和类的外部被访问,Private修饰成员变量和成员函数只能在类的内部被访问;
-
struct和class关键字区别:在用struct定义类时,所有成员的默认属性为public,在用class定义类时,所有成员的默认属性为private;
-
类的声明和实现分开
//main.cpp #include <iostream> using namespace std; #include "AdvCircle.h" #include "MyPoint.h" void main() { AdvCircle c1; MyPoint p1; c1.setCircle(2, 3, 3); p1.setPoint(7, 7); //在圆形1 不在圆内 0 int tag = c1.judge(p1); if (tag == 1) { cout<<"点在圆形内"<<endl; } else { cout<<"点在圆形外"<<endl; } system("pause"); return ; } //AdvCircle.h #pragma once #include "MyPoint.h" class AdvCircle { public: void setCircle(int _r, int _x0, int _y0); int judge(MyPoint &myp); private: int r; int x0; int y0; }; //AdvCircle.cpp #include "iostream" using namespace std; #include "AdvCircle.h" #include "MyPoint.h" void AdvCircle::setCircle(int _r, int _x0, int _y0) { r = _r; x0 = _x0; y0 = _y0; } int AdvCircle::judge(MyPoint &myp) { int dd = (myp.getX1() - x0 )*(myp.getX1() - x0 ) + (myp.getY1()-y0)* (myp.getY1()-y0); if (dd <= r*r) { return 1; //圆形内部 } else { return 0; //0点在圆外 } }
-
构造函数分类
class Test { private: int a; int b; public: //无参数构造函数 Test() { ; } //带参数的构造函数 Test(int a, int b) { ; } //赋值构造函数 Test(const Test &obj) { ; } public: void init(int _a, int _b) { a = _a; b = _b; } };
-
无参构造函数调用
Test t1,t2;
-
有参构造函数的调用方法
class Test5 { private: int a; public: //带参数的构造函数 Test5(int a) { printf("\na:%d", a); } Test5(int a, int b) { printf("\na:%d b:%d", a, b); } public: }; int main() { Test5 t1(10); //c++编译器默认调用有参构造函数 括号法 Test5 t2 = (20, 10); //c++编译器默认调用有参构造函数 等号法,括号里是逗号表达式,结果会调用一个参数的构造函数 Test5 t3 = Test5(30); //程序员手工调用构造函数 产生了一个对象 直接调用构造构造函数法 system("pause"); return 0; }
-
对象的初始化和对象的赋值是不通的概念,用一个对象初始化另一个对象会调用拷贝构造函数,但是对象赋值给另一个对象不会调用,只是简单的赋值运算,如果存在浅拷贝问题,需要对赋值运算符“=”重载;
-
类没有提供构造函数,c++编译器会自动给你提供一个默认的构造函数,类没有提供构造函数 copy构造构造函数, c++ 编译器会自动给程序员提供一个 默认的copy构造函数 ;
-
拷贝构造函数的调用时机(有的编译器会优化,不会调用)
- 用一个对象去初始化另外一个对象,Test5 t2 = t1;
- Test5 t2(t1);
- 实参传给形参时
- 函数返回值是匿名对象,用该匿名对象赋值给一个对象,会调用拷贝构造函数,拷贝到匿名对象,赋值给对象然后匿名对象自动析构
- 函数返回值是匿名对象,用该用匿名对象初始化一个对象,直接将匿名对象转为初始化的对象(改名)
-
匿名对象的去和留,关键看,返回时如何接;
-
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空;
-
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制;
-
当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数;
-
当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数;
-
默认拷贝构造函数成员变量简单赋值;
-
只要你写了构造函数就必须用;
-
默认复制构造函数可以完成对象的数据成员值简单的复制,对象的数据资源是由指针指示的堆时,默认复制构造函数仅作指针值复制,浅拷贝
-
浅拷贝程序C++提供的解决方法:显式提供copy构造函数,显式操作重载=号操作,不使用编译器提供的浅copy;
-
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数
#include <iostream> using namespace std; class A { public: A(int _a) { a = _a; cout << "构造函数" << "a" << a << endl; } ~A() { cout << "析构函数" << "a" << a << endl; } protected: private: int a; }; //1 构造函数的初始化列表 解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数) //根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A //新的语法 Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3) class B { public: B(int _b1, int _b2) : a1(1), a2(2), c(0) { } B(int _b1, int _b2, int m, int n) : a1(m), a2(n), c(0) { b1 = _b1; b2 = _b2; cout <<"B的构造函数"<<endl; } ~B() { cout<<"B的析构函数" <<endl; } protected: private: int b1; int b2; A a2; A a1; const int c; }; //2 先执行 被组合对象的构造函数 //如果组合对象有多个,按照定义顺序, 而不是按照初始化列表的顺序 //析构函数 : 和构造函数的调用顺序相反 //3 被组合对象的构造顺序 与定义顺序有关系 ,与初始化列表的顺序没有关系. //4 初始化列表 用来 给const 属性赋值
-
拷贝构造函数也可以带初始化列表,因为类作函数参数会调用拷贝构造函数将实参传给一个匿名对象时进行初始化;
-
匿名对象使用完就会调用析构函数析构;
-
在构造函数中不能调用构造函数,因为只会产生一个匿名对象,影响不了实际要初始化的对象;
-
C语言中是利用库函数malloc和free来分配和撤销内存空间的。C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数,new和delete是运算符,不是函数,因此执行效率高;
-
在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功;
-
虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符;
new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针) new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址 new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址 new int[5][4]; //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址 float *p=new float (3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p Box *pt; //定义一个指向Box类对象的指针变量pt pt=new Box; //在pt中存放了新建对象的起始地址 在程序中就可以通过pt访问这个新建的对象。如 cout<<pt->height; //输出该对象的height成员 cout<<pt->volume( ); //调用该对象的volume函数,计算并输出体积 C++还允许在执行new时,对新建立的对象进行初始化。如 Box *pt=new Box(12,15,18); delete p;释放指针 delete [] arry;释放数组
-
用malloc分配类对象内存和free释放内存不会自动调用构造和析构函数,而用new和delete会自动调用构造和析构函数;
-
把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员;
-
静态成员局部于类,它不是对象成员;
-
初始化和访问静态成员变量
int counter :: Smem = 1 ;//必须在类的外面初始化 cout << counter::Smem << '\t' ; //访问静态成员变量方法2 cout<<"c.Smem = "<<c.Smem<<endl; //访问静态成员变量方法1
-
静态成员函数:静态成员函数数冠以关键字static,静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针,在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用,同静态成员变量一样;
-
静态成员函数中,能使用静态成员变量但不能使用普通变量;
-
C++对象模型:
- 语言中直接支持面向对象程序设计的部分,主要涉及如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等
- 对于各种支持的底层实现机制,在c语言中,“数据”和“处理数据的操作(函数)”是分开来声明的,也就是说,语言本身并没有支持“数据和函数”之间的关联性。在c++中,通过抽象数据类型(abstract data type,ADT),在类中定义数据和函数,来实现数据和函数直接的绑定
概括来说,在C++类中有两种成员数据:static、nonstatic;三种成员函数:static、nonstatic、virtual
-
C++类对象中的成员变量和成员函数是分开存储的:成员变量:普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式,静态成员变量:存储于全局数据区中,成员函数:存储于代码段中。
-
C语言中的内存四区模型仍然有效,C++中类的普通成员函数都隐式包含一个指向当前对象的this指针;静态成员函数、成员变量属于类,静态成员函数与普通成员函数的区别:静态成员函数不包含指向具体对象的指针,普通成员函数包含一个指向具体对象的指针;
-
类成员函数的形参和类的属性名字相同,通过this指针来解决,类的成员函数可通过const修饰,const修饰的是this指针指向的内容,const可以写在前面后面都行;
-
this指针不能修改指向;
-
把全局函数转化成成员函数,通过this指针隐藏左操作数
Test add(Test &t1, Test &t2)===》Test add(Test &t2)
-
把成员函数转换成全局函数,多了一个参数
void printAB()===》void printAB(Test *pthis)
-
全局函数转换成成员函数案例
#include <iostream> using namespace std; class Test { public: int a; int b; public: ~Test() { cout<<"a:"<<a<<" b: "<<b; cout<<"析构函数自动被调用"<<endl; } public: void printT() { cout<<"a:"<<a<<" b: "<<b<<endl; } public: //t3 = t1.TestAdd(t2); Test TestAdd(Test &t2) { Test tmp(this->a + t2.a, this->b + t2.b); return tmp; } //t1.TestAdd2(t2); //返回一个引用 相当于返回自身 //返回t1这个元素 this就是&t1 Test& TestAdd2(Test &t2) { this->a = this->a + t2.a; this->b = this->b + t2.b; return *this; //把 *(&t1) 又回到了 t1元素 } public: Test(int a=0, int b=0) { this->a = a; this->b = b; } }; //把成员函数 转成 全局函数 多了一个参数 void printT(Test *pT) { cout<<"a:"<<pT->a<<" b: "<<pT->b<<endl; } //全局函数的方法 //全局函数 转成 成员函数 少了一个参数 Test TestAdd(Test &t1, Test &t2) { Test tmp; return tmp; } void main() { Test t1(1, 2); Test t2(3, 4); //t1 = t1 + t2; t1.TestAdd2(t2); t1.printT(); } void main181() { Test t1(1, 2); Test t2(3, 4); Test t3 ; //全局函数方法 t3 = TestAdd(t1, t2); //成员函数方法 { Test t4 = t1.TestAdd(t2); //匿名对象直接转化成t4 t4.printT(); Test t5; t5 = t1.TestAdd(t2); //匿名对象 复制 给t5 t5.printT(); } cout<<"hello..."<<endl; system("pause"); return ; }
-
函数返回元素和返回引用
Test& add(Test &t2) //*this //函数返回引用 { this->a = this->a + t2.getA(); this->b = this->b + t2.getB(); return *this; //*操作让this指针回到元素状态 } Test add2(Test &t2) //*this //函数返回元素 { //t3是局部变量 Test t3(this->a+t2.getA(), this->b + t2.getB()) ; return t3; }
-
数组类的实现
//arry.h #pragma once #include <iostream> using namespace std; class Array { public: Array(int length); Array(const Array& obj); ~Array(); public: void setData(int index, int valude); int getData(int index); int length(); private: int m_length; int *m_space; public: //函数返回值当左值,需要返回一个引用 //应该返回一个引用(元素本身) 而不是一个值 int& operator[](int i); //重载= Array& operator=(Array &a1); //重载 == bool operator==(Array &a1); //重载 != bool operator!=(Array &a1); }; //要求重载以下操作符 // [] == != //arry.cpp #include "myarray.h" Array::Array(int length) { if (length < 0) { length = 0; // } m_length = length; m_space = new int[m_length]; } //Array a2 = a1; Array::Array(const Array& obj) { this->m_length = obj.m_length; this->m_space = new int[this->m_length]; //分配内存空间 for (int i=0; i<m_length; i++) //数组元素复制 { this->m_space[i] = obj.m_space[i]; } } Array::~Array() { if (m_space != NULL) { delete[] m_space; m_space = NULL; m_length = -1; } } //a1.setData(i, i); void Array::setData(int index, int valude) { m_space[index] = valude; } int Array::getData(int index) { return m_space[index]; } int Array::length() { return m_length; } // int& Array::operator[](int i) { return m_space[i]; } //a3 = a1; Array& Array::operator=(Array &a1) { //1 释放原来的内存空间 if (this->m_space != NULL) { delete [] m_space; m_length = 0; } //2 根据a1大小 分配内存 m_length = a1.m_length; m_space = new int[m_length]; //3 copy数据 for (int i=0; i<m_length; i++) { //m_space[i] = a1.m_space[i]; m_space[i] = a1[i]; } return *this; } //if (a3 == a1) bool Array::operator==(Array &a1) { if (this->m_length != a1.m_length) { return false; } for (int i=0; i<m_length; i++) { if (this->m_space[i] != a1[i]) { return false; } } return true; } bool Array::operator!=(Array &a1) { /* if (*this == a1) { return true; } else { return false; } */ return !(*this == a1); } //main.cpp #include <iostream> using namespace std; #include "myarray.h" //类的框架设计完毕 //类的测试案例 //重载[] //void operator[](int i) //int operator[](int i); //int& operator[](int i); void main() { Array a1(10); for (int i=0; i<a1.length(); i++) { a1.setData(i, i); //2 a1[i] = i; // //函数返回值当左值,需要返回一个引用 //a1.operator [i] } cout<<"\n打印数组a1: "; for (int i=0; i<a1.length(); i++) { //cout<<a1.getData(i)<<" "; //1 cout<<a1[i]<<endl; } cout<<endl; Array a2 = a1; cout<<"\n打印数组a2: "; for (int i=0; i<a2.length(); i++) { cout<<a2.getData(i)<<" "; } cout<<endl; //3 Array a3(5); { a3 = a1; a3 = a2 = a1; cout<<"\n打印数组a3: "; for (int i=0; i<a3.length(); i++) { cout<<a3[i]<<" "; } //a3.operator=(a1) //Array& operator=(Array &a1) } //功能4 if (a3 == a1) { printf("相等\n"); } else { printf("不相等\n"); } //a3.operator==(a1); //bool operator==(Array &a1); if (a3 != a1) { printf("不相等\n"); } else { printf("相等\n"); } // //a3.operator!=(a1) // bool operator!=(Array &a1); cout<<"hello..."<<endl; system("pause"); return ; }
-
友元函数:在类里面声明友元函数,声明位置随意,友元函数可以访问类的私有属性,破化了类的封装;
-
若B类是A类的友员类,则B类的所有成员函数都是A类的友员函数,友员类通常设计为一种对数据操作或类之间传递消息的辅助类;
-
友元函数类似于java中的反射机制;
-
不能重载的运算符
. :: .* ?: sizeof
-
复数类中重载+和-运算符
//重载-运算符将下面+号换成-号 //全局函数法 #include <iostream> using namespace std; class Complex { private: int a; int b; public: friend Complex operator+(Complex &obj1, Complex &obj2); Complex(int a, int b) { this->a = a; this->b = b; } void print() { cout << a << "+" << b << "i" << endl; } }; Complex operator+(Complex &obj1, Complex &obj2) { Complex tmp(obj1.a + obj2.a, obj1.b + obj2.b); return tmp; } int main() { Complex t1(3, 4); Complex t2(5, 8); Complex t3(0, 0); t3 = t1 + t2; t3.print(); } //成员函数法 #include <iostream> using namespace std; class Complex { private: int a; int b; public: Complex(int a, int b) { this->a = a; this->b = b; } Complex operator+(Complex &obj) { Complex tmp(this->a + obj.a, this->b + obj.b); return tmp; } void print() { cout << a << "+" << b << "i" << endl; } }; int main() { Complex t1(3, 4); Complex t2(5, 8); Complex t3(0, 0); t3 = t1 + t2; t3.print(); }
-
重载前置和后置++和–,成员函数法,左移右移重载,全局函数法
#include <iostream> using namespace std; class Complex { private: int a; int b; public: Complex(int a, int b) { this->a = a; this->b = b; } Complex operator+(Complex &obj) { Complex tmp(this->a + obj.a, this->b + obj.b); return tmp; } Complex operator-(Complex &obj) { Complex tmp(this->a - obj.a, this->b - obj.b); return tmp; } Complex &operator++()//前置++ { this->a++; this->b++; return *this; } Complex &operator--()//前置-- { this->a--; this->b--; return *this; } Complex operator++(int)//后置++ { Complex tmp = *this; this->a++; this->b++; return tmp; } Complex operator--(int)//后置-- { Complex tmp = *this; this->a--; this->b--; return tmp; } void print() { cout << a << "+" << b << "i" << endl; } }; int main() { Complex t1(3, 4); Complex t2(5, 8); Complex t3(0, 0); t3 = t1 + t2; t3.print(); t3 = t1 - t2; t3.print(); ++t3; t3.print(); --t3; t3.print(); t3++; t3.print(); t3--; t3.print(); }
-
友元函数一般用于重载左移右移操作符,其他情况少用和慎用;
-
重载赋值运算符,先释放旧的内存,返回一个引用;
-
函数返回值当左值,需要返回一个引用;
-
Mystring类的实现
//Mystring.h #include <iostream> using namespace std; //c中没有字符串 字符串类(c风格的字符串) //空串 "" class MyString { friend ostream& operator<<(ostream &out, MyString &s); friend istream& operator>>(istream &in, MyString &s); public: MyString(int len = 0); MyString(const char *p); MyString(const MyString& s); ~MyString(); public: //重载=号操作符 MyString& operator=(const char *p); MyString& operator=(const MyString &s); char& operator[] (int index); public: //重载 == !== bool operator==(const char *p) const; bool operator==(const MyString& s) const; bool operator!=(const char *p) const; bool operator!=(const MyString& s) const; public: int operator<(const char *p); int operator>(const char *p); int operator<(const MyString& s); int operator>(const MyString& s); //把类的指针 露出来 public: char *c_str() { return m_p; } const char *c_str2() { return m_p; } int length() { return m_len; } private: int m_len; char *m_p; }; //main.cpp #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; #include "MyString.h" void main01() { MyString s1; MyString s2("s2"); MyString s2_2 = NULL; MyString s3 = s2; MyString s4 = "s4444444444"; //测试运算符重载 和 重载[] //= s4 = s2; s4 = "s2222"; s4[1] = '4'; printf("%c", s4[1]); cout<<s4 <<endl; //ostream& operator<<(ostream &out, MyString &s) //char& operator[] (int index) //MyString& operator=(const char *p); //MyString& operator=(const MyString &s); cout<<"hello..."<<endl; system("pause"); return ; } void main02() { MyString s1; MyString s2("s2"); MyString s3 = s2; if (s2 == "aa") { printf("相等"); } else { printf("不相等"); } if (s3 == s2) { printf("相等"); } else { printf("不相等"); } } void main03() { MyString s1; MyString s2("s2"); MyString s3 = s2; s3 = "aaa"; int tag = (s3 < "bbbb"); if (tag < 0 ) { printf("s3 小于 bbbb"); } else { printf("s3 大于 bbbb"); } MyString s4 = "aaaaffff"; strcpy(s4.c_str(), "aa111"); //MFC cout<<s4<<endl; } void main011() { MyString s1(128); cout<<"\n请输入字符串(回车结束)"; cin>>s1; cout<<s1; system("pause"); } void main() { MyString s1(128); cout<<"\n请输入字符串(回车结束)"; cin>>s1; cout<<s1<<endl; system("pause"); } //Mystring.cpp #define _CRT_SECURE_NO_WARNINGS #include "MyString.h" ostream& operator<<(ostream &out, MyString &s) { out<<s.m_p; return out; } istream& operator>>(istream &in, MyString &s) { cin>>s.m_p; return in; } MyString::MyString(int len) { if (len == 0) { m_len = 0; m_p = new char[m_len + 1]; strcpy(m_p, ""); } else { m_len = len; m_p = new char[m_len + 1]; memset(m_p, 0, m_len); } } MyString::MyString(const char *p) { if (p == NULL) { m_len = 0; m_p = new char[m_len + 1]; strcpy(m_p, ""); } else { m_len = strlen(p); m_p = new char[m_len + 1]; strcpy(m_p, p); } } //拷贝构造函数 //MyString s3 = s2; MyString::MyString(const MyString& s) { m_len = s.m_len; m_p = new char[m_len + 1]; strcpy(m_p, s.m_p); } MyString::~MyString() { if (m_p != NULL) { delete [] m_p; m_p = NULL; m_len = 0; } } s4 = "s2222"; MyString& MyString::operator=(const char *p) { //1 旧内存释放掉 if (m_p != NULL) { delete [] m_p; m_len = 0; } //2 根据p分配内存 if (p == NULL) { m_len = 0; m_p = new char[m_len + 1]; strcpy(m_p, ""); } else { m_len = strlen(p); m_p = new char[m_len + 1]; strcpy(m_p, p); } return *this; } // s4 = s2; MyString& MyString::operator=(const MyString &s) { //1 旧内存释放掉 if (m_p != NULL) { delete [] m_p; m_len = 0; } //2 根据s分配内存 m_len = s.m_len; m_p = new char[m_len + 1]; strcpy(m_p, s.m_p); return *this; } char& MyString::operator[] (int index) { return m_p[index]; } //if (s2 == "s222222") bool MyString::operator==(const char *p) const { if (p == NULL) { if (m_len == 0) { return true; } else { return false; } } else { if (m_len == strlen(p)) { return !strcmp(m_p, p); } else { return false; } } } bool MyString::operator!=(const char *p) const { return !(*this == p); } bool MyString::operator==(const MyString& s) const { if (m_len != s.m_len) { return false; } return !strcmp(m_p, s.m_p); } bool MyString::operator!=(const MyString& s) const { return !(*this == s); } //if (s3 < "bbbb") int MyString::operator<(const char *p) { return strcmp(this->m_p , p); } int MyString::operator>(const char *p) { return strcmp(p, this->m_p); } int MyString::operator<(const MyString& s) { return strcmp(this->m_p , s.m_p); } int MyString::operator>(const MyString& s) { return strcmp(s.m_p, m_p); }
-
类之间的关系:包含关系,组合关系,继承关系
-
子类拥有父类的所有成员变量和成员函数,子类可以拥有父类没有的方法和属性,子类对象可以当作父类对象使用,子类就是一种特殊的父类;
-
public继承:父类成员在子类中保持原有访问级别,private继承:父类成员在子类中变为private成员,protected继承:父类中public成员会变成protected,父类中protected成员仍然为protected,父类中private成员仍然为private;
-
private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员;
-
C++中的继承方式(public、private、protected)会影响子类的对外访问属性,判断某一句话,能否被访问
- 看调用语句,这句话写在子类的内部、外部
- 看子类如何从父类继承(public、private、protected)
- 看父类中的访问级别(public、private、protected)
-
恰当的使用public,protected和private为成员声明访问级别
- 需要被外界访问的成员直接设置为public
- 只能在当前类中访问的成员设置为private
- 只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间
-
public继承不会改变父类对外访问属性,private继承会改变父类对外访问属性为private,protected继承会部分改变父类对外访问属性;
-
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:
- 子类对象可以当作父类对象使用
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针和引用可以直接指向和引用子类对象
-
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化,在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理;
-
继承中的构造初始化列表的简化写法
#include <iostream> using namespace std; class A { private: int a; int b; public: A(int a=0, int b=0):a(a),b(b){} }; class B :public A { private: int c; public: B(int a, int b, int c) :A(1, 2),c(c){} }; int main() { A a1; B b1(2, 3, 4); }
-
继承中的构造析构调用原则:
- 子类对象在创建时会首先调用父类的构造函数
- 父类构造函数执行结束后,执行子类的构造函数
- 当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
- 析构函数调用的先后顺序与构造函数相反
-
继承与组合混搭情况下,构造和析构调用原则:先构造父类,再构造成员变量、最后构造自己,先析构自己,在析构成员变量、最后析构父类;
-
当子类成员变量与父类成员变量同名时子类依然从父类继承同名成员,成员在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符) ,同名成员存储在内存中的不同位置;
-
同名成员变量和成员函数通过作用域分辨符进行区分;
-
基类定义的静态成员,将被所有派生类共享,根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制),派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
-
一个类有多个直接基类的继承关系称为多继承, 多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n 基类名n { 数据成员和成员函数声明 };
-
多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员,执行顺序与单继承构造函数情况类似,多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序,一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别;
-
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性;
-
如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象,要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类,虚继承声明使用关键字virtual;
-
虚继承能解决一个祖宗类的多继承问题,不能解决不是一个祖宗类的多继承问题;
-
加上virtual关键字的类的大小会变大,实际上是加了一个指针;
-
面向对象新需求:根据实际的对象类型来判断重写函数的调用,如果父类指针指向的是父类对象则调用父类中定义的函数,如果父类指针指向的是子类对象则调用子类中定义的函数;
-
C++中通过virtual关键字对多态进行支持,使用virtual声明的函数被重写后即可展现多态特性,父类中virtual可写可不写,一般写上比较好,容易看;
-
多态成立的三个条件
- 要有继承
- 要有虚函数重写
- 用父类指针或引用指向子类对象
-
多态是设计模式和框架的基础;
-
联编是指一个程序模块、代码之间互相关联的过程;
-
静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配,重载函数使用静态联编;
-
动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定),switch 语句和 if 语句是动态联编的例子;
-
不写virtual关键字,是静态联编,加virtual关键字,动态联编;
-
虚析构函数
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //虚析构函数 class A { public: A() { p = new char[20]; strcpy(p, "obja"); printf("A()\n"); } virtual ~A() { delete [] p; printf("~A()\n"); } protected: private: char *p; }; class B : public A { public: B() { p = new char[20]; strcpy(p, "objb"); printf("B()\n"); } ~B() { delete [] p; printf("~B()\n"); } protected: private: char *p; }; class C : public B { public: C() { p = new char[20]; strcpy(p, "objc"); printf("C()\n"); } ~C() { delete [] p; printf("~C()\n"); } protected: private: char *p; }; //只执行了 父类的析构函数 //向通过父类指针 把 所有的子类对象的析构函数 都执行一遍 //向通过父类指针 释放所有的子类资源 void howtodelete(A *base) { delete base; //这句话不会表现成多态 这种属性 } /* void howtodelete(B *base) { delete base; //这句话不会表现成多态 这种属性 } */ void main() { C *myC = new C; //new delete匹配 // delete myC; //直接通过子类对象释放资源 不需要写virtual //howtodelete(myC); cout<<"hello..."<<endl; system("pause"); return ; }
-
通过子类对象的指针作函数参数传给父类指针,通过父类指针释放内存,需要对父类的析构函数加virtual关键字
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; //虚析构函数 class A { public: A() { p = new char[20]; strcpy(p, "obja"); printf("A()\n"); } virtual ~A() { delete [] p; printf("~A()\n"); } protected: private: char *p; }; class B : public A { public: B() { p = new char[20]; strcpy(p, "objb"); printf("B()\n"); } ~B() { delete [] p; printf("~B()\n"); } protected: private: char *p; }; class C : public B { public: C() { p = new char[20]; strcpy(p, "objc"); printf("C()\n"); } ~C() { delete [] p; printf("~C()\n"); } protected: private: char *p; }; //只执行了 父类的析构函数 //向通过父类指针 把 所有的子类对象的析构函数 都执行一遍 //向通过父类指针 释放所有的子类资源 void howtodelete(A *base) { delete base; //这句话不会表现成多态 这种属性 } /* void howtodelete(B *base) { delete base; //这句话不会表现成多态 这种属性 } */ void main() { C *myC = new C; //new delete匹配 // delete myC; //直接通过子类对象释放资源 不需要写virtual //howtodelete(myC); cout<<"hello..."<<endl; system("pause"); return ; }
-
函数重载:
- 必须在同一个类中进行
- 子类无法重载父类的函数,父类同名函数将被名称覆盖
- 重载是在编译期间根据参数类型和个数决定函数调用
-
函数重写:
- 必须发生于父类与子类之间
- 并且父类与子类中的函数必须有完全相同的原型
- 使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
-
多态是在运行期间根据具体对象的类型决定函数调用;
-
子类无法重载父类的函数,父类同名函数将被名称覆盖,无法再通过子类访问父类的函数,要调用需要显式调用,用子类.父类::函数名
-
C++中多态的实现原理:当类中声明虚函数时,编译器会生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,虚函数表是由编译器自动生成与维护的,virtual成员函数指针会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针);
#include <iostream> using namespace std; //多态成立的三个条件 //要有继承 虚函数重写 父类指针指向子类对象 class Parent { public: Parent(int a=0) { this->a = a; } virtual void print() //1 动手脚 写virtal关键字 会特殊处理 //虚函数表 { cout<<"我是爹"<<endl; } virtual void print2() //1 动手脚 写virtal关键字 会特殊处理 //虚函数表 { cout<<"我是爹"<<endl; } private: int a; }; class Child : public Parent { public: Child(int a = 0, int b=0):Parent(a) { this->b = b; } virtual void print() { cout<<"我是儿子"<<endl; } private: int b; }; void HowToPlay(Parent *base) { base->print(); //有多态发生 //2 动手脚 //效果:传来子类对 执行子类的print函数 传来父类对执行父类的print函数 //C++编译器根本不需要区分是子类对象 还是父类对象 //父类对象和子类对象分步有vptr指针 , ==>虚函数表===>函数的入口地址 //迟绑定 (运行时的时候,c++编译器才去判断) } void main01() { Parent p1; //3 动手脚 提前布局 //用类定义对象的时候 C++编译器会在对象中添加一个vptr指针 Child c1; //子类里面也有一个vptr指针 HowToPlay(&p1); HowToPlay(&c1); cout<<"hello..."<<endl; system("pause"); return ; }
-
证明vptr指针的存在
#include <iostream> using namespace std; class Parent1 { public: Parent1(int a=0) { this->a = a; } void print() { cout<<"我是爹"<<endl; } private: int a; }; class Parent2 { public: Parent2(int a=0) { this->a = a; } virtual void print() { cout<<"我是爹"<<endl; } private: int a; }; void main() { printf("sizeof(Parent):%d sizeof(Parent2):%d \n", sizeof(Parent1), sizeof(Parent2)); cout<<"hello..."<<endl; system("pause"); return ; }
-
构造函数中能调用虚函数不能实现多态,因为vptr指针分步初始化,当执行父类的构造函数时,c1.vptr指向父类的虚函数表,当父类的构造函数运行完毕后,才把c1.vptr指向子类的虚函数表;
-
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多,出于效率考虑,没有必要将所有成员函数都声明为虚函数;
-
父类指针的步长和子类指针的步长只有在子类没有定义新属性时是一样的,子类定义了新属性,步长就不一样;
#include <iostream> using namespace std; //结论: //多态是用父类指针指向子类对象 和 父类步长++,是两个不同的概念 class Parent { public: Parent(int a=0) { this->a = a; } virtual void print() { cout<<"我是爹"<<endl; } private: int a; }; //成功 ,一次偶然的成功 ,必然的失败更可怕 class Child : public Parent { public: /* Child(int a = 0, int b=0):Parent(a) { this->b = b; print(); } */ Child(int b = 0):Parent(0) { //this->b = b; } virtual void print() { cout<<"我是儿子"<<endl; } private: //int b; }; void HowToPlay(Parent *base) { base->print(); //有多态发生 //2 动手脚 } void main() { Child c1; //定义一个子类对象 ,在这个过程中,在父类构造函数中调用虚函数print 能发生多态吗? //c1.print(); Parent *pP = NULL; Child *pC = NULL; Child array[] = {Child(1), Child(2), Child(3)}; pP = array; pC = array; pP->print(); pC->print(); //多态发生 pP++; pC++; pP->print(); pC->print(); //多态发生 pP++; pC++; pP->print(); pC->print(); //多态发生 cout<<"hello..."<<endl; system("pause"); return ; }
-
函数指针
//定义一个函数类型 int add(int a, int b) { return a + b; } int main() { typedef int(fun)(int a, int b); fun *fun1 = &add; int c = fun1(2, 3); cout << c << endl; } //定义一个函数指针类型 #include <iostream> using namespace std; int add(int a, int b) { return a + b; } int main() { typedef int(*fun)(int a, int b); fun fun1 = &add; int c = fun1(2, 3); cout << c << endl; } //定义一个函数指针 #include <iostream> using namespace std; int add(int a, int b) { return a + b; } int main() { int(*fun)(int a, int b); fun = add; int c = fun(2, 3); cout << c << endl; } //函数指针类型的强制转换(c语言) #include <stdio.h> int add(int a, int b) { printf("%d", a + b); return 0; } int main() { void(*funa)(int a, int b); funa = (void(*)(int, int))add; funa(2, 3); }
-
函数指针和指针函数:
- 函数指针是指指向函数的指针
- 指针函数是指函数返回值是指针的函数
-
函数指针作函数参数
#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h> int myadd(int a, int b) //子任务的实现者 { printf("func add() do...\n"); return a + b; } int myadd2(int a, int b) //子任务的实现者 { printf("func add2() do...\n"); return a + b; } int myadd3(int a, int b) //子任务的实现者 { printf("func add3() do...\n"); return a + b; } int myadd4(int a, int b) //子任务的实现者 { printf("func add4() do...\n"); return a + b; } //定义了一个类型 typedef int (*MyTypeFuncAdd)(int a, int b); //函数指针 做 函数参数 int MainOp(MyTypeFuncAdd myFuncAdd) { int c = myFuncAdd(5, 6); return c; } // int (*MyPointerFuncAdd)(int a, int b) int MainOp2(int (*MyPointerFuncAdd)(int a, int b) ) { int c = MyPointerFuncAdd(5, 6); //间接调用 return c; } //间接调用 //任务的调用 和 任务的编写可以分开 void main() { /* MyTypeFuncAdd myFuncAdd = NULL; myadd(1, 2); //直接调用 myFuncAdd = myadd; myFuncAdd(3, 4); //间接调用 MainOp2(myadd); MainOp(myadd); */ //在mainop框架 没有发生任何变化的情况下 ... MainOp(myadd2); MainOp(myadd3); MainOp(myadd4); printf("hello...\n"); system("pause"); return ; }
-
函数指针的正向调用:被调用函数和主调函数不在同一个文件中、模块中;
-
函数指针的反向调用,回调函数;
C++复习
于 2020-12-02 15:16:47 首次发布