最近看到自己之前刚开始学习的时候记的一些笔记就稍微整理了一下
c语言里的结构体和c++里的结构体有什么区别?
答:c语言里的结构体不能定义函数,但是c++里的结构体可以定义函数
1.C的结构体和C++结构体的区别
(1) C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。所以C的结构体是没有构造函数、析构函数、和this指针的。
(2)C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。
(3)C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。
2.C++的结构体和C++类的区别
(1)C++结构体内部成员变量及成员函数默认的访问级别是public,而c++类的内部成员变量及成员函数的默认访问级别是private。
(2)C++结构体的继承默认是public,而c++类的继承默认是private。
第一章 类和对象
类是对象的类型,对象是类的实例。
成员变量:用来区分同一个类不同的对象。
静态成员:描述整个类的特征,如果static修饰函数,那么内部成员都可以调用其函数,如果static修饰类,那么每个声明的类都可以用,在c++中static是公用的。
非静态成员变量:描述不同对象的特征。
成员变量可以是各种类型的,比如;简单类型,指针,引用,复合类型,其他。
常量成员:必须在创建一个成员时进行初始化,初始化后不可修改。
成员函数:用来操作一个类的对象的函数。
静态成员函数:属于整个类的成员函数。
重*
*把类的数据成员对外隐藏,使之从外部不可见,称谓信息隐蔽。
*成员函数是给外部的访问接口。
*如果成员变量为私有(private),成员函数为公有(public),外部只能通过公有的成员函数来访问成员变量,把对象的私有数据保护起来,这种方式叫封装。
构造函数:用于创建生成一个对象,在没有写构造函数时,系统会提供默认的,还可以对数据成员的进行初始化,函数名和类名一样,没有返回值,不能为私有的,一旦为私有就不能对外,只能对内,就没有意义了。
构造函数调用时机:在创建对象时就会被调用,根据对象是否传参。
析构函数:用于销毁一个对象。
调用时机:不需要一个对象,释放对象时调用。
get函数:用于获取成员变量的值,可直接访问,访问函数。
set函数:用于修改成员变量的值,设置函数。
math.h数据函数文件。
using namespace stu;
cout…;
distance距离:实现距离的计算。
pow float类型计算第一个系数指数的值 pow doubtle类型
sqrt float类型计算平方根的函数 sqrtf double类型
类名::成员函数名
::作用域,全局作用域运算符,双目,自左向右只使用于当前的类。
**当我们的类当中有写构造函数,系统就不会在提供构造函数。
一般建议写带参的构造函数不建议多写。
默认构造函数;
*不是说构造函数没有参数,而是调用时可以不传参数。
*一个类可以有多个重载的构造函数,靠不同的参数列表来区分,
*类定义中,一定有一个且只有一个默认函数,
创建对象时不需要向构造函数传递参数,有两种情况?
1、构造函数本身无参数
2、构造函数有参数,但参数都有默认值,(好处可以让工程师在创建对象时有多个选择)
分类:带参数的并有初始值的构造函数,不带参数的默认构造函数
类中构造函数的使用特点?
1、类中无任何构造函数时,类外声明函数时,编译器就会提供默认构造函数(声明对象不能带参数;thing t;)。
2、如果类中有构造函数,编译器就不会提供默认构造函数,此时,类外声明对象,必须与类内的构造函数相匹配。
3、类中最多只能定义一个默认函数,但可以定义多个其他带参的构造函数;
4、一般情况下常规写法:建议程序猿能够自定义带参的默认构造函数(可以替代任何一个构造函数)。
宗旨:在类当中声明对象时,这个对象一定会在类中寻找和匹配声明的格式的构造函数。**//
内联函数:
inline functions
原理:用被调函数的函数体代码,替换掉函数调用语句,替换在编译期间发生。
优点:避免函数调用上的时间开销,提高程序的运行效率。
缺点:增加了程序在内存上的空间开销,消耗内存。
内联函数要尽量简单,代码短小。
inline int func(int a,int b)
{
return a+b;
}
**如果代码又可移植的需求,不要使用内联函数,可用宏定义代替。
函数重载:
function overloading
概念:两个函数名字相同,参数不同(包括参数个数不同或参数类型不同)即为函数重载。
1、不同的参数列表,调用重载函数是,编译器会根据实参的类型找到形参类型与之匹配的那个版本的函数。
2、重载函数必须有不同的参数列表(参数的类型或参数的个数不同)。
3、如果两个函数仅仅只是返回值类型不同,不能称为重载。
4、普通全局函数也可以构成重载。
初始化列表:
在构造函数的函数头,对类的成员变量进行初始化。
:初始化列表标志
:成员表量1(赋给成员变量的值),成员变量2(。。)。
赋给成员变量的值:构造函数的参数或常数。
&&&在类中仅仅对于类的构造函数使用。
常规初始化和初始化列表作用是相同的。
成员初始化顺序:
考点:
1、构造函数的初始化列表只说明用于初始化成员的值,而不限定初始化的具体执行顺序。
2、成员初始化的顺序于它们在类定义中出现的顺序一致。先执行初始化列表,再执行函数体;
构造函数初始化列表中初始化值的前后位置关系不会影响实际的初始化顺序。
3、一般初始化顺序没有要求,如果一个成员是用另一个成员来初始化,那么两个成员的初始化顺序就很关键。
*类对象的初始化的方式?(只能使用在类的构造函数中)
初始化列表的优势:
1、效率高;
*2、有一些类型的成员变量,只能用初始化列表方式:如:常量成员被const修饰过的,类中的引用类型类中有其他类作为对象的成员变量。(对应的类无默认构造函数)继承关系中(子类去初始化父类的成员时,父类无默认构造函数)对子类函数必须使用初始化列表。
三、引用总结
(1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。
(2)用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。
(3)引用与指针的区别是,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。
(4)使用引用的时机。流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
复合类:
复合类中成员变量,是另外一个类的对象。
*复合类最好用初始化列表形式,避免出错。
A类中数据成员包含B类的对象
A与B的关系:A has a B
构造A对象的过程:调用Circle构造->调用Point构造->执行Point函数->执行Circle函数
A中包含B对象,如果B中无默认构造函数,则A构造函数必须对B对象实现初始化列表
const
a、修饰成员函数:对成员函数的实现时,不能直接修改数据成员的值,在函数实现时也要写const,起到保护作用。
(类中的成员函数可以有const和非const版本(普通成员函数))
b、const修饰对象:只能调用const函数;
****普通对象 既可以调用普通函数也可调用const函数
c、const对象只能赋值给const的引用或const指针(const修饰*p)
如果复合类里的对象类没有默认的构造函数,就在复合类里采用初始化列表的形式。
在函数定义时,使用const的三种方式?
1、函数的返回值是const引用,即函数的返回值不能被修改
2、函数的参数是const 类型(引用),函数体内不能修改函数的值。
考点:**函数在传值时参数加引用的目的是为了在函数调用传参时节省内存空间。
3、const成员函数(只限于类的定义)。
8、析构函数 ~
、 概念:收回构造函数时申请的资源,垃圾回收
调用时机:1、离开对象的作用域
2、销毁对象,借助指针来删除它所指向的对象。
特点:无返回值 无参 唯一 公有 但是可以传参
实际应用;
当动态分配内存时,必须显示的销毁分配的堆上的空间。用delete回收指针指向的内存空间,避免内存泄漏。
前向声明:
生命一个类但是不去定义它。
1、只能以有限的方式使用,不能定义该类型的对象。
2、用于定义指向该类型的指针及引用,或者声明该类型作为形参类型或返回值类型的函数
3、只适用于指针,引用的定义,如果是普通类类型就要用#include了。
类和对象的使用?
1、可以向一个函数传递对象作为参数(类中对象也可以传值,传指针,传引用)
2、对象也可以作为函数的返回值(类中对象可以返回值,返回指针,返回引用)
3、为了避免对象的拷贝,经常传递对象的引用节省空间(或则const引用)
为什么传值返回值要创建临时变量?
创建临时的变量是用来接收函数的返回值的,让让函数的返回值有一个临时的存储空间。
什么叫浅拷贝?
可以把一个对象赋值给另一个对象,这个对象的值将一对一的拷贝到另一个对象里面,这种拷贝方式叫做逻辑拷贝,浅拷贝。
**在类中出现指针变量时,就不要用浅拷贝,因为这样会造成二次删除和内存的泄漏。
引用变量相对于无名称的指针
物理拷贝,也称为深拷贝深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
this指针:
1、每一个类中的非静态成员函数都有一个隐含的参数,叫做this
2、this是一个指针,指向对象自己
3、this中的地址就是对象自己在内存里的地址
面试题,今天老师提了但是没想起来***
为什么要返回char*?
因为要形成函数链,为了反复调用,为了下次调用所以要要返回char*。
函数链成立的前提:前两个函数都要返回circle对象。
自己也可以写拷贝函数circle(const circle &c)
如果返回值不是传引用的话,就会变成传值方式,还要创建一个临时的空间来存储形参的变量他是拷贝而不是本身。
****传引用方式可以节省空间,提高运行效率,可以一次性的修改对象的值
4、类的静态成员
静态局部变量:静态存储区
1、在两次函数调用期间可以保留原值在函数内部可见,函数外部无法被访问。
2、可以被自动初始化为0.
对于静态局部变量,只有在第一次进入函数时执行,之后便会跳过。
类的静态成员:
静态成员变量:必须在类声明后在类外使用::初始化
静态成员的值:和某一个具体的对象是无关的
对于非静态成员来说,每一个对象都有一个特定的值,每一个对象都会为自己的非静态成员在内存里保留一个位置
而静态成员,整个类中只有一份copy,不用每个对象都保留一份copy。
静态成员函数:被类的所有对象共享,不是和某一个特定对象,而是和整个类相关联。只可访问静态成员
访问方式:类名::静态成员名;
静态成员不属于任何类的对象,只属于类本身所有,为类创建的共有资源。
1、原则上静态成员变量(函数)不应该让对象来调用
2、静态成员函数的目的就是为了让多个文件来调用,减少了代码量
3、静态成员和变量可以让多个文件共享,增加程序的可维护性。
静态成员的初始化:数据类型 类名::静态成员变量名=初始值。
第二章 操作符重载
可以使用操作符,操作非默认类型的操作数
让同样的操作符执行不同的操作,称为操作符重载。
浅拷贝问题1、通过一个指针更改指向的数据,另一个指针的指向也变了。
**c++里对象与对象的直接进行相互操作的运算称为操作符重载此时,对象属于的类必须去实现运算符重载
2、两个指针指向同一块堆内存,会产生二次删除。
但是使用深拷贝就不会产生二次删除,需要重载赋值运算符。
如果一个类中定义的构造函数里包含指针,就一定要写析构函数。
实现深拷贝的步骤;
1、避免自赋值,就是要避免赋值给对象自己
2、要删除释放掉指针成员已经指向的内存,避免内存泄漏。
3、为指针成员开辟新内存空间(由赋值号右边对象指针指向的空间大小)
4、内容本身的拷贝,其他成员也需要拷贝
5、返回*this; *this赋值号左侧对象。
函数:类中成员函数
运算符重载
为什么要使用运算符重载?
为了实现对象与对象之间的操作运算。
几个重要的运算符重载:=,==,<<,>>,算数(+-*/ ++ —-)。
赋值运算符重载:v1=v2;
vector v1;
vector v2;
类中无指针变量成员(对象成员之间的直接赋值-浅拷贝)
类中有指针变量成员,指针指向堆空间(对象指针成员之间的赋值-深拷贝)
重载==运算符
步骤:
1、判断元素长度是否相等。
2、相等再具体比较数据元素内容。
函数:类的成员函数,全局函数,友元函数
输出运算符重载:
c++中的标准输入(istream)输出(ostream)的类对象为cin和cout(唯一的,不能被覆盖)
函数:类的友元函数。
类的友元函数的特点:
1、可以在类外调用类中的私有成员
2、破坏了类的封装性
第三章 拷贝构造函数和析构函数
拷贝构造函数的特点?
定义 :通过拷贝一个已有对象,来创建一个新的对象,也就是说,这个构造函数的参数就是另一个对象
另一个对象就是指哪个已有对象
拷贝构造函数的调用时机:用一个已有的对象来初始化新对象
参数;该类的引用类型不能为纯对象(递归调用拷贝构造)
调用表现形式:
1、用已经存在的对象,去初始化另外一个对象:
int a=10;
int b=a;
b=20;
2、声明一个对象,并用另一个对象初始化
classt object(another_object)
classt object=another_object
在类中没有指针时,创建一个新的对象,系统会自动调用默认的拷贝构造函数(浅拷贝)
但是类中有指针的话,拷贝函数就要自己去定义,避免二次删除。
拷贝函数的写法
类名(const 类名& a)
{
*this =a;
cout<<“类名(类名a)”
}
const 类名& operator=(const 类名& a)
{
//…..
};
3、用传值方式,从函数返回一个对象或作为函数的形式参数,也将调用拷贝构造函数。
声明一个设置函数
void set(int a,int b)
{
this->a=a;
this->b=b;
}
声明一个重载
void operator=(const 类名& a)
{
*this=a;
return *this;
}
析构函数:
1、会被自动调用
2、和类名相同,前面加~
3、没返回值
4、在类中没有指针成员时可以不定义析构函数,系统会自动的提供默认析构函数,默认的析构函数仅仅只释放指针本身内存。
5、自类中有指针成员时,就要自己定义一个析构函数了,用delete来释放堆内存,避免内存的泄漏。
三 * c++三大函数
拷贝构造函数,析构函数,类的复制运算符重载。
应用:当一个类中有指针成员时,指针指向堆空间时,为了对象与对象之间实现深拷贝,必须要程序员定义三大件,可以避免产生二次删除和内存泄漏
**一定要区别赋值运算符重载,拷贝构造函数,析构函数的调用时机
第四章 继承
1、继承的作用?
(1)、可以在子类中减少代码量
(2)、方便维护程序(只需要修改父类的程序,子类的就会自动随着改变)
2、子类自动继承父类的所有成员变量和成员函数,(除了构造函数,析构函数,赋值运算符重载函数)
、
3、面向对象最主要的目标:
实现代码重用,通过继承,子类自动拥有了父类的代码。
4、为什么要有子类?
为了在继承父类的同时,也增加一些自己的代码。
子类(派生类)成员:继承父类的部分,子类自身扩建的成员。
子类也拥有父类的私有成员,但是子类不能通过自己扩展的函数来访问父类的私有成员,应该通过继承的父类的成员函数来访问。
父类(基类):为子类提供共有的特征。
****重点
5、继承与重载的区别?
(1)子类可以自己实现与父类的成员函数相同的成员函数,称为覆盖,覆盖是函数重载的特例,覆盖一定是发生在继承的过程中。
(2)发生函数覆盖时,根据调用者对象本身的类型来确定,无法根据参数列表确定。
(3)在子类中味了访问父类成员:独立访问时:父类名::访问成员
**重点内容
c++中函数重载与函数重写的区别?
1.重载:要求函数名相同,参数列表不同,重载只是在类的内部存在。与返回类型无关,故不能靠返回类型来判断重载;
2.重定义(隐藏):要求函数名相同,函数类型为非虚函数,与返回类型、函数参数列表无关。重定义是发生在继承子类与父类之间。
当子类的函数名和参数列表和父类相同时,这是要看父类函数是否是virtual虚函数,如果是则是重写,如果不是则是重定义;
3.重写(覆盖):要求函数名相同,参数列表相同,函数类型为virtual虚函数,与返回类型无关,重写是发生在继承子类与父类之间。(注意:被重写的函数不能使static,必须是virtual)。
1 成员函数重载特征:
a 相同的范围(在同一个类中)
b 函数名字相同
c 参数不同
d virtual关键字可有可无
2 重写(覆盖)是指派生类函数覆盖基类函数,特征是:
a 不同的范围,分别位于基类和派生类中
b 函数的名字相同
c 参数相同
d 基类函数必须有virtual关键字
3 重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
注意区分虚函数中的重载和重写:
class A{
public:
virtual int fun(){}
};
class B:public A{
int fun(int a){} //这是重载而不是重写:
}
int mian()
{
}
class B:public A{
int fun() // 从A继承来的 fun, 编译器会自己偷偷帮你加上
int fun(int a){} // 新的fun, 和前面的只是名字一样的重载函数, 不是虚函数
}
**6、继承方式对子类继承自父类的成员的访问权限的影响?
(1)子类中从父类继承的成员,其访问权限不能高于继承声明时使用的访问权限
(2)私有继承:子类中继承的所有的成员(成员变量,成言函数)都变成私有成员
(3)保护成员:子类中继承的私有成员和保护成员不变,公开成员变成保护成员。
(4)公开成员:子类中的继承的所有成员的权限不变,和父类一样。
7、继承的应用?
has-a:复合类(a中有b)
is_a:继承(a是b)
(1)当两个类之间一个类是另一个类的特例,用继承来实现类之间的层级关系一般用公有继承
(2)为实现代码的复用,也可以用继承
(3)一个类想用另一个类的成员函数时,就把一个类声明成另一个类的子类
(4)继承可以使修改更容易,父类改动以后,子类自动的随之修改,无需在去修改子类
**子类中的构造、析构、和赋值运算符重载函数 重点**
1、在子类构造函数中调用父类构造函数,初始化来自于父类那部分的成员,(作用:代码复用)。在初始化自己扩展的成员。
2、如果在子类函数的内部,调用父类的构造函数,会怎样?
(1)会创建一个局部的base对象,而子类自己的对象并没有被初始化。
(2)如果在子类的构造函数中不显示的调用父类的构造函数,系统就会自动的调用父类的默认构造函数。
(3)析构函数无参数,在子类的析构函数中,系统会自动的调用父类的析构函数。
3、在子类中写三大件时一定要注意父类的也要写,调用,千万不要忘记
4、在拷贝构造函数中,也是父类的成员归父类初始化,把作为子类拷贝构造函数的参数的那个子类对象,传递给父类的拷贝构造函数,然后再由子类自己初始化子类的成员。
5、构造子类时的执行顺序:先执行父类的构造函数,然后再执行子类的构造函数。
析构函数时,先执行子类的析构函数,再执行父类的析构函数。
6、关于子类对象和父类对象之间的赋值关系?
(1)一个子类对象赋值给父类对象可以,但是反之不可。
(2)子类对象的地址赋值給父类指针,父类指针指向子类对象,反之不可以。
(3)子类对象赋值父类引用,父类引用指向子类对象,反之不可以。
7、 虚函数
概念:当父类指针或父类引用指向子类对象,而子类中又覆盖了父类的函数,希望用父类指针或父类引用,调用到正确版本的成员函数,需要把该成员函数声明成虚函数
(1)写了虚函数后,调用的是该函数对象本身的类型,而不是指向那个对象的指针或引用类型。
(2)对象的内存空间是为谁开辟的就调用谁的成员方法,或成员变量。
8、使用虚函数的目的?
(1)期望父类指针或引用,不管指向父了句还是子类,在调用虚函数时,可以反映真实情况。
(2)有了虚函数后,无需向下转型,就可以正确的用父类的指针或引用,调用到子类的虚函数。
(3)可避免继承关系里向下转型的不安全性。
9、如果函数的参数是传值方式,形参是父类的对象,实参是子类的对象,则在函数内部,用形参调用的成员函数,依然是父类版本,因为传值只是将子类对象給父类对象赋值,父类对象不是指向子类的指针或引用。
10、如果一个类有子类,则这个父类的析构函数必须是虚函数,即虚析构
如果父类不是虚析构,则当删除一个指向子类的对象的父类指针时,则将调用父类版本的析构函数,子类只释放了来自父类的那部分成员变量,没有释放子类自己扩展的成员变量,造成内存泄露。
**动态绑定
1、概念:虚函数在被调用时候,到底会调用那个版本不知道,就是在编译的时候是无法确定的,只有在执行的时候才会知道,这个过程就叫动态绑定。
动态绑定就是实现多态性:使得代码维护起来更加容易。
第六节 虚函数的实现原理
当一个类存在虚函数后,这个类在编译时系统为其创建一个虚函数表(存储该类中所有虚函数的地址)
用这个类所创建出来的对象就会多出一个虚指针,它指向虚函数表的首地址。
1、使用虚函数的代价?
(1)虚函数的调用时间比其他函数要慢10%-20%,增加程序运行时间,降低时间效率。
(2)虚函数指针和虚函数表增加了内存的空间消耗,和运行时间的消耗。
2、一般继承的总结?
(1)虚函数按照其声明的顺序放于表中。
(2)父类的虚函数在子类的虚函数前面
多态性
实现条件:
1、继承关系:子类必须继承父类
2、子类必须重写父类的虚函数(重虚函数)
3、父类指针(引用)指向子类
4、调用虚函数(实现动态绑定)
多态编程的设计
父类:作为抽象类(抽象类:有纯虚函数的类,不能实例对象,不需要实现纯虚函数,虚函数被声明时初始化为0,无函数体),这样的父类即为抽象基类。为子类提供共有的功能接口。
3、为什么在子类中的虚函数覆盖父类的虚函数时在虚函数表中排在前面?
因为是潜在的机制。
base *b=new derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被子类的虚函数位置代替,于是在实际调用时,是子类的虚函数被调用了,这就实现了多态。
4、多重继承(无需函数覆盖的情况下)
多个负类指向一个子类,即一个子类继承多个父类。
总结:
(1)每一个父类都有自己的虚函数表
(2)子类的虚函数被放在了第一个父类的表中。(所谓第一个父类是按声明的顺序决定的)
(3)主要是为了解决多个父类指向一个子类时能够调用到实际的函数。
5、多重继承(有覆盖)
当三个父类指向一个子类时,如果在子类的声明中第一个虚函数覆盖了父类,就会在虚函数表中三个父类的第一个虚函数都被子类覆盖了。
6、多态的理解
(1)用一个父类的指针指向子类的对象,在函数调用时可以调用到正确的版本的函数。
(2)用一个父类的指针当函数的形参,用这个指针可以接受到其他的子类对象也包括自己。
(3)在复合类中尽量引用高层次的类(父类的指针)当做类的成员变量,这样就可以通过它创建出它所对应的任何子类的对象包括它自己。
7、什么是纯虚函数?
就是当一个类中有虚函数并且虚函数初始化为0就称为纯虚函数,纯虚函数不用在父类中实现,因为实现没有意义,纯虚函数在父类中就相当于接口,供各个子类使用,以实现各个子类自己的功能。
8、多态的实现?
(1)首先在父类中定义虚函数并对虚函数初始化为0,函数体不用实现
(2)在子类中继承了父类中的虚函数,在不同子类中对虚函数进行实现,如果创建子类对象,必须实现抽象基类中的所有的纯虚函数,否则,子类也会成为抽象类。
(3)一个父类的指针或者引用指向任意一个子类对象,通过指针或者引用去调用时会实现它实际的功能。
多态的实现有以上几点,在指针调用不同子类时会呈现不同的特点。
9、抽象类:拥有纯虚函数的类就叫做抽象类。抽象类提供了不同的种类对象的一个通用接口。
10、能不能创建抽象类的对象?
不可以。因为抽象类中的纯虚函数并没有被实现。
抽象类只能作为基类使用,即抽象基类。
注意事项:
(1)不能单独调用抽象类的构造函数,仅可用于子类构造函数的初始化列表中,用于初始化子类中的继承于父类的部分
(2)抽象类并不是必须写上析构函数,但是非要写上的话就必须是虚析构函数。
(3)不能以传值的方式,像一个函数传递抽象基类的参数。
11、如果形参是抽象基类,实参是子类,可以用传值吗?
不可以,因为这样就相当于子类对象创建了一个抽象基类的对象,而后者不允许。
但是可以用传指针或者传引用,返回值也一样。
抽象类的应用:
(1)一般在设计一个应用函数时,将函数的形参指定为父类的指针或引用类型
(2)调用时将子类对象传递该函数的形参。
(3)函数内部实现时用形参指针或引用直接调用纯虚函数,表现出多态性。
12、RTTI(运行时类型转换)
动态转型(动态类型转换):dynamic_cast
作用:经常用来检查一个基类指针是否实际指向的是派生类对象,从而体现多态性
静态转型(静态类型转换):static_cast -和c语言中的强制类型转换相同,向下转型不安全
dynamic_cast<t>(objectptr)形式
objectptr:必须是父类指针或引用
t:必须是子类指针或引用
转型方式:objectptr指向的对象是否为子类对象
是,则返回子类对象的地址(并发生向下转型)(安全)
不是,则返回0
目的:为了实现多态性,在实现过程中避免发生“子类的指针或引用的指向父类对象”能够安全的体现多态性。
第五章 标准c++模版库
名字空间定义了全局标示符的作用域
名字空间作用:避免同一个程序中,不同文件里的全局标示符的冲突。
1、全局变量:在所有的函数体之外声明的变量。
作用域:能够访问某段标示符的程序段。
静态变量也可以放在.h里面,局部变量必须放在.c里面。
函数域:只能在函数的内部被访问到,只能在它所在的函数被访问到。
程序块:花括号的包围,被包围的就是作用域。
标示符可以在整个源文件中被识别,从声明的位置到文件结尾,全局标示符:全局变量和函数名
当在快内部声明了一个与块外部标示符相同的标示符时,外部标示符可以被临时隐藏。
2、extern:从外部的文件引入到当前变量的文件。
分离式编程:.h .c .cpp是分开使用的
一般定义:类型的定义和函数原型的声明
特殊情况下:可以声明和定义(初始化),一些static变量和全局变量。
3、编译:从源文件到目标文件。
链接:把多个目标文件合并成单一的可执行文件。
如果想在多个文件中访问一个变量(全局),就要在变量前面加上extern关键字。
.h 仅仅起到说明的作用。
4、在c++语法当中,static可以通过全局作用域标示符被访问(在头文件被包含的情况下)。
静态变量被分为:静态全局变量和静态局部变量。其生命周期为整个程序。、但是静态变量的作用域范围:static修饰全局变量,作用域为本文件内; static修饰局部变量,作用域为函数域或块域。
被static修饰的变量,延长了生命周期,缩小了作用范围!
全局变量在编译时只是去申请空间static是在编译时直接初始化。
在面向对象的开发里指针用的比价多,因为指针灵活。
一个字符串可以存储在栈上,堆上,常量字符区。
只有存储在栈上的字符串有标示符。
5、string类
char* p=“hello”
这个类没有名字,为什么?
常量字符串没有被定义,它是由栈上的指针p指向的常量字符串首地址
6、print(“string ling”);这种写法效率最高,建议用这种写法
还有其他的两种:
(1)string b=“string ling”;//不提倡,有可能创建两个对象,效率低
print(b);
(2)const char * p=“string ling”;//会形成临时变量
print(p);
(3)string c(“string ling”);//只创建一个对象,效率高
7、下标运算符重载
使用时机:当类定义的时候,如果有指针作为成员变量,并且该指针操作的是指向堆上的数组形势的数据时,则最好实现下标运算符重载函数(类的成员函数有const 版本和非const版本)。
const char& operator[](int index)const //const
{
return rep[index];
}
char& operator[](int index)
{
return rep[index];//no const
}
8、c++里的string类取代了c语言里的char*类型以’\0’结尾的字符串。
原因:
(1)以‘\0’结束的c字符串,容易出现越界等各种内存问题
(2)把字符串数据与成员函数封装在一起,提供更多的功能。
string的特点:和c语言里的相比不用担心内存是否足够,字符串长度等等。
9、string类的特点:
(1)string类也是在std名字空间定义
(2)程序中想使用字符串,必须包含#include<string>和标准命名空间
(3)声明字符串对象,可以不穿参数。
(4)声明字符串对象可以接受常量字符串作为参数,但不可以为单一字符。
(5)可以用隐式类型转换构造函数来创建字符串对象
(6)类string也定义了拷贝构造函数和赋值运算符重载函数
(7)字符串对象的长度是字符串中有效字符(不包含‘\0’)的个数,通过length()函数来返回(或者说通过size()函数)
(8)string类重载了下标运算符,代表访问的是字符串中的哪一个字符
(9)运算符+ 也被string类重载来连接两个字符串对象
(10)string对象同样还可以使用==,!=,<,>,>=,<=等比较运算符
(11)还有一些其它的成员函数如:插入(insert),删除(erase),替换(replace),增加(append).
在string类中想要进行字符串相加一定要左右两边数据类型相一致的。
在string类中的功能有
判断字符串是否为空
if(s.empty())
{
cout<<“字符串为空”<<endl;
}
else{
cout<<“不为空”<<endl;
}
s2.empty()?cout<<“s2为空”<<endl;:cout<<“不为空”<<endl;
还可以进行排序
重点 模版
函数模版:声明定义时候是函数模版
模版函数:在主函数或用到的地方调用时就是模版函数
模版类
template <class T>
class stack
{
}
注意:在模版类中定义的函数在实现时也要加上template <class T>
补充知识:
标准c++库的详细消息均在其对应的头文件进行了说明。主要标准c++库头文件如下所示。其中13项为标准模板库(STL),在其说明文字的前面标有(STL)的为标准模板库。
<algorithm>---(STL)用于定义实现常用、实用算法的大量模板
<bitset>----- 用于定义官位位集合的模板类
<cassert>-----用于在程序执行时执行断言
<cctype>-----用于对字符进行分类
<cerrno>-----用于测试有库函数提交的错误代码
<cfloat>------用于测试浮点类型属性
<cios646>----用于在ISO646变体字符集中编程
<climits>-----用于测试整数类型属性
<clocale>-----用于使程序适应不同的文化风俗
<cmath>———用于计算常用的数学函数
<complex>-----用于定义支持复杂算法的模板类
<csetjmp>-----用于执行非局部的goto语句
<csignal>------用于控制各种异常情况
<cstdrag>-----用于访问参数数量文化的函数
<cstdarg>-----用于访问参数数量变化的函数
<cstddef>----用于定义实用的类型和宏
<cstdio>-----用于执行输入和输出
<cstdlib>----用于执行同一操作的不同版本
<string>-----用于处理几种不同的字符串类型
<ctime>------用于在几种不同的时间和日期格式间进行转换
<cwchar>----用于处理宽流(wide stream)和字符串
<cwctype>---用于对宽字符(wide character是)分类
<deque>---(STL)用于定义实现双向队列容器的模板类
<exception>---用于定义控制异常处理的几个函数
<fstream>-----用于定义处理外部文件的几个iostream模板类
<functional>-----(STL)用于定义几个模板,该模板将帮助在<algorithm>和<numeric>中定义的 模板构造谓词
<iomapip>---- 用于声明一个带有参数的iostreams控制器
<ios>-----用于定义用作大量iostreams类的基类的模板类
<iosfwd>-----用于定义iostreams模板类(在需要定义之前)
<iostream>---用于声明处理标准流的iostreams对象
<istream>---用于定义执行析取操作的模板类
<iterator>----(STL)用于定义帮助定义和管理迭代器的模板
<limits>---用于测试数字类属性
<list>---(STL)用于定义实现list容器的模板类
<locale>----用于定义在iostreams类中控制与特定位置相关的行为的类和模板
<map>------(STL)用于定义实现关联容器的模板类
<memoery>-----(STL)用于定义对不同容器分配和释放内存的模板
<numeric>-----(STL)用于定义实现实用数字函数的模板
<ostream>----用于定义管理字符串容器的iostreamas模板类
<queque>----(STL)用于实现队列容器的模板类
<set>-----(STL)用于定义实现只有唯一元素的关联容器的模板类
<sstream>----用于定义管理字符串容器的iostreams模板类
<stack>-----(STL)用于定义实现堆栈容器的模板类
<stdexcept>----用于定义提交异常的类
<streambuf>----用于定义为iostreams操作分配缓冲区的模板类
<string>------用于定义是实现字符串容器的模板类
<strstream>-----用于定义处理非内存(in-memory)字符系列的iostreams类
<utility>-----(STL)用于定义通用工具的模板
<valarray>----用于定义支持值(value-oriented)数组的类和模板类
<vector>----(STL)用于定义实现向量容器的模板类
标准c++库还包括18个标准C库中的头文件,但其中有些变化。我们暂时不讨论,这些头文件为:
<assert.h>---用于在程序运行时执行断言
<ctype.h>----用于对字符分类
<errno.h>----用于测试用库函数提交的错误代码
<float.h>----用于测试浮点类型属性
<ios646.h>-----用于在IOS646变体字符集中编程
<limits.h>-----用于测试整数类型属性
<locale.h>-----用于适应不同的文化习俗
<math.h>----用于计算常见的数学函数
<setjmp.h>----用于执行非局部的goto语句
<signal.h>----用于控制各种异常情况
<stdrag.h>-----用于访问参数数量变化的函数
<stddef.h>-----用于定义类型和宏
<stdio.h>------用于执行输入和输出
<stdlib.h>------用于执行各种操作
<string.h>-----用于处理字符串
<time.h>-------用于在不同的时间和日期格式之间转换
<wcchar.h>-----用于处理宽流(wide stream)和字符类
<wctype.h>-----用于对宽字符(wide character)分类
容器
STL的容器可以分为以下几个大类:
一:序列容器, 有vector, list, deque, string.
二 : 关联容器, 有set, multiset, map, mulmap, hash_set, hash_map, hash_multiset, hash_multimap
三: 其他的杂项: stack, queue, valarray, bitset
STL各个容器的实现:
(1) vector
内部数据结构:数组。
随机访问每个元素,所需要的时间为常量。
在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存。
vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同)。当把超过capacity()-size()个元素插入vector中时,内存会重新分配,所有的迭代器都将失效;否则,指向当前元素以后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素以后的任何元素的迭代器都将失效。
(2)deque
内部数据结构:数组。
随机访问每个元素,所需要的时间为常量。
在开头和末尾增加元素所需时间与元素数目无关,在中间增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,不提供用于内存管理的成员函数。
增加任何元素都将使deque的迭代器失效。在deque的中间删除元素将使迭代器失效。在deque的头或尾删除元素时,只有指向该元素的迭代器失效。
(3)list
内部数据结构:双向环状链表。
不能随机访问一个元素。
可双向遍历。
在开头、末尾和中间任何地方增加或删除元素所需时间都为常量。
可动态增加或减少元素,内存管理自动完成。
增加任何元素都不会使迭代器失效。删除元素时,除了指向当前被删除元素的迭代器外,其它迭代器都不会失效。
(4)slist
内部数据结构:单向链表。
不可双向遍历,只能从前到后地遍历。
其它的特性同list相似。
(5)stack
适配器,它可以将任意类型的序列容器转换为一个堆栈,一般使用deque作为支持的序列容器。
元素只能后进先出(LIFO)。
不能遍历整个stack。
(6)queue
适配器,它可以将任意类型的序列容器转换为一个队列,一般使用deque作为支持的序列容器。
元素只能先进先出(FIFO)。
不能遍历整个queue。
(7)priority_queue
适配器,它可以将任意类型的序列容器转换为一个优先级队列,一般使用vector作为底层存储方式。
只能访问第一个元素,不能遍历整个priority_queue。
第一个元素始终是优先级最高的一个元素。
(8)set
键和值相等。
键唯一。
元素默认按升序排列。
如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
(9)multiset
键可以不唯一。
其它特点与set相同。
(10)hash_set
与set相比较,它里面的元素不一定是经过排序的,而是按照所用的hash函数分派的,它能提供更快的搜索速度(当然跟hash函数有关)。
其它特点与set相同。
(11)hash_multiset
键可以不唯一。
其它特点与hash_set相同。
(12)map
键唯一。
元素默认按键的升序排列。
如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
(13)multimap
键可以不唯一。
其它特点与map相同。
(14)hash_map
与map相比较,它里面的元素不一定是按键值排序的,而是按照所用的hash函数分派的,它能提供更快的搜索速度(当然也跟hash函数有关)。
其它特点与map相同。
(15)hash_multimap
键可以不唯一。
其它特点与hash_map相同。
1、C++标准:
命名空间:将C++语法中全局的标志符定义在一个通用的作用域中,可以在工程的任何地方使用,(保护和封装)
目的:避免同一个工程中不同文件相同标志符的冲突。
使用:namespace 名称{…}; using namespace 名称;
名称::全局成员(变量/函数)
string类:#include<string>using namespace std;
自定义实现c++中的字符串类型
原理:1、string类作为c++标准库中的一个模版类
string<char*>
2、概念:作为一个字符串类型(类类型)-》变量(对象)
3、类型转换构造函数:参数是(c_const char*)防止指针指向常量字符串的无效操作。(去除对c语言中用常量指针访问常量字符串,该指针定义为字符串的误解)
4、掌握string类中常用字符串函数的用法,比如;删除,插入,清除,添加,排序等等。
模版:
1、函数模版:c++stl泛型
具有一类通用功能的函数的形势。
模版函数:调用该形式函数时,实际传入的参数类型确定后,编译器就会讲抽象形式转换为实际的函数定义。(泛型函数)
2、类模版:具有一类通用属性和行为的类的定义形式。这样的一类通用的形势称之为类模版。
模版类:将抽象形式的类转化为实际的类的定义,在明确或确定该模版种具体操作的数据类型时,将抽象形式的类转化为实际的类的定义。(泛型类)
2、sol=容器(泛型类)+算法(泛型函数)+迭代器(游标)
容器分类:
顺序容器,关联容器
顺序容器:vector(向量容器):连续存储(数组)只能在尾部添加和删除元素
list容器(列表容器):有结点组成的双向链表,(链式存储)可以在任意位置添加和删除元素
deque(队列容器):指向不同元素的指针组成的数组,可以在头部或尾部添加和删除元素。
关联容器:
set集合:只有唯一的key-value,通过key来访问value,key==value.通过红黑树的思想来定义的
map映射(集合):一对一的key-value,一对数,pair<key,value>,也是通过key来访问value;
multi map:可以有重复的多个key,或重复的多个value存在。
algorithms:算法(定义了与容器中相关的功能函数)泛型算法
iterator:迭代器(类模版)通过对类中元素进行访问,类似于指针。
3、stack:容器适配器
是一种数据结构的存储方式,(lfo)的特点
适配容器(value_type)
实现对元素进行入栈,出栈,占内容满、空获取栈定元素等操作。
<stack>、<容器>进行访问操作。
重;
4、要求:自己必须要会的事情
(1)自定义stack,实现相关功能
(2)自定义vector,实现相关功能
(3)自定义string,实现相关功能。
(4)容器栈,自定义vecstack,实现多态性。//一道面试题,,,,,很重要
如何正确去使用c++标准库;
回调函数
就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。