我是靠谱客的博主 淡淡蜡烛,最近开发中收集的这篇文章主要介绍C/C++ 常见问题总结_面向对象与类,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

面向对象与类

面向对象基本知识

  1. 面向对象与面向过程的区别

    面向过程是一种以过程为中心的编程思想,以算法进行驱动。面向对象是一种以对像为中心的编程思想,以消息进行驱动。面向过程编程语言的组成为:程序=算法+数据,面向对象编程语言的组成为:程序=对象+消息。

  2. 面向对象的特征是什么
    面向对象三个要素为:封装、继承、多态。面向对象中所有的对象都可以归属为一个类。
    封装:将抽象得到的数据和行为相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成类。(数据抽象:依赖于类的接口和实现分离的技术)
    继承:通过继承联系在一起的类能够形成一种层次关系。通常在层次关系的根部有一个基类,其他类直接或者间接地从基类继承而来,这些继承得到的类称为派生类。基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。
    多态:(动态绑定)我们把具有继承关系的多个类型称为多态类型,因为我们能使用这些类型的“多种形式”而无须在意他们的差异。引用或指针的静态类型与动态类型不同这一事实正是c++语言支持多态性的根本存在。当我们使用基类的引用或指针调用基类中定义的一个函数时,我们并不知道该函数真正作用的对象是什么,他可能是一个基类的对象也可能是一个派生类的对象 。如果该函数是一个虚函数, 被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个,直到运行时才会决定调用那个版本(通常情况下,如果我们不使用某个函数,则无须为该函数提供定义,但是我们必须为每一个虚函数提供定义,不管他是否被用到)。另一方面,对非虚函数的调用在编译时绑定,此外通过对象进行的函数调用也在编译时绑定(静态类型与动态类型相同)。

  1. 类和结构有什么区别
    c 结构体. c++结构体和类的区别
  2. 抽象类及它的用途
    包含纯虚函数的类称为抽象类。抽象类把有共同属性或方法的对象抽象成一个类。
    将虚函数的函数体位置(声明语句的分号之前)书写=0(只能出现在类内部的虚函数声明处),就可以将一个虚函数声明成虚函数。纯虚函数无须提供定义,但也可以提供定义,不过函数体必须定义在类的外部。不能创建抽象基类的对象,派生类必须重新定义纯虚函数,不然仍是抽象基类。

类成员

  1. 成员变量有哪些访问控制方式
    在类的成员中,类的数据变量就是类的成员变量,通过声明private、protected和public 3种访问权限来对成员变量进行访问控制。

  2. 类的静态成员
    声明为static的类成员能在类的范围内共享,这样的类成员就是类的静态成员。类的静态成员存在于任何对象之外, 对象中不包含任何与静态数据成员有关的数据。类似的, 静态成员函数也不与任何对象绑定一起,它们不包含 this 指针。作为结果,静态成员函数不能声明成 const 的,而且我们也不能在 static 函数体内使用 this 指针。(这一限制既适用于 this 的显示使用, 也对调用非静态成员的隐式使用有效。)类的静态成员函数只能访问类的静态成员。


 **使用类的静态成员**
1、使用作用域运算符直接访问静态成员
2、可以使用类的对象、引用或指针来访问静态成员。
3、成员函数不通过作用域运算符就可以访问静态成员。

多态

  1. 什么是多态?多态的作用
    编写程序实际上就是一个将世界的具体事物进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来,再通过这个抽象的事务,与不同的具体事务进行对话。
    派生类的对象可以绑定在基类的指针或者引用上,此时将会发生静态类型与动态类型不一致的情况,当其调用虚函数时将会发生动态绑定。

继承

  1. 派生类与基类的转换
    派生类对象可以绑定在基类的指针或引用上。
    不存在从基类向派生类的隐式类型转换:
    之所以存在派生类向基类的类型转换是因为每个派生类对象都包含一个基类部分,而基类的引用或指针可以绑定到该基类部分上。一个基类对象既可以以独立的形式存在,也可以以派生类对象的一部分存在,所以不能讲一个派生类的引用或指针绑定到基类的对象上,不存在基类向派生类的自动类型转换。
//quote父类 bulk_quote子类
quote base;
bulk_quote*bulk=&base;//错误
bulk_quote &bulkref=base;//错误

注意:即使一个基类的指针或者引用绑定到一个派生类对象上,我们也不能执行从基类向派生类的转换

bulk_quot bulk;
quote*item=&bulk;
bulk_quote*bulkp=item;//错误

