宋新晨

博客园 首页 新随笔 联系 订阅 管理
  8 随笔 :: 0 文章 :: 0 评论 :: 1222 阅读

C++

导论

编译型语言

应用领域:桌面软件开发、游戏编程、网络编程、系统编程

C++基础

C++是C的超集,表现在:

1)在面向过程编程的基础上,对C语言功能进行补充;

2)增加了面向对象机制

C++的I/O方式

输入:输入设备>输入流>cin>变量

输出:变量>count>输出流>输出设备

C++的输入输出方式不关注数据类型,不关注占位符

变量随用随定义

新的初始化方法:

1)复制初始化:int x=1024;//赋值初始化

2)直接初始化:int x(1024);//函数初始化

3)统一初始化:int x{1024};//初始化列表

命名空间:区分不同的同名函数,避免了命名冲突的问题

标准C++库的所有标识符都定义在命名空间std中

main函数不能定义在命名空间中

使用namespace定义命名空间

面向对象

分析出整个问题共涉及到哪些功能模块,而不是步骤

三大特征:

1)封装:隐藏信息

2)继承:扩展类的功能

3)多态:方法的重载,对象的多态性

不同的对象,同样的操作,不同的结果

每个对象有两个要素:

属性---数据

行为---操作代码,即函数

访问限定符:

public:可被该类中的函数、子类的函数、其友元函数,该类的对象访问

private:只能由该类中的函数、其友元函数访问

protected:可被该类中的函数、子类的函数、以及其友元函数访问,不可以被该类的对象访问

类的实例化

从栈中实例化

从堆中实例化 Dog *p=new Dog(); delete p;

数据成员的访问与C语言中结构体一致

数据的封装

set成员函数、get成员函数

封装的好处:1)防止非法或不合理赋值

2)只读属性

3)修改扩充类的功能·,只需修改类中的数据成员和成员函数,类外部分不用修改

类内定义与内联函数

若一个函数被指定为inline函数,将它在程序每个调用点上被“内联”展开

类内定义的函数优先选择编译为内联函数

C++允许在类内声明成员函数的原型,然后在类外定义成员函数

类和对象

构造函数

构造函数的规则和特点:

构造函数在对象实例化时被自动调用

构造函数与类同名

构造函数没有返回值

构造函数可以有多个重载形式

实例化对象时仅用到一个构造函数

当用户没有定义构造函数时,编译器自动生成一个构造函数

构造函数一般定义为public

一个类只能有一个默认构造函数

构造函数分为:

1)无参构造函数

2)有参构造函数

3) 拷贝构造函数

拷贝构造函数

格式:类名(const 类名 &变量名)

如果没有自定义的拷贝构造函数则系统自动生成一个默认的拷贝构造函数

初始化列表

Animal(int weight,int height):
m_weight(weight),m_height(height)
{}

初始化列表只能用于构造函数

初始化列表效率高,初始化数据成员首选

成员变量的初始化顺序由类定义中声明变量的顺序决定

使用初始化列表的必要性

可以为const类型的变量初始化

析构函数

一个类只有一个析构函数

析构函数会在对象销毁是自动调用,在对象被删除之前完成一些清洁工作:

1)在一个函数内定义的对象当这个函数结束时,自动执行析构函数释放对象

2)static局部对象要到main函数结束或执行exit命令时才自动执行析构函数释放对象

3)全局变量(在函数外定义的对象)当main函数结束或执行exit命令时才自动执行析构函数释放对象

4)如果用new建立动态对象,用delete时自动执行析构函数释放对象

对象成员

对象成员的互相包含

构造函数的调用顺序:先调用内置对象的构造函数

析构函数的调用顺序:先析构最外面的对象实例

对象数组

可以在定义数组是提供实参以实现初始化

可以在等号后的花括号中为每个对象分别写出类名并在括号内指定实参

对象指针

指向对象的指针

new运算符实例化对象

Coordinate *p=new Coordinate();

指向对象成员的指针

存放对象的起始地址的指针是指向对象的指针;对象成员也有地址

存放对象成员地址的指针就是:

指向对象成员的指针

指向数据成员的指针

指向成员函数的指针

定义指向成员函数的指针变量

数据类型 (类名::*指针变量名)(参数列表)

给指针变量赋初值

指针变量名=&类名::成员函数名

取成员函数的地址

& 类名::成员函数名

用指针变量调用成员函数

(对象名.*指针变量名)(【实参表】)

别名

typedef void (A::*PMF)(char *,char *);

PMA pmf=&A::speak;

//pmf是PMF类型(类A成员指针)的变量

为复杂的声明定义一个新的简单别名

对象指针成员

类中包含另一个类的对象指针

this指针

每个成员函数都包含一个特殊的指针this,它是指向本类对象的指针

成员函数中数据成员名的前面隐含有“this->”的指向

