C++学习(34)

本文深入探讨了C++面向对象编程的核心概念和技术细节,包括抽象类的作用及限制、对象的构成、友元函数使用方式、构造函数与析构函数的行为、虚函数的实现机制等关键知识点。

1. 抽象类是不完整的,它只能用作基类。在面向对象方法中,抽象类主要用来进行类型隐藏和充当全局变量的角色。

 

抽象类具有以下特性:

抽象类不能实例化。

抽象类可以包含抽象方法和抽象访问器。

不能用 sealed 修饰符修饰抽象类,因为这两个修饰符的含义是相反的。 采用 sealed 修饰符的类无法继承,而 abstract 修饰符要求对类进行继承。

从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实际实现。

 

2.

#include<iostream>
#include<string.h>
using namespace std;
int i=1;
struct s1{
  union{
    //char a;
    //int b;
    //long c;
    //double f;
    //unsigned inte:32,d:1;
  };
};
 
struct s{
  char *a;
  //int b;
  //long c;
  //unsigned inte:32,d:1;
};
int main() {
  //cout<<sizeof(s1);
  cout<<sizeof(s);
  return 0;
}


3.面向对象程序设计中,对象=数据结构+算法、

 

4.友元函数访问对象中成员可以不通过对象名。

分析:否。友元函数不含this指针,所以要加对象名。

 

5.(1)classB(classA a){mA=a;}

(2)classB(classA a):mA(a){}

分析:通常情况下(2)的效率高。第一种先调用a的默认构造函数,再赋值一次;第二种直接调用复制构造函数,直接初始化表来进行构造。

补充:copy构造函数效率大于运算符的重载。

 

6.在32位gcc编译器下,运行结果:12 12

#include<iostream>
#include<string.h>
using namespace std;
class A{
  public:
    int b;
    char c;
    virtual voidprint() {
     cout<<"helloworld";
    }
};
class B:A{
  virtual voidprint(){
    cout<<"hellohello";
  }
};
int main() {
  cout<<sizeof(A)<<""<<sizeof(B)<<endl;
  return 0;
}
分析:类的大小只与 成员变量(非静态数据成员变量)和虚函数指针有关,还有考虑对齐。

 

在基类中存在虚函数时,派生类会继承基类的虚函数,因此派生类中不再增加虚函数的存储空间(因为所有虚函数共享一块内存区域),而仅仅需要考虑派生类中添加进来的非static数据成员的内存空间大小。

 

补充:

将class B:public A改成class B:virtual public A,则B中会多一个指向虚继承的指针,即12+4=16

 

7.C++面向对象编程语言中,以下不正确的(AD

A 接口中可以用虚方法

B 一个类可以实现多个接口

C 接口不能实例化(不能定义抽象类

D 接口中可以包含已经实现的方法

 

分析:首先所谓的接口是指只包含纯虚函数的抽象类,和普通的抽象类函数不一样。所以A不对,必须是纯虚函数。然后B是正确的没有问题。然后是C,刚才说接口是特殊的抽象类,抽象类的唯一作用就是创建派生类,不能定义抽象类的对象,所以C是正确的。对于D,接口即只包含纯虚函数的抽象类,所以D是不对的。

 

纯虚函数不能再类内实现,可以在类外实现。

C++没有接口概念,用抽象类表示接口,抽象类可以包含实现的虚方法。

 

抽象类实现的具体方法默认为虚的,但实际接口的类中的接口方法却默认为非虚的,当然也可以声明为虚的。

(虚函数可以实现)

编程角度,接口只是包含纯虚函数的抽象类,并不是说只能包含纯虚函数。

 

8.

A选项:在组合类的析构函数中并不需要显式调用其成员对象的析构函数,因为当执行组合类的析构函数时,其数据成员也会被销毁,对于类成员对象来说,成员对象销毁时,程序会自动调用其析构函数;不过对于组合类中new的指向类成员的指针,组合类需要自己去delete该指针;成员类对象在离开作用域的时候会调用其自身的析构函数,并不需要我们手动delete,就将其看做一个基本的局部对象即可。除非在构造的时候用了动态类存分配,这又是另外一回事了。

 

B:在类继承层次中,基类在上,派生类在下,所以可以自动进行向上类型转换,即可以使基类指针和引用指向派生类对象,而不可以使派生类指针和引用指向基类对象;

 

C:正确,构造函数可以根据参数的不同实现函数重载,而因为析构函数没有参数,对于一个类来说也是唯一的,所以是不能重载的;

D:正确,也是《EffectiveC++》第37条,如果子类要重写父类方法,需要将父类该方法声明为virtual。当然你可以不这样干,结果就是静态绑定。补充一点,重写就叫覆盖。如果没有virtual就是隐藏。

 

9. 重载,覆盖和隐藏总结:

1).重载:函数名相同,参数列表不同,重载只是在类的内部实现,但是不能靠返回值来判断。

特点:(1)相同的范围;(2)函数名字相同;(3)参数不同;(4)virtual可有可无。

2.覆盖:子类重新定义父类中相同名称和参数的虚函数,函数特征相同,但是具体实现不同,主要是在继承关系中出现。

特点:(1)不同的范围,分别位于父类和子类中;(2)函数的名称相同;(3)参数相同;(4)基类必须有virtual          

3).隐藏:也叫重定义,是指子类屏蔽了父类与其同名的函数

特点:(1)如果子类的函数和父类的函数同名,但是参数不同,此时,不管有无virtual,父类的函数被隐藏。

(2)如果子类的函数和基类的函数同名,参数也相同,若基类无virtual,则父类函数被隐藏。

 

10.打印结果为:110

#include<iostream>
#include<string.h>
using namespace std;
int inc(int a){
  return (++a);
}
int multi(int *a,int *b,int *c){
  return (*c=*a **b);
}
typedef int (*FUNC1)(int in);// 是函数指针定义
typedef int (*FUNC2)(int *,int *,int *);
void show(FUNC2 fun,int arg1,int *arg2) {//FUNC2类型函数指针fun 指向函数multi的首地址
  FUNC1 p=&inc;//FUNC1类型 函数指针p 指向函数inc的首地址
  int temp=p(arg1);// 此时调用函数inc,参数为10,返回值为11
  fun(&temp,&arg1,arg2);// 调用函数multi,参数为(11,10,arg2) arg2为指针变量负责带回返回值
  printf("%d\n",*arg2);// 输出 110
}
int main() {
  int a;
  show(multi,10,&a);
  return 0;
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值