当我们确定已经是安全的时候,可以使用static_cast来强制覆盖编译器的检查工作。
在对象之间不存在类型转换:
派生类向基类的自动类型转换只对指针或引用有效, 在派生类类型和基类类型之间不存在这样的转换。由于拷贝构造函数或拷贝赋值运算符时这些成员接受引用作为参数,所以派生类向基类的转换允许我们给基类对应的成员(拷贝构造、赋值运算符、移动等)传递一个派生类对象,此时实际运行的成员(拷贝构造、赋值运算符、移动等)是基类中的那个,它只能处理基类自己的成员,因此当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象的基类部分会被拷贝、移动或赋值,它的派生类部分将会被忽略掉。

  1. 什么是虚函数?有什么作用?
    虚函数是用于面向对象中实现多态的机制。它的核心理念就是通过基类访问派生类定义的函数。虚函数必须是费静态成员函数。当某个函数被声明成虚函数时, 则在所有派生类中它都是虚函数(可以使用 virtual 关键字指出该性质,然而并非必须这么做)。
    当派生类的函数覆盖了某个继承而来的虚函数则:1、形参列表相同;2、返回类型匹配(特殊情况: 当类的虚函数是类本身的指针或引用
    时);
    说明符final和override
    可以使用 override 来说明派生类中的虚函数, 可以将某个函数指定为 final, 则之后任何尝试覆盖该函数的操作都将引发错误。
    说明符 final 和 override 说明符出现在形参列表之后(包括 const 和引用修饰符,以及尾置返回类型)。
    虚函数与默认实参
    当使用了默认实参时,则该实参值由本次调用的静态类型决定,即:如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数也是如此。

  2. 构造函数与析构函数调用时机
    (总结拷贝控制时再讲)

访问控制
定义在 public 说明符之后的成员在整个程序内可被访问, public 成员定义类的接口;
定义在 private 说明符之后的成员可以被类的成员函数或友元访问,但是不能被使用该类的代码访问, private 部分封装了类的实现细节。
每个访问说明符指定了接下来的成员的访问级别, 其有效范围直到出现下一个访问说明符或者达到类尾为止。
protected说明符:一个类使用 protected 关键字来声明那些他希望与派生类分享但是不
想被其他公共访问使用的成员。
1、和私有成员类似,受保护的成员对于类的用户来说是不可访问的。
2、和公有成员类似,受保护的成员对于派生类的成员和友元来说是可以访问的
3、派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。

class base{
protected:
int prot_mem;
};
class sneaky:public base{
friend void clobber(sneaky&);
friend void clobber(base&);
int j;
};
//clobber可以访问sneaky对象的private和protected成员
void clobber(sneaky&s){s.j=s.prot_mem=0;}
//错误 clobber不能访问base的protected成员
void clobber(base&b){b.j=b.prot_mem=0;}

派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员;对于普通的基类对象中的成员不具有特殊访问权限

1.继承方式与继承时访问级别的变化
共有、私有和受保护继承
某个类对其继承而来的成员的访问权限受到两个因素影响:
1、在基类中该成员的访问说明符
2、 派生类派生列表中的访问权限符
派生访问说明符对于派生类的成员(及友元)能否访问其直接基类的成员没有什么影响, 对基类成员的访问权限只与基类中的访问说明符有关。派生访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成员的访问权限。
当以 public 方式继承时, 成员遵循原有的访问说明符, 以 protected方式继承时,访问说明降一级,当以 private 方式继承时,都为 private。

派生类向基类转换的可访问性
1、如果是 public 继承,则用户代码和后代类都可以使用派生类到基类的转换。
2、如果是 private 或 protected 继承的,则用户代码不可以使用派生类向基类的转换。
3、如果是 private 继承,则从 private 继承类派生的孙类不能转换为基类
4、如果是 protected 继承,则从 protected 继承派生的孙类的成员函数可以转换为基类
5、 无论以什么派生访问标号, 派生类本身都可以使用派生类向基类的转换。
NOTE: 要确定到基类的转换是否可访问,可以考虑基类的 public 成员是否可访问,如果可以,转换是可以访问的,否则,转换是不可访问的。

友元与继承
友元关系不能传递和继承。 每个类控制自己的成员的访问权限,基类的友元在访问派生类成员时不具有特殊性,类似地, 派生类的友元也不能随意访问基类的成员。

class base{
friend class pal;
protected:
int prot_mem;
};
class sneaky:public base{
friend void clobber(sneaky&);
friend void clobber(base&);
int j;
};
class pal{
public:
int f(base b) {return b.prot_mem;}//正确:pal是base的友元
int f2(sneaky s){return s.j;}//pal不是sneaky的友元
//对基类的访问权限由基类本身控制,即使对于派生类的基类部分也是如此
int f3(sneaky s) {return s.prot_mem;}//正确
}

对于 f3 的解释: pal 是 base 的友元, 所以 pal 能够访问 base 对象的成员,这种可访问性包括了 base 对象内嵌在其派生类对象中的情况。

继承中的作用域
每个类定义自己的作用域,在这个作用域内我们定义类的成员。当存在继承关系时,派生类的作用域嵌套在基类的作用域之类。