对象赋值与复制

浅拷贝

将数据成员的值进行了简单的拷贝

class Array
{
    private:
    int *m_pArr;
	public:
    Array(const Array&arr)
    {
        m_pArr=arr.m_pArr;//指针指向同一位置
	}//会导致重复释放
	    
}

深拷贝

class Array
{
    private:
    int *m_pArr;
	public:
    Array(const Array&arr)
    {
        m_pArr=new int [2];
	}
	    
}

对象的赋值

如果一个类定义了两个或多个对象,则这些同类对象之间可以互相赋值

注意:对象的赋值只对数据成员操作,数据成员中不能含有动态分配

对象的复制

调用拷贝/复制构造函数

公用数据的保护

常对象

格式:const 类名 对象名(实参表);

定义常对象时,必须对其进行初始化

如果一个常对象的成员函数未被定义为常成员函数(构造函数和析构函数除外),则不能调用

void getX() const;

常对象成员

将成员声明为const

常数据成员

格式:const 类型 数据成员名

//只能通过带参数初始化列表的构造函数对常数据成员进行初始化

Coordinate::Coordinate(int x,int y):m_iX(x),m_iY(y)
{}

常成员函数

void changeX()const;

常成员函数不能修改数据成员的值

不能调用没有用const修饰的成员函数

可以被类中其他的成员函数调用

常对象只能调用常成员函数可以实现重载,有普通对象调用普通的成员函数,常对象调用常对象函数

对象指针与对象引用

常引用/常指针:只读权限,只能调用常成员函数

引用用法

1.引用作为参数:在内存中没有产生实参的副本,直接对实参操作

2.常引用:

3.引用作为返回值:1)以引用返回函数值,定义函数时需要在函数名前加&

​ 2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本

引用主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。

静态成员

关键字:static

静态数据成员必须在类外专门初始化

访问:1)直接通过类名引用

​ 2)通过对象名来引用

静态数据成员在对象外单独开辟内存空间,且在程序开始时只诞生一次,直到程序结束释放空间!

静态成员函数不能直接调用非静态成员函数和非静态成员变量

不能直接访问非静态成员

因为静态成员函数中参数无this指针,无法确定调用的是哪个对象的成员

静态数据成员必须在类外单独初始化

只有静态常量数据成员才可以在类定义中初始化

友元

友元函数

友元全局函数:friend 类型 函数f1(类1 &对象) 直接访问私有成员

友元成员函数:friend 类型 类2::成员函数f2(类1 &对象) 声明在本类中,定义在类2中

类1是本类类名

类2是另一个类的类名

友元类

class Circle;//提前声明
class Coordinate
{
    friend Circle;
    ````

}

友元的注意事项

友元关系不可传递

友元的单向性

友元声明的形式及数量不受限制

友元是C++提供的一种破坏数据封装和数据隐藏的记指

建议少用

模板

关键字:

template

typename

class

函数模板

template < class 类型参数名>

template < typename 类型参数名>

**格式: 函数模板名< 实际类型名> **(实参表)

typename 和class可以混用

template <typename T, class U>

T minus( T *a,U b);

类模板

template

void MyArray :: display()

{

……

}

成员函数类外定义,大有不同

template 要给出

成员函数的所属类名的后面用< >括起来,写出类型参数

使用类模板时,定义对象的格式:

类模板名 <实际类型名> 对象名;

类模板名 <实际类型名> 对象名(实参表);

运算符重载

关键字:operator

方法:定义一个重载运算符的函数

函数类型(即返回值类型) operator 运算符符号(参数表)

{

对运算符的重载处理

}

不允许重载的运算符有

. (成员访问运算符)

.* (成员指针访问运算符)

:: (域运算符)

?: (条件运算符)

运算符重载的规则

重载不能改变运算符操作数的个数

重载不能改变运算符的优先级别

重载不能改变运算符的结合性

双目运算符传载(成员函数)

Coordinate Coordinate::operator+(const Coordinate &coor) {
	Coordinate temp;
	temp.m_iX = this->m_iX + coor.m_iX;
	temp.m_iY = this->m_iY + coor.m_iY;
	return temp;
}
coor3 = coor1+coor2; 
//等价于coor1.operator+(coor2)

重载为非成员函数

当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的形参代表自左至右次序排列的各操作数。

调用友元函数运算符的格式如下

operator 运算符(参数1,参数2)

等价于

参数1 运算符 参数2

单目运算符重载

class Coordinate
{
public:
Coordinate( );
Coordinate& operator-();
private:
int m_iX;
int m_iY;
};



Coordinate& Coordinate::operator-()
{
	m_iX = -m_iX;
	m_iY = -m_iY;
	return *this;
}

友元函数重载

