1.封装,继承,多态。
2.作用域运算符(::)
::前面不写东西,表示全局变量
3.名字控制(命名空间:namespace)
1)命名空间只能在全局范围内定义
2)命名空间可以嵌套命名空间
3)命名空间是开放的,可以随时向命名空间添加东西(变量,函数,类等)
4)声明和实现分离
5)匿名命名空间,默认此空间中的函数,变量等只能在当前文件中有效,相当于给每个变量,函数前加static。
nameespace TestA
{
int a;//这是在命名空间中未初始化的定义,当在命名空间进行定义时,而且此命名空间放在头文件,当在其他文件进行调用
//时,会产生重复定义的问题,编译不过
int b;//这是在命名空间中初始化的定义
extern int c;//这是命名空间中的声明
}
4.条件编译
#pragma once (第一种)
#ifndef___;#define____;#endif;____代表一个定义的名字 (第二种)
5.bool类型:bool类型的变量可以把“任何非0值”转换成1;
6.三目运算符:C和C++的区别
C语言中,三目运算符返回的是一个变量的值,没有内存空间,不可以修改(如下)
int a =10;int b = 20; a>b?a:b; (a>b?a:b )=30 (不可以这样操作);
C++语言中,三目运算符返回的时一个变量本省,具有内存空间,可以修改
int a =10;int b = 20; a>b?a:b; (a>b?a:b )=30 (是可以这样操作的);
7.当一个变量有内存空间时(只要在堆栈区),就可以通过他的地址修改内存里的值
8.const的使用
如:C语言中,const修饰的变量时一个只读变量,具有内存;
C++语言中,const修饰的为常量不具有内存(有额外情况,下面有),它放在符号表中。
1)C++中两种情况不一样
const int a = 10;此时a不具有内存,(用一个常量初始化) 它放在符号表中,所以不可以对它进行修改。
int b=9; const int a= b;此时a具有内存(在栈区),因为对它进行赋值的时一个变量b(栈区),可以通过指针间接赋值改变a的值
2)在局部变量中,(C语言和C++语言情况不一样)
C++语言中:const int a=9; int *p=(int*)&a;*p=99;
此时当对const常量a取地址,编译器会为它分配地址(在栈区);但是a常量从符号表中取值,不可被修改;
C语言中,变量a可以被修改
3)在全局变量(C语言和C++语言这两种情况一样)
extern const int a =9; int *p=(int *)&a; *p=99 (第一种)
const int a = 9;(它放在全局变量);int *p = (int *)&a; *p=99; (第二种)
此时对const变量a取地址或声明extern时,编译器会为它分配地址(在只读变量区);这段代码可以编译通过,但是运行阶段会报错,不以被运行
4) C语言中const修饰的变量为外部链接(和普通变量一样)
C++语言中,const 修饰的变量为内部链接(和普通变量不一样)
不管是C还是C++语言,普通变量都是外部链接。
5) const与#define的不同,const有类型,有作用域,可进行编译器类型安全检查;
#define 无类型,不重视作用域,(从定义开始到文件结尾),不可以进行安全检查;
9.extern的使用
使用范围是不同文件之间变量的调用。(不可以在同一文件不同函数之间使用)
在不同文件之间调用一个外部链接的变量时,只用在调用的文件中写一个extern关键字就可以了
当调用一个内部链接时,需要在被调文件和调用文件中的写extern关键字。
10.变量可以定义数组 int n = 9; int arr[n];
不同的编译器编译环境效果不同,在vs2013中不可以通过,在QT Linux中可以通过,支持C99的编译器可以通过。
11.using 编译指令与 using 声明的区别
namespace A{int a=9;func(){}}
namespace B{int a=9;func(){}}
using声明:int main(){
using A::a;
cout<<a<<endl;
int a = 6;
}
当第二个a被定义时,函数编译通不过,命名冲突
using 编译指令:
using 编译指令:
int main(){
using namespace A;;
cout<<a<<endl;
int a = 6;
cout<<a<<endl;
}
输出结果为:9,6;
此时打印最近定义的那个。
int main()
{
using namespace A;
using namespace B;
cou<<a<<endl;
}
此时会报错,编译器不知道选择那个a
注意:使用using 声明或using编译指令会增加命名冲突的可能性,也就是说,如果有名称空间,
在代码中使用作用域解析运算符,则不会出现二义性
12.在C语言中,重复定义多个同名的全局变量时合法的(不正规),但是C++是不合法的。
--------------------------------------------------------------------------------------------------------
1.变量都是有内存空间的,当它在堆栈区时可以通过指针修改它的值。
2.引用:本质地址传递;编译器帮忙做了基本地址传递部分
int &b=a;=========》 int* const b=&a;(实质)b是常指针,指向不可以改变
1)统一个内存块可以取多个别名,int&b=a,int &c=a;
2)引用的基本语法:int a =10;int &b=a;(引用)b=100;cout<<a<<endl; cout<<b<<endl; 输出结果都为100.
3)指针的引用:int *p = NULL; int *&p1 = p; p1=(int*)malloc(sizeof(int));
4)数组的引用: (两种方式)
1.typedef int ARR[10]//建立数组类型,
int a[10] //创建一个数组,
ARR &p=a; //对数组的引用;
p1[i]=i; //对数组的某个元素赋值
2.int(&p)[10]=a;//直接建立数组类型,进行引用,并初始化,
p1[i]=i;//对数组的某个元素赋值
5) 引用没有定义,是一种关系型的声明,声明它和原有某一变量(实体)的关系,故而类型与原类型保持一致,且不分配内存,
与被引用的变量有着相同的地址声明引用变量时必须初始化, int&b;//错误,必须确保引用是和一块合法的内存块关联
6)引用一旦初始化,不能改变 (可以建立数组引用)
int &b=a;b=c; (这种写法并不是改变b的指向,是将c的值赋给b;b并没有指向c)
7)(常量引用) const int &b = a; b的值,指向都不能被改变,
因为它等同于 : const int *const b=&a;(这种写法常用哎保护’实参‘不受’形参‘的改变)
8)C++编译器在编译过程中使用“常指针”作为引用的内部实现,因此引用所占用的空间大小与指针相同。
9)建立普通变量的引用
int ma = 9;
int &ra = ma;//建立引用
int *const ra = &ma; //即ra = &ma;
ra = 88 ;//通过引用修改变量的值。这步是编译器帮忙进行解引用,然后赋值的。
//建立对指针的引用
int *p2 = NULL;
int *&p2 = p2; //建立引用,
int **const mp2 = &p2;
mp2 = (int *) malloc(sizeof(int));//通过引用给指针p2分配空间,也就是给p2重新赋值,让他重新指向。
//建立对数组的引用
typedef int Arr[10];//建立数组类型
Arr a; //建立一个普通的数组
Arr &3 = a; //建立引用,
Arr *const p3 = &a;
p3[3]=10; //对数组的第四个元素赋值。
3.引用的几点基本常识:
1)单独定义引用时,必须初始化;说明它很像一个常量;因为常量在定义的时候必须初始化 (const int a = 5)
2)普通引用有自己的空间(在32位平台占4个字节),但是引用变量的地址和初始化它的变量时同一块地址,
int &a=b; a和b的地址是相同的,
struct teacher {int a;char b; int &d;double &c;};这个结构体所占内存为16
struct teacher {int a;char b;}; 这个结构体所占用内存是8;
3)引用的本质是一个常量指针;
4.函数中的引用:引用做函数的参数,引用做参数的返回值,
1)引用做参数不需要初始化
2)不能返回局部变量引用(和返回局部指针变量原因一样)
3)引用做返回值(可以做左值和右值)
4)指针的引用。
5)(常量引用):const 对引用的使用(如上),const引用的值不能被改变,(主要用在函数的形参,不想用形参改变实参的值)
6)函数的返回值当左值需要返回引用
5.类:
1。使用class关键字,2)类里面可以放变量,函数。3)public:访问权限
6内联函数:
C++使用(即有宏函数的效率,有没有普通函数的开销;可以向普通函数那样,进行参数,返回值类型的安全检查,
又可以作为成员函数,在C++中,定义内联函数,只是对编译器的一个建议,并不Id会成为内联函数)
内联函数的语法:
1)普通函数;inline void func(int x){return;} 但是必须注意函数体和声明结合在一起,否则编译器将作为普通函数来对峙
2)要求:不能存在任何形式的循环语句,不能存在过多的条件判断语句,函数体不能过于庞大,不能对函数进行取址操作
7宏函数:
1)默认参数:int func(int x=10,int y=20);此时形参的赋值就是默认参数,当函数调用不传参数时,就将使用默认参数
2)注意:int func(int x,int y=0,int z=0);//函数的默认参数从左向右,如果一个参数设置默认参数,那么这个参数之后的参数必须
设为默认参数,函数的声明和函数的定义不能同时写默认参数,(即使默认参数相同也不行) 编译器不知道该选择哪套
3)占位参数:函数的占位参数也是参数,必须要给个值,只是函数内部用不了而已 int func(int,int,y);或int func(int,int y=3);
当占位参数与默认参数结合时,int func(int =5,int y);erro;这种写法,int(int y,int =30);占位参数只能卸载最后一个形参的位置
而且可以不给它传参,因为他有了默认参数
8函数重载:
1)函数重载的条件:
可以作为条件的:同一个作用域,函数名相同,形参的个数,形参的类型,形参的类型顺序不同,
用cosnt 进行修饰的函数也可以进行重载,非cosnt对象有限调用非const
const对象只能调用const函数,可以被const函数和非const函数调用,
注意:不可作为条件的:函数的返回值不能作为函数重载的条件,函数重载和默认参数不能同时出现;函数重载碰到默认参数,
name就要考虑是否会出现函数调用二义性(会报错,编译不通过)
2)重载函数的调用:正常调用:函数调用正常匹配函数形参(可以找到)。隐式类型转换后调用:当找不到匹配的形参时,编译器会进行
隐式转换,任然找不到后会进行报错(如下)
void func(char b);
int main()
{
int a=3;func(a);
}
此时就会进行隐式转换,因为与ASCII码匹配
3)函数重载的原理
编译器为了实现函数重载,在编译的时候做了一些优化,用不同的类型来修饰不同的函数名,
如: void func(){}
void func(int a){}
void func(int a,char b){}
上述三个函数编译后:
生成的函数名为:_z4funcv v表示void 无参数
_z4funci i表示参数为int类型
_z4funcic i代表第一个参数是int类型的,第二个参数代表char类型的
----------------------------------------------------------------------------------------------
1.struct的区别(C和C++)
C语言中只能定义变量。
C++语言中可以定义变量和函数,同时C++语言中,struct中所有函数和函数都是public权限,
2.类的封装
3.类内部的三种权限
public:公有属性(修饰的成员变量和方法;可以在类的内部和外部使用)
private:私有属性,(修饰的成员变量和方法,只能在类的内部使用,不能在类的外部使用)
protected:保护属性,主要用于继承,(修饰的成员变量和方法,可以在类的内部和继承的子类使用,不能在类的外部使用)
4.struct 和class的区别
struct中成员默认权限为public;
class中成员的默认权限为private;
5.类的调用(一个类调用另外一个类)
6.对象的构造和析构:
1)构造函数: "名称和类名相同";没有返回值,可以有多个,进行函数重载,在内存中开辟之后调用构造函数
2)无参构造和有参构造:
1无参构造函数:定义对象的时候,对象后面不能加括号,如class stu{std(){“无参构造函数”}};stu st;(正确定义对象)
如果加上括号:stu st();编译时不会报错(因为编译器把它看成函数声明),运行的时候会报错。
2.有参构造函数:普通参数,根据形参的类型和个数,也可以进行函数重载,
class Animal{
public :
Animal(int age)
{
cout<<"一个参数构造函数数字!"<<endl;
mName="undefined";
mAge=age;
}
Animal(string name)
{
cout<<"一个参数构造函数字母"<<endl;
mName=name;
mAge=0;
}
Animal(string name ,int age)
{
cout<<"两个参数的构造函数"<<endl;
mName=name;
mAge=age;
}
//拷贝构造函数,用一个对象来初始化另一个对象,
Animal(const Animal &animal)
{
//拷贝构造函数,形参必须是引用,否则会造成死循环,一直调用它本身,本质上成了递归调用本身
cout<<"拷贝构造函数"<<endl;
mName= animal.mName;
mAge=animal.mAge;
}
拷贝参数:用一个对象来初始化另外一个对象,它的形参必须是引用,如果是普通的变量,就会造成本质上的递归调用本身,无意义
}
3)析构函数:名字=类名+~;没有返回值,没有参数,一个类只能有一个析构函数,在内存释放前调用析构函数
7.构造函数调用的规则:(参考上面的代码)
1)括号法: Animal1("smith");//一个参数的构造函数(字母)
Animal2(20);//一个参数构造的函数(数字)
Animal3("john",20);//两个参数的构造函数
Animal4(animal3);拷贝构造
2)显示调用构造函数(匿名对象调用拷贝构造的问题)
Animal("Smith",20);//匿名函数生命周期仅限于当行,慈航运行完之后立即析构。
Animal animal5 = Animal(30);//一个参数构造的函数(数字); 形参为普通变量
Animal animal5 = Animal("smith",30);//注意:两个参数的构造函数,拷贝构造,这行比上一行多了一个拷贝构造,
//是因为string容器的缘故,只有这时才会有拷贝构造;
Animal animal5(animal(30));一个参数构造的函数(数字)
Animal animal5(animal(30,"smith"));两个参数构造函数,拷贝构造,当不调用string参数时,它不会调用拷贝构造,由编译器决定,具体方式不清楚
以上两种情况,调不调用拷贝构造,主要看参数类型,有string:就调用拷贝构造,没有string就不调用拷贝构造
匿名对象如果没有变量来接,他就是一个对象,此时这个变就相当于匿名对象的名称,
匿名对象如果没有变量来接没他就是一个实例化的对象,
3)等号法,
Animal animal5=10;//不常用,调用一个函数,构造函数,
Animal animal5=animal4;"拷贝构造,常用"
8.拷贝构造常用的两种调用方式:
Animal animal5 = animal4;
Animal animal5(animal4);
9.拷贝构造的调用时机:
1)对象以传递的方式传递给函数参数,void func(Animal animal){};用实参初始化形参,调用额拷贝构造函数,
2)用一个对象初始化另一个对象,Animal animal5 = animal4; 或者Animalanimal5(animal4);
3)函数返回局部对象,(注意这里:debug模式和release模式不一样)。
debug模式下:会调用拷贝构造,打印洗的地址相同,编译器这里会做优化,直接把原来的对象返回回去,
Animal MyBussiness()
{
//局部对象
Animal animal ;//无参构造函数”
cout<<&animal<<endl;
return animal;//编译器县拷贝一份,然后返回,相当于返回一个匿名对象
}
void test03()
{
Animal animal=MyBussiness();
cout<<&animal<<endl;
}
10.构造函数调用的规则
1) 默认构造函数,(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对类中非静态成员属性简单,“值”拷贝
2)如果用户定义了“拷贝构造函数”,C++不会再提供“任何默认构造函数”
3)如果用户定义了普通构造,C++ 不会再提供 默认无参构造,但是会提供默认拷贝构造
4)在构造函数中调用构造函数是一个危险的行为,本质上:里面的构造函数相当于一个匿名对象,生命周期只有本行(没有变量接的情况下)
11.初始化列表:初始化成员列表只能在构造函数使用。
构造函数的初始化列表顺序
1)当一个类中组合了其他类的对象,先执行被组合的构造函数,
2)如果组合对象有多个,来自不同的类,根据定义的顺序进行构造函数调用,而不是初始化列表顺序,
3)被组合对象的构造顺序:与定义顺序有关系,与初始化列表的顺序,没有关系
4)若类成员中有const修饰,必须在对象初始化的时候,给const元素初始化,此时就用到了“初始化列表”
析构函数:与构造函数调用顺序相反,
12.深拷贝和浅拷贝
class Person
{
public:
Person(int a ,int b)
_b(b),_a(a){}//初始化列表”
深拷贝
Person(const person &p1)
{
mstr=(char *)malloc(strlen(p1,mstr)+1);//核心步骤,会进行内存拷贝
strcpy(mstr,p1,mstr);
_a=p1._a;
_b=p1._b;
}
浅拷贝
Person(cosnt Person &p1)
{
mstr=p1.mstr.这只会把一个对象的指针地址,赋给另外一个对象的额指针,不会进行内存拷贝
_a=p1._a;
_b=p1._b;
}
void print_func()
{
cout<<_a<<" "<<_b<<endl;
}
private:
int _a;
int _b;
char *mstr;
};
13.C++中string是一个类,
14.一直一个Peint类,在该类的main()函数里执行语句“Print a,b[2],*c”后,程序会自动调用该类的构造函数4次
a调用一次,b调用两次,*c是空指针,没有开辟空间不会被调用。
----------------------------------------------------------------------------------------------------------
1.expicit:修饰构造韩式;进制编译器隐式转换,
1.针对单参数的构造函数,
2.或者除第一个参数外其他参数都有默认值的多参数构造函数,
2.C++中动态分配内存:
new:在对上分配内存,分配对象的堆内存时会调用构造函数
申请string类型的空间只能string类型数据,可以改为string *ps= new string("10");
delete:释放堆空间,在释放堆呃逆存在前会调用析构函数
delete void* 指针可能会报错,没有调用析构函数
3.创建动态数组:
int *arr = new int[10];创建一个int 类型数组,元素10个,delete[] arr;释放内存。
4.穿件自定义对象数组,必须提供无参构造函数,同时数组有几个元素就电泳几次“无参构造函数和析构函数”
delete void*指针可能会出错,没有调用析构函数
class print
{
private:int a ;//在类外声明静态变量
public:
};
int print::a=0;//在类外初始化静态变量
5.静态成员变量:
1)在编译阶段就分配空间,对象还没有创建;
2)必须在类中声明,在类外初始化(或定义)
3)归同一个类的所有对象所有,共享同一个静态变量,在为对象分配的空间中不包含静态成员所占空间
4)有权限限制:private静态变量和public静态变量
public静态变量,在类外不能访问和操作,类外访问:会报错
public:在类外可以访问和操作,
5)cosnt静态成员变量(cosnt static int a=8);
定义今天变量cosnt 静态成员变量时,最好在类内的内部初始化,
静态变量的调用:用类名加域作用符:print::a;
用对象名加去地址符:class p1;p1.a;
6静态成员函数(主要是尾了访问静态变量)
与静态变量一样,在没有创建对象前即可通过类名调用。
1)静态成员函数,只能访问“成员函数变量”不能访问普通成员变量
2)静态成员函数的使用规则和静态成员变量一样
3)静态成员函数也有使用权限,类外无法访问私有静态函数。
4)普通成员函数可访问“成静态成员变量”,也可以访问“非静态成员变量”
7.单例模式:一个类只存在一个对象
条件:
1)构造函数。拷贝构造,析构函数私有化,
2)提供西优化静态成员变量指向一个对象
3)提供外部获得对象的静态成员函数
class person
{
public:
void print_func(string content)
{
cout<<"打印内容"<<content<<endl;
mcount++;
cout<<"打印次数"<<mcount<<endl;
}
static person *getinstance()
{
//静态成员函数“在公共区域”,提供接口
return p1;
}
private:
static person *p1; //私有化静态成员变量
int mcount;
};
int main()
{
person *p1 = person::getinstance();//p1和p2的地址一样
person *p2 = person::getinstance();//因为他们为指向同一个对象,
p1->print_func("晚上好!!!");
p2->print_func("加油,胜利就在眼前");
return 0;
}
8.C++成员变量和函数的存储:(成员变量和函数分开存储)
1)静态变量不在类对象中存储
2)函数不在类对象中存储(静态函数与非静态函数都是)
3)类对象中之存储,普通成员变量
9.this指针
1)this指针永远指向当前对象
2)“静态成员函数”,内部没有shis指针,所以静态成员函数不能操作非静态成员变量
this指针的使用:
1)当形参与成员变量同名时,可以通过this指针区分。
2)在类的非静态成员函数中返回对象本身,可以使用 return*this;
class person
{
public:
person(int a,string name)
{
this->a=a;
this-name=name;
}
person func()
{
return *this;
}
public:
int a;
string name;
}
10.空对象指针访问成员的不同情况:由于(this指针的存在)
1)当不访问成员变量的时候是允许的
2)当访问成员变量时,函数通过this指针孕照成员变量,此时就会报错。
class ARE
{
public:
void print()
{
cout<<"hello world!!"<<endl;
}
void get_func()
{
cout<<ma<<endl;
}
public:
int ma;
}
int main()
{
ARE *a1 = NULL;
a1->print(); //可以访问,因为此时不访问尘缘变量
a1->get_func; //不可访问,它对成员变量进行了访问
return 0;
}
11.cosnt 对成员函数的修饰,(int func() const{});注意:const要在小括号后面,大括号前面,(--->int func(const ARE *const this))
1)cosnt 修饰this指针指向的内存区域,成员函数体内不可改变类中任何变量,除非变量前面用mutable进行修饰
mutable 对成员变量的修饰
2)用它修饰后,任何情况下都可以对变量进行修改。
12.const 修饰对象(常对象) 不允许修改对象属性
1)常对象对象可以访问const或非const的变量,不能修改变量,除非变量前用mutable修饰
2)常对象只能帝爱用coanst修饰常函数
13.友元函数。(全局函数和成员函数两种)
1)friend 关键字只出现在声明处。
2)其他类,类成员函数,全局函数都可以声明伪为友元。
3)友元函数不是类的成员,不带this指针
4)友元函数访问对象的任意成员属性,包括私有属性。
注意: 1.友元不能被继承
2.友元关系是单向的,类A是类B的友元。但类B不一定是类A的友元。
3.友元关系不具有传递性,类B是类A的友元,类C是类B的友元,但类C不一定是类A的友元
--------------------------------------------------------------------------------------------------
1.友元函数,概念
2.数组类型
3.操作符重载:
1)本质;函数的调用
函数的参数取决于两个因素
1.运算符是一元还是二元
2.运算符被定义为全局函数还是成员函数
2)运算符重载的限制:
1.使用运算符重载不能改变运算符的优先级,不能改变运算符的参数个数。
不能重载的运算符有 : .--::--. *--? :--sizeof.
2.除“=”号外,基类中重载的运算符都将被派生类继承。
3.特殊运算符:
1.“=”,“[]”,"()","->"操作符只能通过函数重载
2."<<";">>" 只能进行友元函数重载
3.不要重载“&&”,“||” 操作符,因为无法实现短路原则,
4.运算符重载建议:
1.所有一元运算符,建议使用:成员函数,
2.“=”。“[]”.“”。”->“;必须使用成员函数
3.+= -= *= &= != %= ^=,建议使用:成员函数。
4.其他二元运算符 建议使用:非成员函数。
5重载“=”号操作符的步骤,
1.先释放旧内存。
2.返回一个引用,实现链式编程,
------------------------------------------------------------------------------------------
运算符重载。
1.前置++;后置--;
2.后置++;前置--;
要创建一个临时变量接受计算时的变量,记住要把临时变量返回去
3.== 和 != 的重载
4.继承,多态
1)C++的继承方式(public(公有),private(私有),protected(保护))会影响子类的对方访问属性,判断某一句话,能否被访问
1三个原则
1-看调用语句,这句话写在子类的内部,还是外部
2-看子类如何从父类继承(public,private,protected)
3-看父类中的访问级别(public,private,protected);
2)继承的使用情况:
1-protected继承,只能在家族内部使用,不能在类的额外部使用
2-项目开发中,通常都是public继承;
5.继承的类型兼容原则:
1)子类对象可以当做父类对象使用
2)子类对象可以直接赋值给父类对象
3)子类对象可以直接初始化父类对象
4)父类指针可以直接指向之类对象
5)父类引用可以直接引用子类对象
6.继承与组合混搭情况下,构造函数和析构函数调用原则
1)先调用父类构造函数,在调用组合类构造函数,最后调用自己的构造函数
2)先调用自己的析构函数,在调用组合类析构函数,最后调用父类析构函数
3)先构造的对象后析构
7.不可被继承的函数
1)基类中的构造函数和析构函数
2)基类中重载的运算符 =号运函数
8.继承中同名成员函数和变量的问题;
1)如果子类和父类中有相同的函数和变量名,子类调用时默认调用自身的函数和变量。
2)如果想调用父类的函数和变量,需要加上“类名和域作用符”,显示调用
9.继承中的静态成员特性:
1)基类中定义的静态成员,可被所有派生类共享
2)静态成员的访问权限和普通成员函数一样,遵守派生类的访问控制
3)与函数重载一样,如果子类中重新定义了同名的静态函数,基类的函数将被隐藏
4)静态成员函数不能是虚函数
10.继承中的函数重载现象;
1)如果重新定义了基类中的重载函数,只要函数名相同,继承的基类中的重载函数将被隐藏
想要调用必须使用域作用符显示调用
11.多继承的概念
1)多继承可能会出现二义性,解决办法,显示的调用不同类中的同命名属性或方法,
2)多继承的相同数据重复继承,解决办法:虚继承:virtual 即保持一份数据,也不会产生二义性
菱形继承解决方法: 加上virtual,用虚继承
12.多态:分为:静态多态(编译时多态)和动态多态(运行时多态)。
面向对象编程的设计思路,开闭原则(对修改关闭,对扩展开放)
C++的多态性是通过虚函数来实现。(虚函数允许子类重新定义父类成员,这种做法叫做重写或覆盖)
13.向上类型转换
14.虚函数:函数前加virtual修饰
1)virtual 关键字只能修饰成员函数
2)构造函数不能为虚函数
3)创建一个虚成员函数,只需要在函数声明前加上virtual 关键字。
4)如果一个函数在基类中被声明virtual,那么它在所有派生类中都是virtual;它派生类的函数前,(可加可不加virtual)
关键字推荐加上;
纯虚函数:virtual int func()=0;
1-当一个类中有纯虚函数时,这个类为抽象类,同时注意,不能实例化一个抽象类,编译时会报错
2-当继承一个抽象类时,必须实现所有的纯虚函数,否则抽象类派生的类也是一个抽象类
15.建立公共接口的目的:
将子类中公共的操作抽象出来,可以通过一个公共结构操作一组类,这个公共接口不需要事先事先,及创建一个公共类
16模板方法:就是通过创建一个公共接口来实现的
一。多态的思想
1)封装:突破C函数的概念,用类做函数参数的时候,可以调用对象的属性和对象的方法
2)继承:A B 代码可以复用,可以使用前人的代码
3)多态。可以使用未来的代码,架构不需要改变
二。多态成立的条件
1)要有继承
2)要有虚函数重写
3)用父类指针或者父类引用 指向子类对象
17.虚析构函数的特点和目的:
1)通过父类指针,把所有子类对象的析构函数都执行一遍
2)通过父类指正,释放所有子类资源
18.函数的重写、重载、重定义
1).重载:必须在同一个类中,重载是在编译期间根据参数类型和个数决定函数调用的,子类无法重载父类函数,父类同名函数将被覆盖
2)重写:必须发生在父类与子类之间(同时分为两类),并且父类与子类的函数有完全相同的原型。
1-virtual具有这个关键字,发生重写时会产生多态。
2-无这个关键字,叫重定义
3-重定义(非虚函数重写叫重定义)。父类与子类的有完全相同的函数原型,而且无virtual关键字修饰,此时叫做函数重定义
3)子类中的vptr指针的分布初始化
1-当创建子类对象时,如果它具有vptr指针,那么此时是分布初始化的。
2-当执行父类的构造函数时,子类对vptr指针会指向父类的虚函数表
当父类的构造函数执行完毕后,会把子类的vptr指针指向子类的虚函数表
3-结论:子类的vptr指针的分布初始化的,它在程序运行时会进行寻址操作,效率会降低,不建议每个成员函数都声明为虚函数
4-纯虚析构函数:
5-多态案列:
1->思路,
定义一套自己的数据结构,打包放在结构体中,数据接口使用函数指针
初始化模块接口(将自己定义的接口与生产商实现的函数联系在一起)
19.函数指针
---------------------------------------------------------------------------------------------------------------------------------
C++模板:用于实现泛型编程
一、1)模板关键字 template<class T ,class T1>或template<typename T,typename T1> T,T1 代表数据类型
2)定义的模板关键字当前行下一个函数或类有用
3)分为函数模板和类模板
4)一旦声明了多个类型T,不管用或者不用,都必须给它指定类型
二、函数模板的编译机制:(原理)
1)函数模板具有自动推到功能
2)编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
3)函数模板通过具体的类型产生不同的函数,
三、模板函数和普通模板的区别
1)函数模板不允许自动类型转换,必须严格匹配
2)普通函数能自动进行类型转换
template <class T>
T print(T a, T b){}
int show(int a,int b){}
void main()
{
int a=6;char b='c';
printf(a,b); //error,两个变量类型必须相同,不允许进行自动转换
show(a,b);"yes", 普通函数能自动类型转换;
}
四.函数模板和普通函数一起调用规则
1)函数模板可以向普通函数一样被重载
2)如果函数模板与普通函数一样,C++编译器优先调用普通函数
例如:
template <class T>
void print (T a,T b){}
void print (int a,int b){}
void main(){int a=4,int b=9; printf(a,b)};
此时优先调用普通函数
3)如果函数模板能产生更好的匹配,则有限调用函数模板。
例如:template <class T,class T1>
void print(T a,T b){}
void print(int a , int b){}
void main()
{
int a=4;
char b='u';
print(a,b);
此时优先调用函数模板
}
4)可以通过空模板实参列表的语法限定编译器只能调用模板函数
例如:第二个函数例子
void main(){int a=4;int b=6; print<>(a,b)};
五.类模板
1)只能显示的指定类型,不具有自动推到功能
2)类模板做函数参数(必须显示指定类型模板类型)
六.类模板与函数模板(有自动推到功能)的混合使用案例,有奇特。
template <class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
this->mName=name;
this->mAge=age
}
public:
T1 mName;
T2 mAge;
}
//类模板做函数参数 (指定类型)
void Dobusiness(Person<string,int>&p1)
{
cout<<"name:"<<p1.mName<<"age:"<<p1.mAge<<endl;
}
//类模板与函数模板混用使用(使用函数模板的自动推倒功能)
template<class T1,class T2>
void Dobusiness01(Person<T1,T2>&p1)
{
cout <<"name:"<<p1.mName<<"age"<<p1.mAge<<endl;
}
void test04()
{
Person<string ,int >p1("john",30);
Dobusiness(p1);
Person<string,int>p2("johneqeq",303);
Dobusiness01(p2);
}
七。类模板派生普通类和类模板类:
1)继承的话,一个继承一个具体的类,因为一个具体的类,编译器才知道分配多大的内存
2)如果想继承类模板,应该给继承过程中类模板显示指定类型。
例如:(类模板派生普通类)
template<class T>
class MyClass{private:"父类模板" T age;};
class SubClass:public MyClass<int>{"普通类",这里继承时,必须对类模板显示指定类型,编译器才知道父类分配多大的内存
public:
int a;
}
例如1:(类模板派生子类模板)
template<class T>
class MyClass{
private:"父类模板" T age;
};
template <class T>
class SubClass : public MyClass<T>{
"子类模板,这里继承时,可以用子类的类型指定父类的模板"
public:
T a;
};
八.类模板分文件编写
C++恩建的编译过程:先每个文件独立编译,然后由连接器将编译好的文件链接在一起(这一步就是寻找各种调用函数,和头文件的定义内容)
需要引入 .cpp, 原因:
1)C++编译机制的原因;(分文件编译导致的)
2)二次编译有关
解决编发文件,将他们放入:
类模板的声明与定义要放在一个文件中编写,创建一个.hpp将他们放入
C++的类型转换
九.静态类型转换:
1)上行转换:把派生类的指针或者引用转换成基类的安全的 Animal *ani = static_cast<Animal*>(dog);
2)下行转换:把基类的指针或引用转换成派生类的不安全的,因为没有动态类型检查,Dog *dog = static_cast<Dog *>(ani);
3)总结一句话:就是把大转成小的,把小转成大的,
4)当两个类之间无继承关系时,不可以进行转换。
如:Teacher *t1= static_cast<Teacher *>(dog);//error,因为两个类之间无关系
5)基础数据类型之间的转换:
1-可以把高精度的数据类型转换成低精度的数据类型; 如:double da; int a = static_cast<int>(da);
2-当把低精度的数据类型转换成搞精度时,安全性需要程序员保存,(原理和上一样),double da= static_cast<double>(a);
十.动态类型转换:
dynamic_cast<>(类与类之间的转换)
dynamic_cast<>具有类型检查功能,比static_cast<>类型转换安全
1)只能转换具体父子关系“指针或引用”
上行转换:只能讲子类指针转换成父类指针(可以大转小)
下行转换:不能讲父类指针转换成子类指针(不能小转大);
例如:parent *parent = dynamic_cast<parent*>son;
十一.数组模板:
1)自定义类型做元素构建数组时,必须提供无参构造函数;
-------------------------------------------------------------------------------------------------------------------------------
1.常量转换:(const_cast<>)用来修改类型的cosnt属性
1)常量指针。引用 被转换成 非常量指针、引用,并且仍然指向原来的对象()互相转换,将非常量转换为常量
2)不能直接对非指针、引用的变量直接使用const_cast<> 操作符去移除它的const属性
2.重新解释转换或强制类型转换。reinterpret_cast<>(不安全的转换类型,各种类型之间的强转)
3.异常处理(需要了解C的异常处理和C++的异常处理)C的异常处理的缺陷,和特点。
4.C++的异常特点。
1)异常不能被忽略,而且异常可以跨函数,一床并不是简单的int类型数字,最好明确的意义,
2)C++提供的异常机制,具有跨函数和不可忽略的特点。
3)异常捕获的严格类型匹配
5.栈解旋:异常被抛出后,从进入try起,到异常处理完毕,这期间在栈上创建的所有对象都会讲被自动析构掉,析构的顺序和创建的顺序相反
6.异常接口声明:
1)可以在函数前声明抛出的异常类型,如果不声明,表示可以抛出各种类型的异常,
例如:void func()throw(int ,double, char ){} //可以抛出三个类型的异常。
void func()throw(){}不可以抛出任何类型的异常。
void func(){};//可以抛出任何类型的异常
2)但是:C++的异常规范在不同编译器下可能只需效果会不同(编译器的不同会导致结果不同)
VS下回忽略C++的异常规范,但是会发生警告,程序照常只需;QT会报错,终止程序运行
通常情况下:如果一个抛出了它的异常接口声明所不允许抛出的类型,unexcepted函数将被调用,该函数默认调用terminate函数终端程序
7.异常变量的生命周期:
1)异常也可以抛出一个类的对象,此时可以使用纯虚函数做接口,声明每种不同的异常对象(减少代码量)
例如:
class BaseException{
public:
virtual void print_execption()=0;
}
class TagetException:public BaseException{
public:
virtual void print_execption()
{
cout<<"目标 空间空指针异常"<<endl;
}
};
class DestException:public BaseException
{
public:
virtual void print_Exception()
{
cout<<"源空间的空指针异常"<<endl;
}
};
void CopyString(char *dest,const char *source){
if(source == NUll)
{
throw DestException();
}
memcpy(TargetException,DestException,strlen(source)+1);
}
int mian()
{
const char *source = "nnakew";
char dest[1024]={0};
try{
CopyString(dest,source);
}
catch(BaseException& ex)
{
ex.print_execption();
}
return 0;
}
8.C++标准异常类:系统头文件<exception>
9.创建自己的异常类:
引入系统头文件,继承系统的标准 出错类;重载父类的what函数和虚析构函数
10.标准输入输出操作。
cin由缓冲区获取文件到目标内存或变量,若缓冲区没有内存时,才需要从键盘上输入,
cout 是有缓冲区输出文件到屏幕的,若缓冲区没有满时,或强制释放,它是不会在屏幕上输出内容的。
endl 的作用是刷新缓冲区,将内容输出到屏幕或文件里。
11.格式化输入输出操作。
--------------------------------------------------------------------------------------------------------------------
1.STL
2.STL三大组件:容器、算法、迭代器
没一个容器都有自己的迭代器,迭代器用来遍历容器中的元素,
遍历:不重复的进行访问每个元素。
3.容器,迭代器,算法的初步使用。
1.先引入所需要的容器头文件和算法头文件
void Myprint(int val)
{
算法中的回调函数需要自己写,
cout<<val<<" ";内容为自己所想打印的内容
void test()
{
vector<int>V;
for(int i=0;i<5;i++)
{
V.push_back(i+10);
}
vector<int>::iterator pBegin = V.begin();
vector<int>::iterator pEnd = V.end();
//直线使用迭代器进行容器的变量
while(pBegin != pEnd)
{
cout<<*pBegin<<" ";
pBegin++;
}
cout<<endl;
//模拟算法的最后一个回调函数的使用
pBegin = V.begin();
while(pBegin !=pEnd)
{
Myprint(*pBegin);
pBegin++;
}
cout<<endl;
//使用算法遍历容器,并且打印出来,注意:算法的最后一个参数为回调函数,需要自己写(所需要打印的内容)
for_each(V.begin(),V.end(),Myprint);
cout<<endl;
}
};
2.容器的分类:序列式容器和关联系容器
序列式容器:容器元素在容器中的位置有元素的时间和地点来决定的
如(vector 、 deque、 list、 stack 、queue)
关联式容器:容器具有自己的规则,元素在容器中的位置,由容器自己来决定,
如(树状容器、set/mutilset,map/mutilmap)
3.每个容器都有自己的迭代器,有容器自己提供
4.string容器:
1.从const char*到string有隐式转换;反之则没有
2.下标操作符:[] 和 at()方法。
两个的区别在于:
1)[]越界程序会直接挂掉
2)at()方法越界程序会提供错误信息
5.vector容器:
1)连续的内存空间
2)单口
3)会实现内存空间动态增长(当已有内存占满时);申请更大的空间,原来的空间析构掉,原来的额迭代器会失效
4)它的内存空间不会随着clear()方法清除数据而消失,只会当容器生命周期结束时,它的容量才会释放;
所以当想在生命周期存在时减少或增大容器内存,可以使用swap()方法。(如下)
void test04()
{
vector<int>V;
for(int i=0;i<10000;i++)
{
v.push_back(i);
}
cout<<"capacity:"<<v.capacity()<<endl;
cout<<"size:"<<v.size()<<endl;
//收缩内存
//vector<int>.swap(v);
vector<int>(v).swap(v);//匿名对象
cout<<"capacity:"<<v.capacity()<<endl;
cout<<"size:"<<v.size()<<endl;
}
6.reserve的使用:“预留空间”,在此空间还没有初始化时不允许访问,可以通过push_back()插入元素后,进行访问
7.resize的使用:开辟空间并初始化,它的空间申请完后是可以访问的
重新指定容器的长度num,若容器边长,则默认值或指定值填充新位置,如果容器变短,则末尾超出容器长度则被删除
只是边长变短,但是容器的容量没有变化,
8迭代器的注意事项:
1)(当内存由于动态增长,跟换地址后,原来的迭代器就会失效,不可以在使用了)
2)只要能遍历容器中所有元素的,都是容器认可的迭代器
3)每个容器的迭代器不一样,有自己的独立的迭代器
4)const_iterator 只读
reverse_iterator 逆序迭代器
iterator 最普通的正向迭代器
9.迭代器的种类 5种
1)每个容器值提供一种
2)都是由对应的容器自己提供的迭代器,因为每个容器的实现机制不一样
3)输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器
10.vector 申请和释放空间的注意:
尽量不要频繁申请和释放空间(太浪费时间);可以先预测一下所用空间的大小,直接用reserve()申请足够大的空间,在逐步初始化
11.随机迭代器:(vector容器支持随机访问)
12.deque容器:
1)是一种双向开口,动态以分段连续内存空间组合而成,随时可以增加一块新内存容器,
2)可以在首尾两端分别作元素和删除和插入操作。
---------------------------------------------------------------------------------------------------------------------------
1.栈容器(stack):元素先进后厨,单口容器
1)不提供迭代器:没有遍历功能
2)以其他的容器作为底层,它相当于提供接口‘
3)只有栈顶元素才可以被外界取用,所以向辨别能力容器,只能从栈顶开始,去除一个元素,就删除它,当遍历完之后,
容器也就没有元素了
2.队列容器(queue):元素先进先出
1)没有迭代器,不支持随机访问,
2)两个出口,一个只进,一个只出;
3)和stack栈容器一样,遍历完后,容器的元素也被删除空。
4.(stack 和queue 容器叫受限的线性表)
3.链表容器(list):双向循环链表
1)采用动态存储分配,不会造成内存浪费和溢出
2)元素插入和删除十分方便,修改指针即可
3)list容器提供自己的排序算法:
4)list容器提供的自己的排序算法;
5)swap()即可交换两个链表的数据,也可以动态的伸缩内存。
4.set容器(红黑树,平衡二叉树的一种)
1)它的元素即是键值对又是是实值,而且所有的元素都会自动排序。
2)不允许两个元素有相同的键值’
3)不允许通过迭代器修改元素的值
4)与list有某些相同的性质,当对容器中的元素进行插入和删除造作时,操作之前的所有迭代器,在完成操作完成后有效的,
除了被删除的那个元素的迭代器
5.multiset容器:
1)特性和set一样,唯一却别在于它允许键值重复
6.算法的默认排序原则,都是由小到大,如果想从到小,就需要自己建立一个回调函数
7.对组(pair):将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别对组的公有属性first和second访问
创建对象的两种方式:
1)pair<<string,int>pair<"xiaobai",20>;cout<<pair.first<<endl;cout<<pair.second<<endl;
8.map 容器:(它的所有元素都是一个对组)
1)键值对:键值不可以相同,实值可以相同,所有元素都会根据键值自动排序。
2)插入元素的四种方式:
map<int,int>m;
m.insert(pair<int,int>(1,2));
m.insert(make_pair(2.2));
m.insert(map<int,int>::value_type(3,2));
m[4]=4;
3)如果通过[]访问一个不存在的key值,那么编译器创建一个,实值为默认值
4)map的迭代器与普通容器不同模式一个队组迭代器,可以通过迭代器修改实值的值,不可以修改键值的值,
5)map的迭代器与list迭代器有某些相同的性质,对容器元素进行插入和删除时,操作之前的所有迭代器
在操作完成之后不会失效,当然那个被删除的元素迭代器除外
6)指定map的排序规则:(因为它的元素为对组,所以排序规则需要自己写函数确定)
9.multimap容器:
与map容器操作类似,唯一不同在于他的键值可以相同,
10.如何判断容器支持随机访问(或提供随机迭代器):
只需要看容器提供的迭代器能否 +2,+3;
vector<int>::iterator it=v.begin(); it = it+3;(可以提供随机迭代器)
queue<int>::iterator it=q.begin(); it= it +2;(不可向后跳跃,不提供随机迭代器)
11.STL中,所有的拷贝都是值寓意,所以提供的内容是可以拷贝的
1.此时就设计到深拷贝和浅拷贝的问题,当由指针存在,而且它指向堆内存时,用容器提供拷贝只会复制指针的指向,
并没有拷贝到指针指向的数据,当生命周期结束,进行空间析构时,就会出现统一块内存二次析构,程序会挂掉
2.必须自己重载一个深拷贝函数(和类的函数重载一样);(一般都是:一个拷贝函数和一个虫子啊=号操作函数)
12.STL 使用的时机