class quote{
public:
quote()=default;
quote(const string&book,double sales_price):bookNo(book),price(sales_price){}
string isban() const{return bookno;}
virtual double net_price(size_t n) const{return n*price;}
virtual ~quote()=default;
private:
string bookno;
protected:
double price;
};
class disc_quote:public quote{
public:
disc_quote()=default;
disc_quote(string &book,double price,size_t qty,double disc):
quote(book,price),quantity(qty),discount(disc){}
double net_price(size_t) const=0;
protected:
size_t quantity=0;
double discount=0.0;
};
class bulk_quote:public disc_quote{
public:
bulk_quote()=default;
bulk_quote(string &book,double price,size_t qty,double disc):
disc_quote(book,price,qty,disc){}
double net_price(size_t) const override;
};
bulk_quote bulk;
cout<<bulk.isbn();

名字 isban 的解析过程:
1、我们通过bulk_quote 的对象调用 isban,首先在bulk_quote 中寻找;
2、接下来在 disc_qutoe 中查找;
3、最后在 quote 中查找,最终被解析为 quote 中的 isban。
在编译时进行名字查找
一个对象、 指针或引用的静态类型决定了该对象的哪些成员是可见的。 即使, 静态类型与动态类型可能不一致。 但我们能使用哪些成员仍热是由静态类型决定的。搜索是由静态类型开始的。

class disc_quote:public quote{
public:
pair<size_t,double>discount_policy() const
{return {quantity,discount};}
};
bulk_quote bulk;
bulk_quote*bulkp=&bulk;
quote*itemp=&bulk;//静态类型与动态类型不一致
bulkp->discount_policy(); //正确:
itemp->discount_policy();// 错误 搜索从 quote开始将找不到discount_policy()成员

名字冲突与继承
派生类也可以重用定义在其直接基类或间接基类中的名字, 此时定义在内层作用域中的名字, 将隐藏定义在外层作用域中的名字。
假定我们调用 p->mem()(或者 obj.mem())步骤如下:
1、首先确定 p->mem()(或者 obj.mem())的静态类型
2、在静态类型中查找 mem。如果找不到则依次在直接基类中不断查找直至到达继承链的顶端。3、一旦找到了 men,就进行常规的类型检查,确认本次调用是否合法。
4、加入调用合法则:如果是虚函数且我们通过指着或引用调用,编译器产生的代码在运行时确定该运行虚函数的哪个版本。若不是虚函数(或者我们是通过对象进行的调用),则编译器产生一个常规函数调用。

名字查找先于类型检查
声明在内层作用域中的函数不会重载声明在外层作用域的函数。 如果派生类的成员与基类的某个成员名字相同, 就会隐藏基类成员。(即使形参列表不一致)。一旦名字找到编译器就不会再继续查找。

struct base{
int memfcn();
};
struct derived:base{
int memfcn(int);
//隐藏基类的memfcn
};
derived d; base b;
b.memfcn();
d.memfcn(10);
d.memfcn(); //错误:参数列表为空的函数被隐藏了
d.base::memfcn();
//正确

虚函数与作用域
基类与派生类中的虚函数必须有相同的参数列表, 如果不同我们无法通过基类的指针或这引用调用派生类的虚函数了。

class base{
public:
virtual int fcn();
};
class d1:public base{
public:
//隐藏了基类的fcn,这个fcn不是虚函数 d1继承了base::fcn()的定义
int fcn(int);
virtual void f2();
};
class d2 :public d1{
public:
int fcn(int);
//隐藏d1::fcn(int)
int fcn();
//覆盖base虚函数
void f2();
//覆盖d1的虚函数
};
base bobj; d1 d1obj; d2 d2obj;
base *bp1=&bobj, *bp2=&d1obj,*bp3=&d2obj;
bp1->fcn();
//虚调用 运行时调用 base::fcn()
//虚调用 运行时调用 base::fcn()
实际绑定的虽然是d1,而d1没有覆盖,所以将在运行时解析为base定义的版本
bp2->fcn();
bp3->fcn();
//虚调用 运行时调用 d2::fcn
d1 *d1p=&d1obj ; d2 *d2p=&d2obj;
bp2->f2();
//错误
d1p->f2();
//虚调用运行时调用d1::fcn
d2p->f2();
//虚调用运行时调用d2::fcn
base *p1=&d2obj; d1*p2=&d2obj; d2*p3=&d2obj;
//没有调用虚函数,不会发生动态绑定
p1->fcn(42);
//错误 
p2->fcn(42);
// 调用d1::fcn
p3->fcn(42);
// 调用d2::fcn

最后

以上就是淡淡蜡烛为你收集整理的C/C++ 常见问题总结_面向对象与类的全部内容,希望文章能够帮你解决C/C++ 常见问题总结_面向对象与类所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(67)

评论列表共有 0 条评论

立即
投稿
返回
顶部