class Coordinate
{
friend Coordinate & operator-(Coordinate &coor);
public:
Coordinate(int x, int y);
private:
int m_iX;
int m_iY;
}

重载++运算符

//前置
Coordinate &Coordinate ::operator++()
{
m_iX++;
m_iY++;
return *this;
}
//后置
Coordinate Coordinate::operator++(int) {
Coordinate old(*this);
m_iX++;
m_iY++;
return old;
}

Coordinate coor1(3,5);
coor1++; // coor1.operator++(0);
return

输入输出运算符重载

凡是用“cin>> ”,“cout<<”都要用

include把头文件包含到本程序中

cin 是istream输入流类的对象

cout 是ostream 输出流类的对象

<<流输出(插入)运算符

》流输入(提取)运算符

在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。

用户自己的定义的类型的数据(如类对象),不能直

接用<< >>输出或输入,必须对其进行重载。

函数形式为:

istream & operator >> (istream &, 自定义类 **&****);

ostream & operator <<(ostream &, 自定义类 **&****);

<<符号运算符重载

class Coordinate
{
friend ostream& operator<<(ostream
&out, const Coordinate &coor);
public:
Coordinate( int x, int y);
private:
int m_iX;
int m_iY;
};

ostream& operator<<(ostream &out, constCoordinate &coor);
{//out对应std::cout
out << coor.m_iX << " " << coor.m_iY;
return out;
}

1. 通常情况,输出运算符的第一个形参是一个非常stream对象的引用。之所以是非常量是因为向写入内容会改变其状态,而该形参为引用是因为我们无法直接复制一个ostream对象,out可用任何一个变量名代替。

2. 第二个形参是一个常量的引用,该常量是我们想要打印的类类型。该参数是引用的原因是我们希望避免复制实参,而该形参可以是常量是因为打印对象不会改变对象的内容。

3. 为与其他输出运算符保持一致,operator<< 返回它的ostream形参

注意

第一个对象必须是ostream,就意味着不能是this指针,不能是当前对象。所以绝对不可以通过成员函数来重载的,必须使用友元函数来重载。

后置单目运算符 ++和--的重载函数,形参列表中要增加一个int,但不必写形参名

继承

class Worker: public Person
{
public:
void work();
int m_iSalary;
};

继承与派生

继承与派生是同一过程从不同的角度看。

保持已有类的特性而构造新类的过程称为继承;

在已有类的基础上新增自己的特性而产生新类的过程

称为派生

继承方式包括:public、private、protected。

如果省略,系统默认为private

派生类的构成

1)从基类接收成员。 派生类将基类除构造函数和析构函数外的所有成员接收过来。

2)调整从基类接收的成员。 一方面可以通过继承方式改变基类成员在派生类中的访问属性,另一方面可以在派生类中声明一个与基类成员同名的成员屏蔽基类的同名成员。

3)在声明派生类时,还要自己定义派生类的构造函数

继承方式

不同继承方式的影响主要体现在:

派生类成员对基类成员的访问权限

通过派生类对象对基类成员的访问权限

访问权限

公有继承:

派生类中的成员函数:可以直接访问基类中的public和protected成

员,但不能直接访问基类的private成员;

派生类的对象:只能访问public成员。

保护继承:

派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;

通过派生类的对象:不能直接访问从基类继承的任何成员。

protected的特点与作用

对建立其所在类对象的模块来说,它与 private 成员的性质相同。

对于其派生类来说,它与 public 成员的性质相同。

既实现了数据隐藏,又方便继承,实现代码重用

基类的私有成员不会变为派生类的私有成员

B类从A类公共派生,那么A类的私有成员函数不能被B类访问使用。

继承与派生

派生类的构造函数和析构函数

默认情况

基类的构造函数不被继承;

派生类需要定义自己的构造函数。

如果派生类有自己新增的成员,且需要通过构造函数

初始化,则派生类要自定义构造函数。

Carton(const carton):Box(carton),material(carton.material)
{}//利用初始化列表调用基类的构造函数

若不继承基类的构造函数

派生类新增成员:派生类定义构造函数初始化;

继承来的成员:自动调用基类构造函数进行初始化;

派生类的构造函数需要给基类的构造函数传递参数。

派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(基类参数表),本类成员初始化列表

{

//其他初始化赋值语句;

};

在实例化一个对象时,执行构造函数的顺序是:

①派生类构造函数先调用基类构造函数;

②再执行派生类构造函数本身

释放派生类对象时,先执行派生类析构函数,再执行其基类的析构函数。

class Student1: public Student 
{ public:
// 派生类构造函数
Student1(int n, string name, int n1,string 
name1,int a, string ad ): Student(n, name) ,
monitor(n1,name1) , age(a), addr(ad)
{ }
private: // 派生类的私有部分
int age; 
string addr; 
Student monitor; // 定义子对象(班长)
};

有子对象的派生类的构造函数

