c编译阶段:只是给变量分配地址,地址中没有值,运行阶段时候才把值放进去,所以arr[a] 根本原因是c90标准不支持
再根本原因是a是个变量,到了c99就开始支持了。vs2013用的是c90标准 linux环境下是c99标准
而c++却可以
C++对c的拓展:
1域作用符:::a
有两个同名变量,一个是全局一个局部,局部变量在其作用域内有较高优先权,它屏蔽全局变量。
2命名空间:工程越大,名称互相冲突性的可能性越大 std::cout<<
无名命名空间,命名空间中的标识符只能在本文件内访问,相当于给这个标识符加static,使得作为内部连接
using 声明:可使得指定的标识符可用。using A::paramA;不常用
using 编译指令:使整个命名空间标识符可用 using namespace A;
使用 using 声明或 using 编译指令会增加命名冲突的可能性。也就是说,如果
有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。
3变量检测增强
4struct 类型加强
结构体中即可以定义成员变量,也可以定义成员函数
c++中定义结构体变量不需要加 struct 关键字
5C++中所有的变量和函数都必须有类型
C 中,int fun() 表示返回值int,接受任意参数的函数,int fun(void) 表示返回值为 int 的无参函数。
C++ 中,int fun() 和 int fun(void) 都表示返回值为 int 的无参函数。
6c语言的bool在头文件 stdbool.h中 而C++又bool类型
const:
c中必须得为const分配内存 而c++中不必
c++中const分配内存的4种情况:(分配内存意味着可能通过指针修改它)
1const接一个变量
2extern const定义成全局的。所有的全局const都不能修改。
3自定义的数据类型 eg一个结构体
4取地址时:const int a=10; 取a的地址,这是会在内存上生成一个伪a
内连外链:内连联想一下项目中的static
外连接:外部文件可以访问 两个c文件不能都定义同一个const int a
c中const是外链的,extern之后,外部文件只能访问。(不管外链还是内链都得用extern才能使用)
c++中const默认是内连的,本文件内都可用的。因为是内连所以只有加上extern后,外部也只能访问。
一般情况下const在使用时,都必须要初始化一个值。若不初始化则为0
c/c++中const区别:
也就是说只要const是全局的不管是c还是c++都只能有只读属性 因为放到只读数据段了
c中临时区的const 是一个伪的 你懂得
c++中是个真的
引用:引用是操作符重载的基础,引用初始化必须2行(除非 常引用const int &a=100);可以对数组引用(看不太懂);
引用一定要初始化,一旦初始化,就不能再次初始化(只能是一个空间的别名)。只能给他赋值。
1.引用就是给内存取别名(不同的门牌号) 前提必须是内存分配了
2.引用必须要赋值才能用,(必须两句话组成,一定要有内存)就像用const定义一个常量一样必须要赋值(也没有内存,放一个表中)
3.所以引用很像一个常量
4.定义一个 int &a; 占4个字节 很像指针所占内存大小 即使是double int a仍占4个字节
5.由上可得:常量+指针:int * const a。就是 int &a。*a=5就是 a=5
使用时:
1.用变量来接非常简单。但是用引用来接 首先要注意:这个变量在哪里?是栈还是静态区?
2.返回值是引用,返回的是一个内存空间(而通常返回值是一个常量)。
案例:
参数:两个数交换;不用初始化
返回值:引用接栈上返回的引用会出现问题,除非返回静态的;变量接栈上返回的引用就没有问题;(谁接和返回谁的问题)
引用既可以当左值又可以当右值:若想让返回值当左值就返回引用。(操作符重载的来源)(链式编程的来源)
注意返回const int a和static int a 引用的区别????????????????????
其实不是常指针,大家都是这么认为的。占不占空间,编译器说的算。
常引用:
主要用在函数参数。不能通过常引用修改原变量的值。
目的:希望在被调函数中,不要改变原变量的值。而且const传递效率高,尤其是拷贝构造函数。
常引用可以值传递。因为它的初始化可以简化成1行的原因:字面量不能赋给引用,但是可以赋给 const 引用 即其初始化一行就够了
引用没有定义(内存?),是一种关系型声明。声明它和原有某一变量(实体)的关系。
故 而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址;引用在声明的时候必须初始化,并且一经声明,不可变更。
宏的副作用:没有范围(作用域);只是替换;
内联函数:空间换时间
既继承了宏函数的效率,没有函数调用的开销(高效)。内联函数象宏一样展开,不需函数调用开销(压栈跳转) 空间换时间
又象普通函数那样可以进行参数,返回值的安全检查(类型)。又可以作为成员函数(作用域)。
宏的副作用太多(笔试),不如函数。函数要想在类中使用(有作用域)就得用内联函数
类内部的函数都是内联函数,默认不写inline。
简单的函数编译器可能都会把它内联编译。 编译器决定是否内联编译
函数的默认参数:普通的思维想想。
主调函数写参数时可以不写全。
注意1:若参数的前面有默认值,其后面的参数必须有默认值。
注意2:函数声明和函数实现不能同时写默认参数(否则用个参数,不知道)
占位参数:
只有类型,没有变量名,只是占位作用。但主调函数必须要给它一个值。
也可以在参数位置赋个值 即类型=变量值(int=20),这样主调函数就可以不用给它一个值了。
(主要用于操作符重载时 区别前置++后置++)
重载:
为什么返回值不能作为条件?因为有些时候根本用不上返回值。编译器懵了,到底调哪个函数。
重载时 函数中最好不要写默认参数,会出现二义性问题。eg:void fun(int a, int b); void fun(int a, int b, int c = 0);主函数调fun(1,2)时就不知道调哪个了
char,int的问题:先找完全匹配的,找不到就类型转换。
编译看.s文件 重载函数被编译成了不同的函数。 无法连接到外部符号。
const也可以为重载的条件 do(const Teacher &t){} do(Teacher &t){}
C++中调用c:
同样的函数fun。c c++编译的结果都不一样。所以要想在C++ extern "C" void +函数名
告诉编译器以c语言的形式链接。或写成
#ifdef __cplusplus
extern "C"{函数或写成.c的头文件}
#endif
构造函数调用方法(获得一个新对象的3种方法):
1无参构造函数调用方法
2有参构造函数调用方法:
a括号法(包括有参、拷贝):(常用Test t1(1, 2); Test t1(t2)会调拷贝构造) 显示调用
c等号法(旧对象等号给新对象):Test t1=t2会调拷贝构造 隐式调用(会进行隐式转换,所以不常用)
还有一种等号法:只能针对一个参数的构造函数来使用调用法)Test2 t2=(1,2,3,4,5,5);
b匿名对象(最麻烦的那种):(Test(1, 2);匿名对象没有名字,没有接则立即析构)
Test t2=Test(10);或 Test t2=Test(t1) 类似这种形式的;
前者不会调用拷贝构造函数 后者会调用拷贝构造函数 而Test t2=Test(t1)又通常等价于Test t2=t1;
这就是匿名函数里放值和放对象的区别.
由于是对象的初始化接所以两者都不会调用析构函数
t 为 test 的实例化对象,test a = test(t) 和 test(t)的区别?
当 test(t) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时
候,编译器认为你 test(t) 等价于 test t.
Test t2 = t1;第一反应 只调用拷贝 不调用析构;第二反应 Test t2=Test(t1)两者等价
只有匿名的那种形式才会析构 而匿名非常容易分辨出来
return t;第一反应,包含两个步骤:立即调用拷贝生成一个匿名 析构t
拷贝构造函数调用的3个时机:
1实参初始化形参会调用拷贝构造函数(值传递)
2一个对象初始化另一个对象。Test t6 = t1,(Test t6(t1))或 Test t2=Test(t1);
3函数return 返回局部对象(debug模式,不会被优化,会调用拷贝构造)而release模式下却不同(优化,不拷贝(拷贝耗时),用同一个地址)。其编译的本质是用引用的概念。
类是一个复杂的东西,我们称之为元素。返回值是一个类,比较复杂,寄存器也保存不了所以用拷贝构造函数拷贝出一份内存出来,
这个内存就是匿名对象。此时原来的对象被析构
返回一个匿名对象:没人接 则匿名对象被析构
对象的初始化接:匿名转有名。
对象接:赋值后,匿名立即析构。
Teacher t1;
Teacher(t1); //此时等价于 Teacher t1; //error C2086:“ Teacher t1” : 重定义
构造函数调用规则:
1如果自己写了拷贝构造函数 则其他函数一定要手写。
2若自己写了构造函数,不写拷贝构造,则编译器提供出一个拷贝构造函数。
3拷贝构造函数的参数是引用 还是有道理的
4若写了构造函数,编译器不提供无参构造函数。
5只要写了,一定得调用。
浅拷贝:成员变量有char * 就会出现浅拷贝问题。等号法初始化时候,若不自己写拷贝构造,默认用编译器提供的,则出现浅拷贝问题。
解决方法:自己写拷贝构造,里面分配内存。
explicit:针对单参数的构造函数 防止隐式转换
explicit MyString(int n)
只能MyString str2(10);而MyString str2=10;是错的
MyString str2(‘a’);也是错的
初始化列表:有自己写的构造函数
eg1:
Parent类只有ab成员,Child类有c成员 父类构造很简单不再赘述,
但子类构造:Child(int a, int b, int c) :Parent(a,b)
eg2:
Object类只有ab成员,其构造的高级写法:
Object(int _a,int _b):a(_a),b(_b){}
child继承与Object 只有成员char*p,其构造:
Parent(char * p) :Object(1,2)
一个类调用了其他类,这个类成为类对象成员,若被调用类里有自己写的构造函数,则一定要写初始化列表
继承相似,如果另一个类中有自定义的构造函数,那么
写子类函数的时得初始化成员列表。只写父类的初始化。每一次继承,子类中都必须写初始化(基类的初始化)语句。
构造函数调用顺序是根据成员的先后顺序调用的:
初始化成员列表三种情况:
1const常量成员,因为常量只能初始化,不能赋值,所以必须放在初始化列表中;
2引用类型的成员,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表中;
3成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成
员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败
const 成员:在初始化列表初始化
static 成员:在类外初始化,初始化时,不需要static关键字 eg:int Person::sNum = 0;
静态成员可以在外部初始化的原因:静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。
因为静态共享所以不管它是public还是private,都可以在外部用域作用符初始化。但是 静态成员也有访问权限,类外不能访问私有成员
即:静态属性初始化没有权限,访问会有权限
const static 既要实现共享,又要实现不可改变 在本地初始化
有了const就得把初始化放内部(因为是const所以定义时得初始化)。
void sonmeOperate() const{ 在函数括号后面加上 const,修饰成员变量不可修改,除了 mutable 变量
//this->mAge = 200; //mAge 不可修改 因为mutable int mID;
this->mID = 10;
}
访问控制:
有三种继承,可以随便继承,继承前后属性会发生改变。
除了私有的,都能(在子类内部访问)。而在类的外部(main中) 只能访问public属性的变量
private:private继承后,成员和成员函数只能在类内部使用
protected:protect继承以后只在类的内部使用 在继承的子类中可用
class不写 是私有 而struct默认是public。
this指针:
C++类对象中的变量和函数是分开存储。这是因为this指针
每个构造函数都会隐含了一个this指针,指向不同的对象。即不同的对象有他们自己的this指针。
全局函数和成员函数的转变:返回元素会拷贝成匿名对象 而 返回一个引用相当于返回自身(内存首地址)返回的是t1这个对象!!!!!!!!!!
全局函数转成员函数 少了一个参数 体现了面向对象思想 很方便
友元函数:友元函数声明的位置和public private没有关系;函数是类A的好朋友,可以通过函数访问类A中的所有属性,
破坏封装不好。友元函数不是类的成员没有this指针。
友元类,可以省去写初始化列表
友元函数 至少包含类指针(或引用)和一个值
友元主要用在:C++库函数的操作符重载,而且必须是全局函数的方式;而其它操作符的重载可以用全局函数法或者成员函数法
1友元关系不能被继承。
2友元关系是单向的 不具有传递性
友元+引用时重载c库函数符的必要条件。
运算符重载:编译器应该提供一种机制 让用户有机会最自定义数据类型 有机会进行运算符操作
全局函数法的固定3步骤:
第一步 定义一个全局函数
第二步 改造函数名(非常单纯的改名字)
第三步 全局函数变友元(可省一个参数)
或者直接写成成员函数法,就不用写友元函数了,封装性依然很好。
一般情况下:参数是引用(效率高);重载C++库函数时参数必须用引用。
总结:运算符重载本质是一个函数 就像上面的一样
前置++:++当左值,所以返回引用。重载<<也是
new和delete运算符(运算符高函数一个档次)
1主函数中new一个对象,若创建成功则自动调用构造函数(而malloc不会自动调用),完成初始化(分配内存)。
delete自动调用析构函数 而且new完之后不用判断返回值 而malloc时还得判断返回值。
即:构造里仍用malloc+strcpy 析构里仍又写了一遍delete
2new自定义对象数组,必须提供无参构造函数(malloc+strcpy)。注意数组的delete[]格式。
new定义一般数组时,无需写构造函数。貌似是一句废话。
3不要delete void * 类型,将不会自动调用析构函数,会造成内存减少。
通常new写主调函数中后面加个括号,进行初始化。
delete 只适用于由 new 创建的对象。如果正在删除的对象的指针是 NULL,将不发生任何事
单例模式:
只有一个对象+一个接口 (恶汉式) 确保只有一个实例(任务管理器)
1首先定义静态变量(!!!!不是类而是类指针),内部定义外部new一个类。
要声明成私有的(外面不能直接访问到它,只能通过函数(静态)访问它)main之前在编译阶段就分配好了
2 静态成员函数不能访问非静态的成员(因为对象还没有实例化) 静态函数只能调用静态变量,所以函数只能定义成public类型的
3私有化 构造,拷贝构造函数,保证外部无法创建对象。
条件中不等于 即可等于!
象重载左移等C库定义的操作符 只能通过友元 写成全局函数的形式。
参数传递最好用引用,尤其是C库函数它不允许外界拷贝,一定得用引用。
重载是依据 你写的格式(参数,返回值)而写的,而非凭空写一个重载。
最好返回引用。
后置加加a++:
按道理可以返回元素也可以返回引用。但是按照后置加加的特性,它得返回出来一个临时值,
(有一条:不能返回栈上的引用)所以返回元素,用元素来接。
编译器默认提供4个构造函数:别忘了等号那个函数
针对=赋值与拷贝构造的区别:
一般情况下,有参构造函数中有new+strcpy。若用默认的等号赋值操作,则运行时会down掉(重复释放);
若自己只写一个拷贝构造函数,也是然并卵,因为根本不调用它;因为形式是str1=str2;而不是Str str3=str1。
所以,针对=赋值操作情况下:得重载=操作符。先释放=左边(this)的空间,防止内存泄露(越用越少)。
返回值是引用,参数也是引用。
参数是引用的原因是:
如果写成非引用的形式,则实参初始化形参调用构造函数(而若此时没有自己写拷贝构造函数,则又会出现down的问题),
当分析程序的时候造成麻烦。而且用引用不在创建新的对象,效率高。
返回值是引用的原因同上 不仅效率高,而且返回的是本身*this 还支持链式编程
重载&&就变成函数调用了,执行顺序改变了。不建议重载。
注意重载赋值运算符和[],(),->运算符必须定义为类的成员函数。赋值函数是构造函数的重载,不是普通的成员函数,不能用对象来调用
重载等号操作符,什么时候返回引用什么时候返回类????
str1=str2;
Str str3=str1+str2;
从上式子可得:返回本身,则写成引用;返回一个新的则写成一个对象。想想后置++也是这样。
当返回一个对象时,有个明显的特征:就是会有一个新的对象去接它,如上Str str3去接str1+str2一样。
结论:参数,返回值尽量用引用。
继承:
等号,析构,构造不能被继承。
子类的构造与析构都需要先调用父类函数 先完成爹的初始化 再完成儿子的初始化
继承与一个类中包含了另一个类一样,如果另一个类中有自定义的构造函数,那么写子类函数的时得初始化成员列表。只写父类的初始化。
每一次继承,子类中都必须写初始化(基类的初始化)语句。
重载、重写、重定义:
重载:同一作用域的同名函数
重定义(默认有继承):即隐藏,有继承,子类重新定义父类的同名函数。
重写(默认有继承和virtual):即覆盖,由于用了virtual覆盖掉基类的函数。而之前上面的重定义是隐藏,还可以通过
重定义就是同名也就是说 可以是同名同参 也可以是同名不同参
重写就是同名同参
重写 函数一模一样的函数 1虚函数重写(发生多态) 2非虚函数重写(重定义)
基类,子类中有同名的属性:能继承 就近原则 通过域作用符访问(隐藏)
多继承菱形问题:
B-B1-C c中的b到底是从B1继承过来的还是从B2继承过来的?出现了二义性问题。
\ /
B2
解决方法:
B1,B2继承时,加上virtual虚继承(虚基类)。虚继承能解决共同老祖先的问题。占内存大小为:只用一份(老祖先)数据+2个vBptr指针
也可以写域作用符:此方法不好,用工具看 有好几份同样的数据 浪费空间
面向对象三大概念:
封装:(可重用,可扩充;一个接口不同的实现功能;隐蔽:定义与实现分离)可接隐
封装,继承和多态。在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,
它不仅支持系统的可重用性,而且还有利于提高系统的可扩充性;
消息传递可以实现发送一个通用的消息而调用不同的方法;
封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。
.可以跟属性和函数 这就不是面向过程
继承:编译;运行;接口;重写
继承是在编译时刻静态定义的, 类继承可以较方便地改变父类的实现.
继承在编译时刻就定义了,无法在运行时改变从父类继承的实现。
父类通常至少定义了子类的部分行为(接口),父类的任何改变都可能影响子类的行为。
如果继承下来的实现不适合解决新的问题, 则父类必须重写或被其他更适合的类替换。
这种依赖关系限制了灵活性并最终限制了复用性。 所以产生了虚函数重写发生多态
多态:(提迟)
1提前布局,vptr指针指向各自对象的虚函数表
2迟邦定 运行时候绑定 模板才是编译时绑定
早绑定指在对象申明的时候就和他的类型建立了关联。
晚绑定是指我们的代码在运行时再检查对象是否提供了我们所需要的方法和属性。
开闭原则:对修改关闭,对扩展开放。通过增加代码来增加新功能而非修改源代码
迟邦定原因(什么是virtual):
向上类型转换:若没有写virtual,传什么类型(指针),调用什么类型(不关心基类指针定义什么对象)。
即:定义基类指针则调基类函数 定义子类指针调子类函数
向上类型转换的原因是:c语言的早绑定造成的,解决方法就是C++中的迟绑定即virtual
即:用基类指针定义子类对象 而不调用基类中的函数 调用子类对象的函数
重写:即覆盖,由于用了virtual覆盖掉基类的函数。而之前上面的重定义是隐藏,还可以通过::访问
只要调用构造函数,就会开辟内存空间,就会默默的加上vptr指针指向各自对象的虚函数表(vptr指针与函数地址重叠,vptr就是函数地址),
。虚函数表中的入口地址存放的是各个不同的实现函数(建议看PDF)
纯虚函数:子类必须得实现它
抽象类(有纯虚函数的类)不能被实例化,即不能这样:Figer f;
但是可以:Figure * base = NULL;即:可定义指针(基类) 接口的精髓所在!可指向内部虚函数
模板方法模式:(顾名思义,模板即具体的步骤)
主要用纯虚函数类
茶和咖啡模式:纯虚函数中写4个步骤。(抽象出过程) 写出make()模板方法
再继承下来,分别写茶和咖啡类的具体步骤。(具体步骤)
调用时:定义指针指向其内部的make()
先构造父类,再构造成员变量、最后构造自己;
如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为抽象类。
Switch语句和if语句都属于动态联编。
当父类中存在虚函数,则会生成一个虚函数表,同时存在一个vptr指针,指向虚函数表,当有子类继承此父类时,
子类也会生成一个虚函数表和vptr指针,vptr指针指向子类的虚函数表。如果子类不虚函数重写它,子类的成员函数一直是基类的成员函数。
纯虚函数没有函数体 虚函数有函数体。c++中的接口,可通过纯虚函数实现
接口:公共的,抽象的 就是步骤 就是纯虚的
纯虚函数约定了一个接口,要求其子函数必须实现
面向抽象类编程 面向一套预先定义好的接口编程 解耦合 模块的划分
虚析构函数:当做一个普通的虚函数,实现多态。
纯虚析构函数:父类中声明,类外定义。仍不能实例化。
面向接口编程:
接口:公共的,抽象的。c中的回调函数和c++中的纯虚函数恰好迎合了这种机制
(回调函数规定了函数的参数,返回值格式)
(纯虚函数,只定义接口,实现交给厂商)
经典的 发送,接受函数的参数形式:
项目:
用C++写:
我是中标厂商 用C++写:自带耦合属性
纯虚函数法写时:在完成纯虚函数子函数具体化后,main中定义纯虚函数类指针和一些其他参数
根据主厂商提供测试框架(传递纯虚函数类指针 和 一些其它参数)这个框架的目的是:把主厂商的接口 与 具体的厂商函数粘结在一起),
把指针传和其他参数传递过去。
用C写: (有点用struct指针模拟纯虚函数类指针的味道)
主厂商定义了typedef的回调函数接口(抽象层),
写了一个结构体(确定回调函数的函数名)
又写了一个初始化框架(传递结构体指针 和 回调函数)把厂商函数与主厂商函数耦合 即具体化结构体中的函数
主函数中又写一个测试框架(传递结构体指针 和 一些其他参数 进行具体数据的传递)
总结: 即定义结构体->具体化结构体->传递给结构体参数
厂商根据回调函数写具体的函数
在main中 定义结构体->初始化耦合->测试框架
加密:需要4个参数:待加密字符串(主函数分配内存),长度,加密的字符串(主函数分配内存),长度(int *)
解密:同上。
套路是固定的:定义结构体;耦合;测试框架;
记住是发送前加密。接受后解密。
模板即泛型:
函数的业务逻辑一样 但函数参数类型不一样 有一种机制 让类型参数化 方便编程 就是泛型编程(模板机制)
函数模板:
template<typename T>//告诉编译器要进行泛型编程了 看见T不要报错
void myswap(T &a,T &b){
T c;
c = a;
a = b;
b = c;
cout << "我是模板函数" << endl;}
这样调用:
int x = 10;int y = 20;
myswap<int>(x, y);//自动类型推导的写法myswap(x, y);
一般情况下,使用自动推到,但得严格的类型匹配
函数模板可以使用自动类型推到,类模板不行
eg:有函数: T func(T x, T y)
这样调用是可以的:func<int>(3, 2.5); 而func(3, 2.5);这样就不行
调用函数模板 本质是类型参数化 将严格的按照类型进行匹配 不会进行自动类型转换,
而普通函数调用编译器会自动的转换
函数模板像普通函数一样可以被重载
当都符合条件时候 普通函数优先于函数模板
如果函数模板能产生更好的匹配就用函数模板
都符合时 若强烈想使用函数模板加空<>函数类型链表
两次编译:
看汇编 编译器根据<>内部的类型 生成具体的函数
两次编译原理机制
编译器进行了两次编译:第一次编译对模板语法编译,第二次根据调用产生具体的函数(有内存了)类型
根据类型生成具体函数,若类型相同则第一次编译的时候,只生成一个函数。
模板类:
template <typename T>//告诉编译器要泛型编程了 出现T不要报错
class A{
public:
A(T a = 0)//A(int a = 0){
this->a = a;}
void printA(){
cout << "a:" << a << endl;}
protected:
T a;
private:};
继承:class B :public A<int> 得额外加上<type>确定内存大小
类中的构造:B(int a = 10, int b = 20) :A<int>(a)
模板类派生 也需要具体化模板类 要知道父类占的内存大小是多少 只有数据类型固定下来才能分配内存
模板类定义时:在main()中定义时必须加上<>并具体化,并不能自动类型推到
模板类做函数参数时:只能传具体的。形参必具体才能分配内存,同定义。
void DoBussiness1(Person<string,int>&p)
也可以用函数模板嵌套类模板,不需要具体化
template<class T1,class T2>
void DoBussiness2(Person<T1, T2>& person){
cout << "Name:" << person.mName << " Age:" << person.mAge << endl;
}
普通类继承一个模板类 即模板类派生普通类需要具体化。只能写具体的int long double .....
而模板类派生模板类 可以具体化为基类的T 也可以具体化为一个具体的(看老王的代码)
不管是模板类派生普通类 还是 模板类派生模板类 都不能继承一个类模板。
模板类里的友元重载时候若其实现的函数在类内部,可以不具体化模板参数T,也不用加template
模板类的一般成员或友元函数具体实现写外面:以前是写成:Person::Person()的形式
现在得写成:Person<T>::Person(),把类模板具体化。而T从Template<typedef T>处来,得加上template
所以说 写外部非常麻烦,还是写内部好
无法解析的外部符号:编译无错误,链接时候错误。(想起c ,c++互不通用)。
所以:左移的友元,
函数外边写(注意是写在函数外部)的是函数模板,所以类的内部也得写成模板:
即:函数前一句和函数里都写template,就不会出现“无法解析的外部符号”报错
比较麻烦......
当分文件编写时
二次编译:类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
和编译文件有关,和二次编译有关。编译器一个一个cpp文件编译的。当编译模板的cpp时,没有生成具体的函数,
因为当前文件中你没哟用。
main.cpp也能编译通过。 最后连接时:由于编译模板的cpp没有生成具体函数 所以无法连接的外部错误
所以:别分文件,建一个.hpp文件 里面写类模板 和 具体的函数。
小案例:
为什么数组类的拷贝构造里 拷贝临时长度而非总长度?
定义一个数组类 必手动提供默认构造函数
类的属性只要有指针 先写默认构造,在里面分配内存;再写有参构造函数分配多少内存;
然后立马写重载拷贝构造和重载等号操作符
只要有指针 重载等号少不了,拷贝构造少不了。
***********************
为甚么看到类成员函数中有* 就烦
因为所有函数都得写一遍:默认,构造,析构,拷贝,=重载
***********************
静态转换static_cast:用于父子类指针或者引用 和 基础类型转换
dynamic_cast只能将子类指针转为父类指针或者引用。
总之:static不安全 不建议用
static_cast为静态类型转换,在编译期间就可以决定其类型的转换。
从类模板实例化的每一个模板类有自己的类模板数据成员, 该模板的所有对象共享一个 static 数据成员
模板函数的真正代码是在(运行执行函数时)时产生的。
new的用法
之前的用法有:
类中私有成员是数组的三要素:指针,长度,容量。
默认构造函数里new了20个int;有参构造函数中也有new。只是纯数组的增添改删。没有拷贝构造,重载什么的
实际的用法是 :main中new一个对象(可以写成有参(最好) 可以调无参) 别忘最后delete
今天的用法是:类模板
异常:
c中 必须逐层的return数据,判断是否错误。还得约定好
c++中,忽略返回值,返回值无意义,异常可以跳级
返回一个值有很大的缺陷性,这个值可能错误码等于了计算值
catch(...)捕获所有异常
栈解锁:针对的是类 抛出异常时,调用析构
这就是异常接口声明:
//void myDivide() throw(int,char,const char *)//可以扔出int char char *类型的异常
//void myDivide() //可以抛出任何类型异常
//void myDivide() throw()//不抛出异常 (跟编译器有关) 。。。这就是异常接口声明
抛出一个类时,case中用引用来接(可以省了写拷贝构造,而且调试起来比较方便)。
不要抛指针 要是抛的话最好抛new出来的指针。
若有相似的业务 保持高度警惕!用多态!
第一份工资决定以后的公司。大公司好,技术能力强的人多。
自己琢磨点东西去写, 不要模仿老师的思路。
经验:出现问题,解决问题
写。
写自己的异常库:写一个类继承c++库异常。而这个类的(构造函数)作用是:
1.把抛出异常的字符串信息接住。
2.重载父类的what和虚析构函数
输入输出流:
流:<=> 即是流
cin.get()从缓冲区拿‘走’字符。包括回车
cin.get(buf,1024);一次读一行 回车键读到缓冲区
cin.getline(buf,1024);一次读一行,回车换行不能读到缓冲区
但两者显示的是相同的。
cin.putback(ch)把ch放到被cin.get()拿走的缓冲区。
一旦有深拷贝浅拷贝问题,不仅要修改拷贝构造函数而且还得重载等号操作符
构造函数初始化列表:一点都不难,就是不好记,就是一个类中的成员有另一个类
面向对象模型初探 没看懂
再根本原因是a是个变量,到了c99就开始支持了。vs2013用的是c90标准 linux环境下是c99标准
而c++却可以
C++对c的拓展:
1域作用符:::a
有两个同名变量,一个是全局一个局部,局部变量在其作用域内有较高优先权,它屏蔽全局变量。
2命名空间:工程越大,名称互相冲突性的可能性越大 std::cout<<
无名命名空间,命名空间中的标识符只能在本文件内访问,相当于给这个标识符加static,使得作为内部连接
using 声明:可使得指定的标识符可用。using A::paramA;不常用
using 编译指令:使整个命名空间标识符可用 using namespace A;
使用 using 声明或 using 编译指令会增加命名冲突的可能性。也就是说,如果
有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。
3变量检测增强
4struct 类型加强
结构体中即可以定义成员变量,也可以定义成员函数
c++中定义结构体变量不需要加 struct 关键字
5C++中所有的变量和函数都必须有类型
C 中,int fun() 表示返回值int,接受任意参数的函数,int fun(void) 表示返回值为 int 的无参函数。
C++ 中,int fun() 和 int fun(void) 都表示返回值为 int 的无参函数。
6c语言的bool在头文件 stdbool.h中 而C++又bool类型
const:
c中必须得为const分配内存 而c++中不必
c++中const分配内存的4种情况:(分配内存意味着可能通过指针修改它)
1const接一个变量
2extern const定义成全局的。所有的全局const都不能修改。
3自定义的数据类型 eg一个结构体
4取地址时:const int a=10; 取a的地址,这是会在内存上生成一个伪a
内连外链:内连联想一下项目中的static
外连接:外部文件可以访问 两个c文件不能都定义同一个const int a
c中const是外链的,extern之后,外部文件只能访问。(不管外链还是内链都得用extern才能使用)
c++中const默认是内连的,本文件内都可用的。因为是内连所以只有加上extern后,外部也只能访问。
一般情况下const在使用时,都必须要初始化一个值。若不初始化则为0
c/c++中const区别:
也就是说只要const是全局的不管是c还是c++都只能有只读属性 因为放到只读数据段了
c中临时区的const 是一个伪的 你懂得
c++中是个真的
引用:引用是操作符重载的基础,引用初始化必须2行(除非 常引用const int &a=100);可以对数组引用(看不太懂);
引用一定要初始化,一旦初始化,就不能再次初始化(只能是一个空间的别名)。只能给他赋值。
1.引用就是给内存取别名(不同的门牌号) 前提必须是内存分配了
2.引用必须要赋值才能用,(必须两句话组成,一定要有内存)就像用const定义一个常量一样必须要赋值(也没有内存,放一个表中)
3.所以引用很像一个常量
4.定义一个 int &a; 占4个字节 很像指针所占内存大小 即使是double int a仍占4个字节
5.由上可得:常量+指针:int * const a。就是 int &a。*a=5就是 a=5
使用时:
1.用变量来接非常简单。但是用引用来接 首先要注意:这个变量在哪里?是栈还是静态区?
2.返回值是引用,返回的是一个内存空间(而通常返回值是一个常量)。
案例:
参数:两个数交换;不用初始化
返回值:引用接栈上返回的引用会出现问题,除非返回静态的;变量接栈上返回的引用就没有问题;(谁接和返回谁的问题)
引用既可以当左值又可以当右值:若想让返回值当左值就返回引用。(操作符重载的来源)(链式编程的来源)
注意返回const int a和static int a 引用的区别????????????????????
其实不是常指针,大家都是这么认为的。占不占空间,编译器说的算。
常引用:
主要用在函数参数。不能通过常引用修改原变量的值。
目的:希望在被调函数中,不要改变原变量的值。而且const传递效率高,尤其是拷贝构造函数。
常引用可以值传递。因为它的初始化可以简化成1行的原因:字面量不能赋给引用,但是可以赋给 const 引用 即其初始化一行就够了
引用没有定义(内存?),是一种关系型声明。声明它和原有某一变量(实体)的关系。
故 而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址;引用在声明的时候必须初始化,并且一经声明,不可变更。
宏的副作用:没有范围(作用域);只是替换;
内联函数:空间换时间
既继承了宏函数的效率,没有函数调用的开销(高效)。内联函数象宏一样展开,不需函数调用开销(压栈跳转) 空间换时间
又象普通函数那样可以进行参数,返回值的安全检查(类型)。又可以作为成员函数(作用域)。
宏的副作用太多(笔试),不如函数。函数要想在类中使用(有作用域)就得用内联函数
类内部的函数都是内联函数,默认不写inline。
简单的函数编译器可能都会把它内联编译。 编译器决定是否内联编译
函数的默认参数:普通的思维想想。
主调函数写参数时可以不写全。
注意1:若参数的前面有默认值,其后面的参数必须有默认值。
注意2:函数声明和函数实现不能同时写默认参数(否则用个参数,不知道)
占位参数:
只有类型,没有变量名,只是占位作用。但主调函数必须要给它一个值。
也可以在参数位置赋个值 即类型=变量值(int=20),这样主调函数就可以不用给它一个值了。
(主要用于操作符重载时 区别前置++后置++)
重载:
为什么返回值不能作为条件?因为有些时候根本用不上返回值。编译器懵了,到底调哪个函数。
重载时 函数中最好不要写默认参数,会出现二义性问题。eg:void fun(int a, int b); void fun(int a, int b, int c = 0);主函数调fun(1,2)时就不知道调哪个了
char,int的问题:先找完全匹配的,找不到就类型转换。
编译看.s文件 重载函数被编译成了不同的函数。 无法连接到外部符号。
const也可以为重载的条件 do(const Teacher &t){} do(Teacher &t){}
C++中调用c:
同样的函数fun。c c++编译的结果都不一样。所以要想在C++ extern "C" void +函数名
告诉编译器以c语言的形式链接。或写成
#ifdef __cplusplus
extern "C"{函数或写成.c的头文件}
#endif
构造函数调用方法(获得一个新对象的3种方法):
1无参构造函数调用方法
2有参构造函数调用方法:
a括号法(包括有参、拷贝):(常用Test t1(1, 2); Test t1(t2)会调拷贝构造) 显示调用
c等号法(旧对象等号给新对象):Test t1=t2会调拷贝构造 隐式调用(会进行隐式转换,所以不常用)
还有一种等号法:只能针对一个参数的构造函数来使用调用法)Test2 t2=(1,2,3,4,5,5);
b匿名对象(最麻烦的那种):(Test(1, 2);匿名对象没有名字,没有接则立即析构)
Test t2=Test(10);或 Test t2=Test(t1) 类似这种形式的;
前者不会调用拷贝构造函数 后者会调用拷贝构造函数 而Test t2=Test(t1)又通常等价于Test t2=t1;
这就是匿名函数里放值和放对象的区别.
由于是对象的初始化接所以两者都不会调用析构函数
t 为 test 的实例化对象,test a = test(t) 和 test(t)的区别?
当 test(t) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时
候,编译器认为你 test(t) 等价于 test t.
Test t2 = t1;第一反应 只调用拷贝 不调用析构;第二反应 Test t2=Test(t1)两者等价
只有匿名的那种形式才会析构 而匿名非常容易分辨出来
return t;第一反应,包含两个步骤:立即调用拷贝生成一个匿名 析构t
拷贝构造函数调用的3个时机:
1实参初始化形参会调用拷贝构造函数(值传递)
2一个对象初始化另一个对象。Test t6 = t1,(Test t6(t1))或 Test t2=Test(t1);
3函数return 返回局部对象(debug模式,不会被优化,会调用拷贝构造)而release模式下却不同(优化,不拷贝(拷贝耗时),用同一个地址)。其编译的本质是用引用的概念。
类是一个复杂的东西,我们称之为元素。返回值是一个类,比较复杂,寄存器也保存不了所以用拷贝构造函数拷贝出一份内存出来,
这个内存就是匿名对象。此时原来的对象被析构
返回一个匿名对象:没人接 则匿名对象被析构
对象的初始化接:匿名转有名。
对象接:赋值后,匿名立即析构。
Teacher t1;
Teacher(t1); //此时等价于 Teacher t1; //error C2086:“ Teacher t1” : 重定义
构造函数调用规则:
1如果自己写了拷贝构造函数 则其他函数一定要手写。
2若自己写了构造函数,不写拷贝构造,则编译器提供出一个拷贝构造函数。
3拷贝构造函数的参数是引用 还是有道理的
4若写了构造函数,编译器不提供无参构造函数。
5只要写了,一定得调用。
浅拷贝:成员变量有char * 就会出现浅拷贝问题。等号法初始化时候,若不自己写拷贝构造,默认用编译器提供的,则出现浅拷贝问题。
解决方法:自己写拷贝构造,里面分配内存。
explicit:针对单参数的构造函数 防止隐式转换
explicit MyString(int n)
只能MyString str2(10);而MyString str2=10;是错的
MyString str2(‘a’);也是错的
初始化列表:有自己写的构造函数
eg1:
Parent类只有ab成员,Child类有c成员 父类构造很简单不再赘述,
但子类构造:Child(int a, int b, int c) :Parent(a,b)
eg2:
Object类只有ab成员,其构造的高级写法:
Object(int _a,int _b):a(_a),b(_b){}
child继承与Object 只有成员char*p,其构造:
Parent(char * p) :Object(1,2)
一个类调用了其他类,这个类成为类对象成员,若被调用类里有自己写的构造函数,则一定要写初始化列表
继承相似,如果另一个类中有自定义的构造函数,那么
写子类函数的时得初始化成员列表。只写父类的初始化。每一次继承,子类中都必须写初始化(基类的初始化)语句。
构造函数调用顺序是根据成员的先后顺序调用的:
初始化成员列表三种情况:
1const常量成员,因为常量只能初始化,不能赋值,所以必须放在初始化列表中;
2引用类型的成员,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表中;
3成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成
员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败
const 成员:在初始化列表初始化
static 成员:在类外初始化,初始化时,不需要static关键字 eg:int Person::sNum = 0;
静态成员可以在外部初始化的原因:静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。
因为静态共享所以不管它是public还是private,都可以在外部用域作用符初始化。但是 静态成员也有访问权限,类外不能访问私有成员
即:静态属性初始化没有权限,访问会有权限
const static 既要实现共享,又要实现不可改变 在本地初始化
有了const就得把初始化放内部(因为是const所以定义时得初始化)。
void sonmeOperate() const{ 在函数括号后面加上 const,修饰成员变量不可修改,除了 mutable 变量
//this->mAge = 200; //mAge 不可修改 因为mutable int mID;
this->mID = 10;
}
访问控制:
有三种继承,可以随便继承,继承前后属性会发生改变。
除了私有的,都能(在子类内部访问)。而在类的外部(main中) 只能访问public属性的变量
private:private继承后,成员和成员函数只能在类内部使用
protected:protect继承以后只在类的内部使用 在继承的子类中可用
class不写 是私有 而struct默认是public。
this指针:
C++类对象中的变量和函数是分开存储。这是因为this指针
每个构造函数都会隐含了一个this指针,指向不同的对象。即不同的对象有他们自己的this指针。
全局函数和成员函数的转变:返回元素会拷贝成匿名对象 而 返回一个引用相当于返回自身(内存首地址)返回的是t1这个对象!!!!!!!!!!
全局函数转成员函数 少了一个参数 体现了面向对象思想 很方便
友元函数:友元函数声明的位置和public private没有关系;函数是类A的好朋友,可以通过函数访问类A中的所有属性,
破坏封装不好。友元函数不是类的成员没有this指针。
友元类,可以省去写初始化列表
友元函数 至少包含类指针(或引用)和一个值
友元主要用在:C++库函数的操作符重载,而且必须是全局函数的方式;而其它操作符的重载可以用全局函数法或者成员函数法
1友元关系不能被继承。
2友元关系是单向的 不具有传递性
友元+引用时重载c库函数符的必要条件。
运算符重载:编译器应该提供一种机制 让用户有机会最自定义数据类型 有机会进行运算符操作
全局函数法的固定3步骤:
第一步 定义一个全局函数
第二步 改造函数名(非常单纯的改名字)
第三步 全局函数变友元(可省一个参数)
或者直接写成成员函数法,就不用写友元函数了,封装性依然很好。
一般情况下:参数是引用(效率高);重载C++库函数时参数必须用引用。
总结:运算符重载本质是一个函数 就像上面的一样
前置++:++当左值,所以返回引用。重载<<也是
new和delete运算符(运算符高函数一个档次)
1主函数中new一个对象,若创建成功则自动调用构造函数(而malloc不会自动调用),完成初始化(分配内存)。
delete自动调用析构函数 而且new完之后不用判断返回值 而malloc时还得判断返回值。
即:构造里仍用malloc+strcpy 析构里仍又写了一遍delete
2new自定义对象数组,必须提供无参构造函数(malloc+strcpy)。注意数组的delete[]格式。
new定义一般数组时,无需写构造函数。貌似是一句废话。
3不要delete void * 类型,将不会自动调用析构函数,会造成内存减少。
通常new写主调函数中后面加个括号,进行初始化。
delete 只适用于由 new 创建的对象。如果正在删除的对象的指针是 NULL,将不发生任何事
单例模式:
只有一个对象+一个接口 (恶汉式) 确保只有一个实例(任务管理器)
1首先定义静态变量(!!!!不是类而是类指针),内部定义外部new一个类。
要声明成私有的(外面不能直接访问到它,只能通过函数(静态)访问它)main之前在编译阶段就分配好了
2 静态成员函数不能访问非静态的成员(因为对象还没有实例化) 静态函数只能调用静态变量,所以函数只能定义成public类型的
3私有化 构造,拷贝构造函数,保证外部无法创建对象。
条件中不等于 即可等于!
象重载左移等C库定义的操作符 只能通过友元 写成全局函数的形式。
参数传递最好用引用,尤其是C库函数它不允许外界拷贝,一定得用引用。
重载是依据 你写的格式(参数,返回值)而写的,而非凭空写一个重载。
最好返回引用。
后置加加a++:
按道理可以返回元素也可以返回引用。但是按照后置加加的特性,它得返回出来一个临时值,
(有一条:不能返回栈上的引用)所以返回元素,用元素来接。
编译器默认提供4个构造函数:别忘了等号那个函数
针对=赋值与拷贝构造的区别:
一般情况下,有参构造函数中有new+strcpy。若用默认的等号赋值操作,则运行时会down掉(重复释放);
若自己只写一个拷贝构造函数,也是然并卵,因为根本不调用它;因为形式是str1=str2;而不是Str str3=str1。
所以,针对=赋值操作情况下:得重载=操作符。先释放=左边(this)的空间,防止内存泄露(越用越少)。
返回值是引用,参数也是引用。
参数是引用的原因是:
如果写成非引用的形式,则实参初始化形参调用构造函数(而若此时没有自己写拷贝构造函数,则又会出现down的问题),
当分析程序的时候造成麻烦。而且用引用不在创建新的对象,效率高。
返回值是引用的原因同上 不仅效率高,而且返回的是本身*this 还支持链式编程
重载&&就变成函数调用了,执行顺序改变了。不建议重载。
注意重载赋值运算符和[],(),->运算符必须定义为类的成员函数。赋值函数是构造函数的重载,不是普通的成员函数,不能用对象来调用
重载等号操作符,什么时候返回引用什么时候返回类????
str1=str2;
Str str3=str1+str2;
从上式子可得:返回本身,则写成引用;返回一个新的则写成一个对象。想想后置++也是这样。
当返回一个对象时,有个明显的特征:就是会有一个新的对象去接它,如上Str str3去接str1+str2一样。
结论:参数,返回值尽量用引用。
继承:
等号,析构,构造不能被继承。
子类的构造与析构都需要先调用父类函数 先完成爹的初始化 再完成儿子的初始化
继承与一个类中包含了另一个类一样,如果另一个类中有自定义的构造函数,那么写子类函数的时得初始化成员列表。只写父类的初始化。
每一次继承,子类中都必须写初始化(基类的初始化)语句。
重载、重写、重定义:
重载:同一作用域的同名函数
重定义(默认有继承):即隐藏,有继承,子类重新定义父类的同名函数。
重写(默认有继承和virtual):即覆盖,由于用了virtual覆盖掉基类的函数。而之前上面的重定义是隐藏,还可以通过
重定义就是同名也就是说 可以是同名同参 也可以是同名不同参
重写就是同名同参
重写 函数一模一样的函数 1虚函数重写(发生多态) 2非虚函数重写(重定义)
基类,子类中有同名的属性:能继承 就近原则 通过域作用符访问(隐藏)
多继承菱形问题:
B-B1-C c中的b到底是从B1继承过来的还是从B2继承过来的?出现了二义性问题。
\ /
B2
解决方法:
B1,B2继承时,加上virtual虚继承(虚基类)。虚继承能解决共同老祖先的问题。占内存大小为:只用一份(老祖先)数据+2个vBptr指针
也可以写域作用符:此方法不好,用工具看 有好几份同样的数据 浪费空间
面向对象三大概念:
封装:(可重用,可扩充;一个接口不同的实现功能;隐蔽:定义与实现分离)可接隐
封装,继承和多态。在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,
它不仅支持系统的可重用性,而且还有利于提高系统的可扩充性;
消息传递可以实现发送一个通用的消息而调用不同的方法;
封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。
.可以跟属性和函数 这就不是面向过程
继承:编译;运行;接口;重写
继承是在编译时刻静态定义的, 类继承可以较方便地改变父类的实现.
继承在编译时刻就定义了,无法在运行时改变从父类继承的实现。
父类通常至少定义了子类的部分行为(接口),父类的任何改变都可能影响子类的行为。
如果继承下来的实现不适合解决新的问题, 则父类必须重写或被其他更适合的类替换。
这种依赖关系限制了灵活性并最终限制了复用性。 所以产生了虚函数重写发生多态
多态:(提迟)
1提前布局,vptr指针指向各自对象的虚函数表
2迟邦定 运行时候绑定 模板才是编译时绑定
早绑定指在对象申明的时候就和他的类型建立了关联。
晚绑定是指我们的代码在运行时再检查对象是否提供了我们所需要的方法和属性。
开闭原则:对修改关闭,对扩展开放。通过增加代码来增加新功能而非修改源代码
迟邦定原因(什么是virtual):
向上类型转换:若没有写virtual,传什么类型(指针),调用什么类型(不关心基类指针定义什么对象)。
即:定义基类指针则调基类函数 定义子类指针调子类函数
向上类型转换的原因是:c语言的早绑定造成的,解决方法就是C++中的迟绑定即virtual
即:用基类指针定义子类对象 而不调用基类中的函数 调用子类对象的函数
重写:即覆盖,由于用了virtual覆盖掉基类的函数。而之前上面的重定义是隐藏,还可以通过::访问
只要调用构造函数,就会开辟内存空间,就会默默的加上vptr指针指向各自对象的虚函数表(vptr指针与函数地址重叠,vptr就是函数地址),
。虚函数表中的入口地址存放的是各个不同的实现函数(建议看PDF)
纯虚函数:子类必须得实现它
抽象类(有纯虚函数的类)不能被实例化,即不能这样:Figer f;
但是可以:Figure * base = NULL;即:可定义指针(基类) 接口的精髓所在!可指向内部虚函数
模板方法模式:(顾名思义,模板即具体的步骤)
主要用纯虚函数类
茶和咖啡模式:纯虚函数中写4个步骤。(抽象出过程) 写出make()模板方法
再继承下来,分别写茶和咖啡类的具体步骤。(具体步骤)
调用时:定义指针指向其内部的make()
先构造父类,再构造成员变量、最后构造自己;
如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为抽象类。
Switch语句和if语句都属于动态联编。
当父类中存在虚函数,则会生成一个虚函数表,同时存在一个vptr指针,指向虚函数表,当有子类继承此父类时,
子类也会生成一个虚函数表和vptr指针,vptr指针指向子类的虚函数表。如果子类不虚函数重写它,子类的成员函数一直是基类的成员函数。
纯虚函数没有函数体 虚函数有函数体。c++中的接口,可通过纯虚函数实现
接口:公共的,抽象的 就是步骤 就是纯虚的
纯虚函数约定了一个接口,要求其子函数必须实现
面向抽象类编程 面向一套预先定义好的接口编程 解耦合 模块的划分
虚析构函数:当做一个普通的虚函数,实现多态。
纯虚析构函数:父类中声明,类外定义。仍不能实例化。
面向接口编程:
接口:公共的,抽象的。c中的回调函数和c++中的纯虚函数恰好迎合了这种机制
(回调函数规定了函数的参数,返回值格式)
(纯虚函数,只定义接口,实现交给厂商)
经典的 发送,接受函数的参数形式:
项目:
用C++写:
我是中标厂商 用C++写:自带耦合属性
纯虚函数法写时:在完成纯虚函数子函数具体化后,main中定义纯虚函数类指针和一些其他参数
根据主厂商提供测试框架(传递纯虚函数类指针 和 一些其它参数)这个框架的目的是:把主厂商的接口 与 具体的厂商函数粘结在一起),
把指针传和其他参数传递过去。
用C写: (有点用struct指针模拟纯虚函数类指针的味道)
主厂商定义了typedef的回调函数接口(抽象层),
写了一个结构体(确定回调函数的函数名)
又写了一个初始化框架(传递结构体指针 和 回调函数)把厂商函数与主厂商函数耦合 即具体化结构体中的函数
主函数中又写一个测试框架(传递结构体指针 和 一些其他参数 进行具体数据的传递)
总结: 即定义结构体->具体化结构体->传递给结构体参数
厂商根据回调函数写具体的函数
在main中 定义结构体->初始化耦合->测试框架
加密:需要4个参数:待加密字符串(主函数分配内存),长度,加密的字符串(主函数分配内存),长度(int *)
解密:同上。
套路是固定的:定义结构体;耦合;测试框架;
记住是发送前加密。接受后解密。
模板即泛型:
函数的业务逻辑一样 但函数参数类型不一样 有一种机制 让类型参数化 方便编程 就是泛型编程(模板机制)
函数模板:
template<typename T>//告诉编译器要进行泛型编程了 看见T不要报错
void myswap(T &a,T &b){
T c;
c = a;
a = b;
b = c;
cout << "我是模板函数" << endl;}
这样调用:
int x = 10;int y = 20;
myswap<int>(x, y);//自动类型推导的写法myswap(x, y);
一般情况下,使用自动推到,但得严格的类型匹配
函数模板可以使用自动类型推到,类模板不行
eg:有函数: T func(T x, T y)
这样调用是可以的:func<int>(3, 2.5); 而func(3, 2.5);这样就不行
调用函数模板 本质是类型参数化 将严格的按照类型进行匹配 不会进行自动类型转换,
而普通函数调用编译器会自动的转换
函数模板像普通函数一样可以被重载
当都符合条件时候 普通函数优先于函数模板
如果函数模板能产生更好的匹配就用函数模板
都符合时 若强烈想使用函数模板加空<>函数类型链表
两次编译:
看汇编 编译器根据<>内部的类型 生成具体的函数
两次编译原理机制
编译器进行了两次编译:第一次编译对模板语法编译,第二次根据调用产生具体的函数(有内存了)类型
根据类型生成具体函数,若类型相同则第一次编译的时候,只生成一个函数。
模板类:
template <typename T>//告诉编译器要泛型编程了 出现T不要报错
class A{
public:
A(T a = 0)//A(int a = 0){
this->a = a;}
void printA(){
cout << "a:" << a << endl;}
protected:
T a;
private:};
继承:class B :public A<int> 得额外加上<type>确定内存大小
类中的构造:B(int a = 10, int b = 20) :A<int>(a)
模板类派生 也需要具体化模板类 要知道父类占的内存大小是多少 只有数据类型固定下来才能分配内存
模板类定义时:在main()中定义时必须加上<>并具体化,并不能自动类型推到
模板类做函数参数时:只能传具体的。形参必具体才能分配内存,同定义。
void DoBussiness1(Person<string,int>&p)
也可以用函数模板嵌套类模板,不需要具体化
template<class T1,class T2>
void DoBussiness2(Person<T1, T2>& person){
cout << "Name:" << person.mName << " Age:" << person.mAge << endl;
}
普通类继承一个模板类 即模板类派生普通类需要具体化。只能写具体的int long double .....
而模板类派生模板类 可以具体化为基类的T 也可以具体化为一个具体的(看老王的代码)
不管是模板类派生普通类 还是 模板类派生模板类 都不能继承一个类模板。
模板类里的友元重载时候若其实现的函数在类内部,可以不具体化模板参数T,也不用加template
模板类的一般成员或友元函数具体实现写外面:以前是写成:Person::Person()的形式
现在得写成:Person<T>::Person(),把类模板具体化。而T从Template<typedef T>处来,得加上template
所以说 写外部非常麻烦,还是写内部好
无法解析的外部符号:编译无错误,链接时候错误。(想起c ,c++互不通用)。
所以:左移的友元,
函数外边写(注意是写在函数外部)的是函数模板,所以类的内部也得写成模板:
即:函数前一句和函数里都写template,就不会出现“无法解析的外部符号”报错
比较麻烦......
当分文件编写时
二次编译:类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
和编译文件有关,和二次编译有关。编译器一个一个cpp文件编译的。当编译模板的cpp时,没有生成具体的函数,
因为当前文件中你没哟用。
main.cpp也能编译通过。 最后连接时:由于编译模板的cpp没有生成具体函数 所以无法连接的外部错误
所以:别分文件,建一个.hpp文件 里面写类模板 和 具体的函数。
小案例:
为什么数组类的拷贝构造里 拷贝临时长度而非总长度?
定义一个数组类 必手动提供默认构造函数
类的属性只要有指针 先写默认构造,在里面分配内存;再写有参构造函数分配多少内存;
然后立马写重载拷贝构造和重载等号操作符
只要有指针 重载等号少不了,拷贝构造少不了。
***********************
为甚么看到类成员函数中有* 就烦
因为所有函数都得写一遍:默认,构造,析构,拷贝,=重载
***********************
静态转换static_cast:用于父子类指针或者引用 和 基础类型转换
dynamic_cast只能将子类指针转为父类指针或者引用。
总之:static不安全 不建议用
static_cast为静态类型转换,在编译期间就可以决定其类型的转换。
从类模板实例化的每一个模板类有自己的类模板数据成员, 该模板的所有对象共享一个 static 数据成员
static_cast < type-id > ( expression ) 将 expression 转换成type-id 指定的类型. 但是在这个转换过程中没有运行时类型检查
icr_int *confnum = static_cast<icr_int*>(conf);
模板函数的真正代码是在(运行执行函数时)时产生的。
new的用法
之前的用法有:
类中私有成员是数组的三要素:指针,长度,容量。
默认构造函数里new了20个int;有参构造函数中也有new。只是纯数组的增添改删。没有拷贝构造,重载什么的
实际的用法是 :main中new一个对象(可以写成有参(最好) 可以调无参) 别忘最后delete
今天的用法是:类模板
const_cast:把普通指针转为const int,char,.... *的类型 或者反过来,即加或去const属性 eg
const_case<int*> 去掉const属性; const_case<const int*> 可以把一个非const变量直接赋给一个const变量
异常:
c中 必须逐层的return数据,判断是否错误。还得约定好
c++中,忽略返回值,返回值无意义,异常可以跳级
返回一个值有很大的缺陷性,这个值可能错误码等于了计算值
catch(...)捕获所有异常
栈解锁:针对的是类 抛出异常时,调用析构
这就是异常接口声明:
//void myDivide() throw(int,char,const char *)//可以扔出int char char *类型的异常
//void myDivide() //可以抛出任何类型异常
//void myDivide() throw()//不抛出异常 (跟编译器有关) 。。。这就是异常接口声明
抛出一个类时,case中用引用来接(可以省了写拷贝构造,而且调试起来比较方便)。
不要抛指针 要是抛的话最好抛new出来的指针。
若有相似的业务 保持高度警惕!用多态!
第一份工资决定以后的公司。大公司好,技术能力强的人多。
自己琢磨点东西去写, 不要模仿老师的思路。
经验:出现问题,解决问题
写。
写自己的异常库:写一个类继承c++库异常。而这个类的(构造函数)作用是:
1.把抛出异常的字符串信息接住。
2.重载父类的what和虚析构函数
输入输出流:
流:<=> 即是流
cin.get()从缓冲区拿‘走’字符。包括回车
cin.get(buf,1024);一次读一行 回车键读到缓冲区
cin.getline(buf,1024);一次读一行,回车换行不能读到缓冲区
但两者显示的是相同的。
cin.putback(ch)把ch放到被cin.get()拿走的缓冲区。
一旦有深拷贝浅拷贝问题,不仅要修改拷贝构造函数而且还得重载等号操作符
构造函数初始化列表:一点都不难,就是不好记,就是一个类中的成员有另一个类
面向对象模型初探 没看懂