执行派生类构造函数的顺序是:

①调用基类构造函数,初始化基类数据成员

②调用子对象构造函数,初始化子对象数据成员

③执行派生类构造函数,初始化派生类数据成员

多层派生时的构造函数

按照前面派生类构造函数的规则逐层写出各个派生类的

构造函数。

强调:只须调用其直接基类的构造函数即可,不用列出

每一层派生类的构造函数。

派生类的析构函数

类派生时,派生类不能继承基类的析构函数,撤

销派生类对象时,需要派生类的析构函数去调用

基类的析构函数。

继承中的特殊关系

基类和继承类中有同名成员

int main()
{
Soldier soldier;
soldier.play(); //访问子类成员函数
soldier.Person::play(); //访问父类同名成员函数
return 0;
}

基类与派生类类型转换

公有派生类对象可以被当作基类的对象使用,反之则不可。

派生类的对象可以隐含转换为基类对象;

派生类的对象可以初始化基类的引用;

派生类的指针可以隐含转换为基类的指针。

通过基类对象名、指针只能使用从基类继承的成员。

虚析构函数

当存在继承关系,使用父类指针指向堆中的子类对象,并要通过父类指针释放这块内存,就需要虚析构函数

关键字 virtual

A* d = new B();(假定A是基类,B是从A继承而来的派生类*),**

那么其(A类)析构函数应定义成是虚的,否则

在delete d时,B类的析构函数将不会被调

用,无法正确的析构,进而会产生内存泄漏

和异常。

多继承

class Worker
{…
}; 
class Farmer
{…
};
class MigrantWorker: public Worker, public Farmer
{…
};

多继承可能会带来BUG question Error Wrong

image-20211213222448554

菱形继承带来数据冗余和二义性的问题

解决菱形继承带来决数据冗余的问题:虚继承

虚继承(Virtual Inheritance),解决多

继承时可能发生的对同一基类继承多次而产生的二义性问题

虚继承

关键字:****virtual

解决二义性,防止双份拷贝间接基类

class Worker : virtual public Person

{ };

虚基类:使得在继承共同间

接基类时只保留一份成员

class Farmer : virtual public Person

{ };

class MigrantWorker : public Worker, public Farmer

{ };

考试题库

以下关于虚函数表说法错误的是:(A)

A.当类中仅含有虚析构函数,不含其他虚函数时,不产生虚函数表

以下关于异常处理说法错误的是(B)

B.一个 try 语句只能对应一个 catch 语句。

C++从 C 的面向过程变成为面向对象的主要原因是(D)

D.引进了类和对象的概念

面向对象方法中,继承是指(B)。

B.类之间共享属性和操作的机制

下面关于友元的描述中,错误的是:(D)

D.友元关系不能被继承,是双向可交换的。

在 C++中,类与类之间的继承关系具有(C)

C.传递性

下列哪个不是重载函数在调用时选择的依据(C).

C.函数类型

下列有关重载函数的说法中正确的是(C)

C.重载函数必须有不同的形参列表

已知 f1(int)是类 A 的公有成员函数,p 是指向成员函数 f1 的指针,则对 p 进行

赋值的下列方法中正确的是(B)

B.p=A::f1;

对于基类型相同的两个指针变量之间,不能进行的运算是(C)。

C.+

以下叙述中不正确的是(C)

C.在 C++中,外部变量的隐含类别是自动存储类别

在 C++程序中,对象之间的相互通信是通过(B)实现的

B.调用成员函数

关于析构函数的描述中,正确的是(C)

C.析构函数不能有参数

在类定义中,三种访问限定符出现的次数为(D)

D.任意多次

析构函数的特征是(A)

A.类只能定义一个析构函数

在 C++中用类将数据和对数据操作的代码连接在一起称为(B)

B.封装

关于 new 的描述中,正确的是(B)

B.new 可以分配对象空间

new是关键字

下列特性中,C 与 C++共有的是(D)

D.函数定义不能嵌套

建立包含有类对象成员的派生类对象时,自动调用构造函数的执行顺序依次为

(C)

C.基类、对象成员所属类、自己所属类的构造函数

程序补全题。完成下面程序,使 printXY 函数可访问到 Coordinate 类的私有数

据成员。

\1. class Coordinate

\2. {3. ▁▁▁

\4. public:

\5. Coordinate(int x,int y);

\6. private:

\7. int m_iX;

\8. int m_iY;

\9. };

\10. void printXY( ▁▁▁&c)

\11. {

\12. cout<<c.m_iX<<c.m_iY;

\13. }

\14. int main()

\15. {

\16. Coordinate coor(3,5);

\17. printXY(coor);

\18. return 0;

\19. }

答案 1 friend void printXY(Coordinate &c);

答案 2 Coordinate

posted on   宋新晨  阅读(256)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示