这里整理牛客网上c++题目
前言
题目集合
一、知识点
一、const
关于const函数的一些内容:
c++的const函数特点:
1. 不能在const函数中修改所在类的对象的数据,因为const函数中的*this是常量,同样只能访问const函数;
2. const函数中只能调用其他的const函数,不能调用非const函数,因为对象调用函数是需要传递对象自己,const函数中的*this是常量,非const函数中的*this是变量,因此不可以调用(除非去除*this的const属性);
Note:使用const_cast后,可以在const函数中调用非const函数的
3. const函数与同名的非const函数是重载函数;
4. const对象只能调用const函数 ,但是非const对象可以调用const函数。
5.还有const 函数可以使用非const数据成员,但是非 const函数不能使用const 数据成员
1.2
c语言没有常成员函数,c语言就没有成员函数的概念。
这个帖子介绍C++的常成员函数很详细:
1.2.1.常成员函数声明:
const成员函数也就是常成员函数,它的声明形式:
返回类型 成员函数名(参数表) const ;
例如: int function(int x) const ;
易混淆的地方: const 的位置:
函数开头的 const 用来修饰函数的返回值,表示返回值是 const 的,也就是不能被修改,例如const char * getname()。
函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const。
2023年12月23日10:17:04
关于返回值用const修饰,之前是很少在意或者用过的,对此并不是很了解。然后自己验证了下,发现返回类型是int的时候并没有发挥作用。在返回类型是指针或者引用的时候是有用的。之前对此并不知道啊。
至于为什么返回类型是常规数据类型的时候,下面这个帖子也说了。
参考这篇帖子:C++入门——const的作用_c++ 返回值为const引用 值能被修改吗-CSDN博客
下面是自己的验证demo
#include <iostream>
using namespace std;
const char* GetString()
{
return NULL;
}
const int* GetData()
{
return NULL;
}
const int& GetYinyongData()
{
return NULL;
}
const int GetIntData()
{
return NULL;
}
int main()
{
const char * GetString(); //定义一个函数
//char *str = GetString(); //错误,因为str没有被const修饰
const char *str = GetString();
const int* d = GetData();
int f = GetYinyongData(); //这样可以,但是不能做左值
f = 23;
GetYinyongData() = 4; //error 不能做左值
int h = GetIntData();
h = 22;
const int g = GetIntData();
//g = 22; //error: assignment of read-only variable 'g'
return 0;
}
1.2.2.常成员函数的主要特点:
1)不能更新类的成员变量
2)不能调用该类中没有用const修饰的成员函数,即只能调用常成员函数
3)可以被类中其它的成员函数调用
4)常对象只能调用常成员函数,而不能调用其他的成员函数。
3.其它注意事项
1)const关键字可以用于对重载函数的区分
2)const是函数类型的一部分,在实现部分也要带const。
二、题目
一、10月8号
1、类中静态成员
类中的静态成员在构造函数之前初始化
静态成员属于类,提前开辟空间。构造函数是在类实例化对象时调用
类内声明,类外初始化
2、题目
下列代码可以通过编译吗?如何修改使其通过编译?
template <class T>
struct sum {
static void foo(T op1 , T op2){
cout << op1 <<op2;
}
};
sum::foo(1,3);
A、编译通过
B、应该去掉static关键字
C、调用应该如下: sum<int>:: foo(1,3)
D、调用应该如下: sum:: <int>foo(1,3)
解释:
这一题加深了我对模板知识的理解,做一点小小的分享
很多答友已经给出了模板、类模板,以及静态成员函数的概念,百度里也有不少。(假设这里你已经懂了)
正确答案C,我第一次选的A,因为在函数模板中,体化时可以不给定模板实际参数,编译器自动识别进行体化,而在类模板的体化中,与函数模板不同,类模板的体化必须给定模板实际参数,如:A<T> a; 所以本例编译不通过,考的就有这个知识点。
那为什么D错了,还是回到前面讲的类模板的体化格式诸如 A<T> a;
A是一个模板, A加上<T>以后:A<T>这个整体才是我们要的“类”,所以是语法错误
这道题的考点是类模板的static成员。首先说说模板实例化,即类模板需要指定确定的类型,例如本题中sum<int> a或者sum<string> a等。 static成员函数可以由类名或者对象直接调用。在本题中,sum<int> a;a.foo(1,3)或者sum<int>::foo(1,3)。两种形式均可。
3、题目
关于抽象类说法以下哪些是正确的?答案:AC
A、抽象类中可以不存在任何抽象方法
B、抽象类可以为final的
C、抽象类可以被抽象类所继承
D、如果一个非抽象类从抽象类中派生,不一定要通过覆盖来实现继承的抽象成员
解释:
a:抽象类必须存在至少一个抽象方法(纯虚函数)
b:因为final的类不可以被集成,只能用于生成实例对象;抽象类不能用于生成实例对象,因此,抽象类不能是final.
c:如果我们不在派生类中覆盖纯虚函数,那么派生类也会变成抽象类,所以抽象类可以被抽象类继承
d:同c
2023年11月25日10:02:05更新,上述是在java中答案确实是AC,因为java可以用abstract关键词声明抽象类。
在c++中不太确定。另外MFC书中定义方法为:成员函数用来对数据成员进行操作,称之为方法。纯虚函数应该也是方法吧,所以在c++中A选项可能还不对。
二、10月13号
1、题目
判断下列说法是否正确:设有两个串 S1 和 S2,求 S2 在 S1 中首次出现位置的运算称为求子串。( B)
A、正确
B、错误
解释:求子串是在该字符串的基础上进行的,模式匹配是求另外一个字符串在该字符串上首次出现位置的运算
2、题目
试分析下面代码运行后,内存空间的状态(D)
String s1="hello"; s1=s1+" world";
A、只有"hello"
B、只有"hello world"
C、有 hello 和 "world"
D、有 "hello" 和 "hello world"
解释:因为在Java中String类被设计成不可改变的类,所以String类的所有对象都是不可变的。第一句代码中,s1(存储在栈区)引用了堆中的一个内存区域(记为a1),a1堆内存区域存储的内容是“Hello”。执行第二句代码后,s不在引用a1区域,而是重新引用了堆中的另外内存区域(记为a2),且a2中的内容是“Hello world”。s并没有对原始的a1堆内存中的内容进行改变,而是重新指向了新的堆内存区域。
3、题目
若串 S="software",则其子串的数目是(B)
A、8
B、37
C、36
D、9
解释:
子串: n(n+1)/2 + 1
非空子串:n(n+1)/2
非空真子串:n(n+1)/2 - 1
2023年11月25日15:03:49
重新回头回顾这些题目的时候,感觉也根本记不住所谓的这些公式,所以学习过一道题跟掌握一道题根本就不是一回事。
然后就想到可以举一个例子,比如字符串"abc",子串有a、b、c、ab、ac、bc、abc共6个。即就是n(n+1)/2个。全部子串包括一个空子穿,就是7个。非空子串就是6个。非空真子串应该就是不包括'abc',就是5个。这样会更能记住吧。
4、题目
Linux 中,在给定文件中查找与设定条件相符字符串的命令为?(C)
A、find
B、gzip
C、grep
D、sort
解释:grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。 find只能匹配单个字符,
10月12号
1、题目1
一、类的静态成员初始化
1.类的静态成员初始化方法
在C++中,类的静态成员(static member)必须在类内声明,在类外初始化,像下面这样
class A { private: static int count ; // 类内声明 }; int A::count = 0 ; // 类外初始化,不必再加static关键字
为什么?因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
什么东西能在类内初始化?
能在类中初始化的成员只有一种,那就是静态常量成员。
这样不行
class A { private: static int count = 0; // 静态成员不能在类内初始化 };
这样也不行
class A { private: const int count = 0; // 常量成员也不能在类内初始化 };
但是这样可以
class A { private: static const int count = 0; // 静态常量成员可以在类内初始化 };
结论:
- 静态常量数据成员可以在类内初始化(即类内声明的同时初始化),也可以在类外,即类的实现文件中初始化,不能在构造函数中初始化,也不能在构造函数的初始化列表中初始化;
- 静态非常量数据成员只能在类外,即类的实现文件中初始化,也不能在构造函数中初始化,不能在构造函数的初始化列表中初始化;
- 非静态的常量数据成员不能在类内初始化,也不能在构造函数中初始化,而只能且必须在构造函数的初始化列表中初始化;
- 非静态的非常量数据成员不能在类内初始化,可以在构造函数中初始化,也可以在构造函数的初始化列表中初始化;
总结如下表:
类型 初始化方式 | 类内(声明) | 类外(类实现文件) | 构造函数中 | 构造函数的初始化列表 |
非静态非常量数据成员 一般数据类型 | N | N | Y | Y |
非静态常量数据成员 | N | N | N | Y (must) |
静态非常量数据成员 | N | Y (must) | N | N |
静态常量数据成员 | Y | Y | N | N |
以下三种类型必须通过初始化列表来初始化
1.非静态 常量
2. 引用类型
3. 没有默认构造函数的类类型
针对以上,有人在评论区说
常量成员是可以直接在类内初始化的,或者使用初始化列表。
这个需要再确认。
2024年3月26日11:30:42更新
类中包含引用成员变量
、const成员变量
、自定义类型成员(且该类没有默认构造函数时)
,必须在初始化列表进行初始化。
2、题目
在下面的类定义中,横线上应填入的内容是(B)(注意,count是在类外了)
class Fred {
public:
void print() {
cout << data << endl;
}
void setData(double d) {
data = d;
}
static int count;
private:
double data;
};
__________count = 0;
A、static int Fred::
B、int Fred::
C、int
D、static int
2023年11月25日15:09:16补充,
类的大小不包含静态数据成员。
2023年12月14日10:02:41
补充一题
C++语言中,有关类的初始化叙述正确的是(AD)
A、静态函数中不能出现this指针
B、可以在类定义时,对对象直接初始化
C、一般数据类型可以在类的外部进行初始化
D、静态数据类型可以在类的外部进行初始化
我选错ACD。牛客网公司真题_免费模拟题库_企业面试|笔试真题
2024年3月26日14:25:19
今天看BD时,依然懵逼。D看了解释,不知道下次看是否还懵逼。
10月16号
1、题目
对字符串 "mabnmnm" 的二进制进行哈夫曼编码有多少位()
A、12
B、13
C、14
D、15
解释:霍夫曼编码
哈夫曼编码(Huffman Coding)原理详解-CSDN博客
10月24号
1、题目
打印一个字段宽度为6的左对齐字符串中的前3个字符,应使用(C)转换说明。
A、%3.6s
B、%6.3s
C、%-6.3s
D、%-3.6s
官方解析:
%s:打印字符串时使用的转换说明。
%后加(数字)修饰符:表示打印的最小字段宽度,如%6s。
%后加(.数字)修饰符:对%s转换来说,表示待打印字符的最大数量,如%.3s。
%后加(-)修饰符:表示待打印项左对齐,即从字段的左侧开始打印该项,如%-6s。
综上,使用%-6.3s转换说明,可成功打印出一个字段宽度为6的左对齐字符串中的前3个字符。
所以正确答案为C。
2023年11月25日15:18:54
#include <stdlib.h>
#include <stdio.h>
int main()
{
char array[] = "hello world";
printf("%6.3s\n", array);
printf("%-6.3s\n", array);
return 0;
}
// hel
//hel
眼见为实!
10月27号
1、题目
以下对于方法覆盖的说法正确的有(BCD)
A、方法覆盖发生在同一类中
B、方法的覆盖发生在子类型中
C、方法名一定要一样
D、参数类型一定要一样
E、返回类型一定要一样
F、访问权限只能一样
涉及到一个知识点,即对应E选项,有个概念叫协变。不明所以,所以百度一下
协变定义
C++中,协变(covariant)是指派生类(子类)中的返回类型可以是基类(父类)中返回类型的子类型。换句话说,如果一个虚函数在基类中返回的是基类类型的指针或引用,那么派生类可以重写该虚函数并返回基类类型的子类类型的指针或引用。
协变在C++中是通过使用返回类型协变(return type covariance)来实现的。返回类型协变是指派生类中重写的虚函数可以具有比基类更具体的返回类型。
这种协变的能力使得在使用多态时更加灵活,可以根据具体的派生类返回不同的子类型,而不需要进行显式的类型转换。
实现协变需满足以下条件:
基类中的函数必须是虚函数(使用 virtual 关键字声明)。
派生类中重写的函数必须具有相同的函数签名(函数名、参数列表和常量性)。
派生类中重写的函数的返回类型必须是基类函数返回类型的子类型。
通过使用协变,可以在使用多态时更加灵活,可以根据具体的派生类返回不同的子类型,而不需要进行显式的类型转换。
需要注意的是,C++中只支持返回类型协变,而不支持参数类型协变。也就是说,在派生类中重写虚函数时,参数类型必须与基类中虚函数的参数类型保持一致。
public class CovariantReturn {
public static void main(String[] args) {
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
}
class Grain{
@Override
public String toString() {
return "Grain";
}
}
class Wheat extends Grain{
@Override
public String toString() {
return "Wheat";
}
}
class Mill{
Grain process(){
return new Grain();
}
}
class WheatMill extends Mill{
Wheat process(){
return new Wheat();
}
}
10月28号
1、题目
对拷贝构造函数的描述正确的是(D)
A、该函数名同类名,也是一种构造函数,该函数返回自身引用
B、该函数只有一个参数,必须是对某个对象的引用
C、每个类都必须有一个拷贝初始化构造函数,如果类中没有说明拷贝构造函数,则编译器系统会自动生成一个缺省拷贝构造函数,作为该类的保护成员
D、拷贝初始化构造函数的作用是将一个已知对象的数据成员值拷贝给正在创建的另一个同类的对象
1.1针对这个题目,首先遇到的第一个概念就是拷贝构造函数。
然后答案中有人提到拷贝构造函数和拷贝赋值函数。【拷贝构造函数和拷贝赋值函数】我蒙了,百度了一下,看到如下文章。
下面链接是介绍拷贝构造函数的
在读这篇文章的时候还碰到了一个问题,就是类方法在返回对象时,竟然没有调用拷贝构造函数
百度了看到这篇文章
C++ return时不调用拷贝构造函数 返回值优化_c++return的时候会调用构造函数吗-CSDN博客
还没读完需要确认。2023年10月27日17:37:17
已经都确认好了 2023年10月28日09:50:37
2023年12月9日14:32:27
哈哈哈,这次是终于搞懂啦,之前提出了这个问题:
【然后答案中有人提到拷贝构造函数和拷贝赋值函数。【拷贝构造函数和拷贝赋值函数】我蒙了】
这次做这个题目终于搞懂了。题目如下:
#include<iostream>
using namespace std;
class MyClass
{
public:
MyClass(int i = 0)
{
cout << i;
}
MyClass(const MyClass &x)
{
cout << 2;
}
MyClass &operator=(const MyClass &x)
{
cout << 3;
return *this;
}
~MyClass()
{
cout << 4;
}
};
int main()
{
MyClass obj1(1), obj2(2);
MyClass obj3 = obj1;
return 0;
}
运行时的输出结果是()
A、11214444
B、11314444
C、122444
D、123444
这个题目的答案是D,我选错为C。附上链接:牛客网公司真题_免费模拟题库_企业面试|笔试真题
2024年2月21日11:36:28更新
这个题目答案是C,我选错为D。题目链接:牛客网公司真题_免费模拟题库_企业面试|笔试真题
这个题目我做错了,然后看答案,答案是这么写的:
C D 辨析: 关键是区分 浅/深拷贝操作 和 赋值操作: 没有重载=之前: A a ; A b; a = b; 这里是赋值操作。 A a; A b = a; 这里是浅拷贝操作。 重载 = 之后: A a ; A b; a = b; 这里是深拷贝操作(当然这道题直接返回了,通常我们重载赋值运算符进行深拷贝操作)。 A a; A b = a; 这里还是浅拷贝操作。 所以 MyClass obj3 = obj1; 调用的是拷贝构造函数。 如果写成 MyClass obj3; obj3 = obj1; 输出的结果就是 1203444
看了答案我就懂了,然后我突然看到了这一句话:所以 MyClass obj3 = obj1; 调用的是拷贝构造函数。我突然联想到这里,原来这是拷贝构造函数啊(构造函数是没有返回值的,也是上面题目的A选项错误原因),然后我就想那写成 MyClass obj3; obj3 = obj1;这个是不是赋值构造函数?查看一下评论区,
看见评论区一个人这么说:
C MyClass obj3 = obj1; obj3还不存在,所以调用拷贝构造函数输出2, 如果obj3存在,obj3=obj,则调用复制运算符重载函数,输出3
其实这个人也没有说是拷贝赋值函数,但是我现在理解的是:
复制运算符重载函数 = 拷贝赋值函数。
那么也就分清了拷贝构造函数和拷贝复制函数的区别了。
以上就是理解了之前不懂的一个点啊。然后我就突然发现这种方式的明显先进性了,那就是每次都能进步一点,每次都能固化一点。真棒啊!!每天都有进步。
然后我又突然想到,别人也会有这个困扰和知识盲区吗。我想也许没有吧,可能别人软件专业上课听过就记住了,第一次就走上了正确的路,而这个却知识一个简单的概念。但是我想,这样也是往他们靠拢,总归是在前进。所以我现在才能理解,走的慢其实不可怕,走错路才是最可怕的。
以上。
然后我在总结上述一些文字的时候,翻看评论区,突然发现又涉及到了另外一个点。那就是上面一楼的问题,
方法返回的时候会调用拷贝构造函数吗?
下面别人解释为:
你的返回类型是 “A” 是值 ,题目中返回的是“MyClass&” 是引用。在返回值是 会生成一个临时变量这时候会调用拷贝构造函数,而返回引用却不需要。 这就是为什么你的程序 在输出3 后面 会有 2 shit。 如果你把|“operator=”的参数(const A &a)改为 (const A a)。这时候又会调用拷贝构造函数 输出 2 shit 3 2 shit。
链接:牛客网公司真题_免费模拟题库_企业面试|笔试真题
上面的解释看的费劲,我也来验证一下
#include<iostream>
using namespace std;
class MyClass
{
public:
MyClass(int i = 0)
{
cout << i;
}
MyClass(const MyClass &x)
{
cout << 2;
}
MyClass &operator=(const MyClass &x)
{
cout << 3;
return *this;
}
~MyClass()
{
cout << 4;
}
};
int main()
{
MyClass obj1(1), obj2(2);
MyClass obj3 = obj1;
printf("+++++++++\n");
MyClass obj4;
obj4 = obj2; //预测034
return 0;
}
//预测122
//+++++++++++
//034444
//122+++++++++
//034444
//预测正确
预测正确,return时接收的是引用,不需要调用拷贝构造函数。
修改成如下代码
#include<iostream>
using namespace std;
class MyClass
{
public:
MyClass(int i = 0)
{
cout << i;
}
MyClass(const MyClass &x)
{
cout << 2;
}
MyClass operator=(const MyClass x)
{
cout << 3;
return *this;
}
~MyClass()
{
cout << 4;
}
};
int main()
{
MyClass obj1(1), obj2(2);
MyClass obj3 = obj1;
printf("+++++++++\n");
MyClass obj4;
obj4 = obj2; //预测02324
printf("start destruct\n");
return 0;
}
//预测122
//+++++++++++
//02324
//start destruct
//44444
//122+++++++++
//023244start destruct
//4444
//预测有点差别,不过已经很不错了
//预测错误的原因在于没搞清返回值什么时候析构的
哈哈,清晰多了呀!!
这边就又留下一个问题就是返回值什么时候销毁,百度了看到一个回答比较靠谱:
因为你的 int fun(a ,b);没有用某个变量去接收返回值,那么这句代码结束后,返回值就被销毁了,完全没起到作用。
目前这个答案看起来靠谱。暂时这样。这个问题分析的通透啊!
2024年3月26日15:05:47
今天再看,对于B选项竟然又一脸懵逼。还回去看了下原题,才知道B选型为啥错误。
对于一个类X,如果一个构造函数的第一个参数是下列之一:X&、const X&、volatile X&、const volatile X&,且没有其他参数或其他参数都是有默认值,那么这个函数是拷贝构造函数。
2、题目
在C++中,对引用和指针的区别说法错误的是( B )
A、引用总是指向一个对象,指针可能不指向对象
B、引用和指针都可以被重新赋值
C、sizeof 引用得到的是所指向的变量(对象)的大小,而 sizeof 指针得到的是指针变量本身的大小
D、引用创建时必须初始化,而指针则可以在任何时候被初始化
解释:正确答案是B。当然关于答案B评论的人也有争议,就是指针应该在声明的时候初始化为空指针。但是我百度了好像确实是说指针可以在任何时候被初始化。但是这不是今天的重点,重点是关于答案B中的引用和指针都可以被重新赋值。
对于这个我一开始的认知是引用可以给赋值啊,但是看了下面这个解释后,在说明其实我理解的那个不是给引用赋值,这边说的给引用赋值指的是改变引用指向的引用对象,来看下下面的解释。
C++中引用不能重新赋值的理解
教材上说引用是不能重新赋值的,可是下面的程序能正常运行,不会出错。这里怎么出现了引用赋值语句呢(语句[1])?是不是教材错了?原因究竟是什么呢?
请看如下程序:#include<iostream.h> void main() { int i=1,j=5; int& k=i; k=j; //语句[1] cout<<"i="<<i<<"; j="<<j<<"; k="<<k<<endl; }
首先想想程序运行结果应该是什么呢?
VC6.0上运行后的结果是:
i=5; j=5; k=5分析:
程序没有错误,是正确的,但是并不能说明:引用能重新赋值。很明显,引用是不能重新赋值的,只是理解上错了!
引用的赋值:是指引用初始化时,它的引用对象只能是变量,并且,一旦它指定为某一个对象的引用后,就不能更改了。但是,可以用这个引用来改变它的对象的值,从而达到引用的目的——作为变量对象的别名。
如上例,引用k初始化为i,即k从此以后一直是i的引用,若想让k不再是i的引用而成为别的变量的引用那是不可能的。所以,接下来的一句“k=j;”就不能理解成:取消k是i的引用而将k作为j的引用。正确的理解应该是:利用引用k来改变它所指对象i的值,即相当于语句“k=5;”。若在上示例语句“k=j;”后加上一句“j=10”,结果将是:“i=5; j=10; k=5”,从这个结果就能很好理解了。
所谓的引用的重新赋值,应该是:
int x,y,z;
int &x=y;
&x=z;
这种是对引用x,改变了它的指定对象,一开始是y的引用,之后,又重新说明是z的引用,这种引用的重新赋值是不允许的。另外:
常引用所引用的对象的值是不能更改的,即上述示例中若将语句“int& k=i;”更改为“const int& k=I;”,则在编译时就会出现错误了。
3、题目3
关于重载和多态正确的是(B)
A、如果父类和子类都有相同的方法,参数个数不同,将子类对象赋给父类后,由于子类继承于父类,所以使用父类指针 调用父类方法时,实际调用的是子类的方法
B、选项全部都不正确
C、重载和多态在C++面向对象编程中经常用到的方法,都只在实现子类的方法时才会使用
D、
class A{ void test(float a){cout<<"1";} }; class B:public A{ void test(int b){cout<<"2";} }; A *a=new A; B *b=new B; a=b; a.test(1.1); 结果是1
解释:评论里一个人的评论涉及到一个我的知识盲区,即
重载 :参数不一样; 重写(覆盖):虚函数 调用子类; 重定义 隐藏:非虚函数 ,子类有效
查找百度
1、重载和重写的区别是什么?
重写多态性起作用,对调用被重载过的方法可以大大减少代码的输入量,同一个方法名只要往里面传递不同的参数就可以拥有不同的功能或返回值。用好重写和重载可以设计一个结构清晰而简洁的类,可以说重写和重载在编写代码过程中的作用非同一般。
重载和重写的区别如下
1.定义不同---重载是定义相同的方法名,参数不同;重写是子类重写父类的方法。
2.范围不同---重载是在一个类中,重写是子类与父类之间的。
3.多态不同---重载是编译时的多态性,重写是运行时的多态性。
4.返回不同---重载对返回类型没有要求,而重写要求返回类型,有兼容的返回类型。
5.参数不同---重载的参数个数、参数类型、参数顺序可以不同,而重写父子方法参数必须相同。
6.修饰不同---重载对访问修饰没有特殊要求,重写访问修饰符的限制一定要大于被重写方法的访问修饰符。
关于三个介绍看这篇:
【精选】重载、重定义、重写(覆盖)、隐藏的定义与区别__才疏学浅_的博客-CSDN博客
重写(Override)与隐藏(hide)的区别 - 百度文库
2024年2月22日10:48:02更新
今天 再看这个题目,虽然加少了重载等概念,但是具体选项却没有介绍,今天介绍一下,同时重温了下上述的几个概念,时间长了还是有点忘啊。。。
A选项,子类和父类有相同的函数,参数列表不同。所以是隐藏关系。父类指针调用的是父类的方法。
C选项重载是在同一个类中。
D选项a.test(1.1); 结果是1。但是本身程序有问题,首先test(int b)函数没有定义成public。另外a.test(1.1);要写成a->test(1.1);才行。
4、题目
下列代码编译时会产生错误的是(D)
#include <iostream>
using namespace std;
struct Foo {
Foo() {}
Foo(int) {}
void fun() {}
};
int main(void) {
Foo a(10); //语句①
a.fun(); //语句②
Foo b(); //语句③
b.fun(); //语句④
return 0;
}
A、语句1
B、语句2
C、语句3
D、语句4
解释:在C++中,调用默认构造函数初始化,不应该加()。
5、题目
8.关于c++的inline关键字,以下说法正确的是(D)
A、使用inline关键字的函数会被编译器在调用处展开
B、头文件中可以包含inline函数的声明
C、可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数
D、定义在Class声明内的成员函数默认是inline函数
E、优先使用Class声明内定义的inline函数
F、优先使用Class实现的内inline函数的实现
解释:
A 项错误,因为使用 inline 关键字的函数只是用户希望它成为内联函数,但编译器有权忽略这个请求,比如:若此函数体太大,则不会把它作为内联函数展开的。
B 项错误,头文件中不仅要包含 inline 函数的声明,而且必须包含定义,且在定义时必须加上 inline 。【关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用】
C 项错误, inline 函数可以定义在源文件中,但多个源文件中的同名 inline 函数的实现必须相同。一般把 inline 函数的定义放在头文件中更加合适。
D 项正确,类内的成员函数,默认都是 inline 的。【定义在类声明之中的成员函数将自动地成为内联函数】
EF 项无意思,不管是 class 声明中定义的 inline 函数,还是 class 实现中定义的 inline 函数,不存在优先不优先的问题,因为 class 的成员函数都是 inline 的,加了关键字 inline 也没什么特殊的。
11月13号
1、题目1
假定Qiniuome是一个类,执行下面这些语句之后,内存里创建了几个Qiniuome对象。答案5个。
1 2 3 4 5 6 |
|
2、题目2
C++11 中以下一维整型数据 a 的声明正确的是(B、C、D)
A、int a(10);
B、int n = 10, a[n];
C、int n; scanf("%d", &n); int a[n];
D、#define N 10 int a[N];
3、题目3
3、类B是类A的公有派生类,类A和类B中都定义了虚函数func(),p是一个指向类A对象的指针,则p->A::func()将(答案C)?
A、调用类B中函数func()
B、即调用类A中函数,也调用类B中的函数
C、调用类A中函数func()
D、根据p所指的对象类型而确定调用类A中或类B中的函数func()
解释:D答案因为有了A::func,所以只会指向A类的func(),如果没有A::func,D答案也对。
另外还要补充一个点就是,用派生类指针引用基类的对象,无论怎么强制转换都是不安全的。
父类指针可以指向派生类对象。也即父类指针根据所指的对象类型而确定调用父类或子类的函数。
2023年11月27日18:56:05更新
这个题目如果没有A::func,应该调用的也是A类的func方法吧。
下面开始写代码验证上面的想法。
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
class A
{
public:
A();
virtual ~A();
func()
{
std::cout << "it is A func()" << endl;
}
};
class B : public A //这边会报错'A' is an inaccessible base of 'B',A不能访问B,要加public
{
public:
B();
virtual ~B();
func() //这边不加virtual,不是实现多态
{
std::cout << "it is B func()" << endl;
}
};
int main()
{
A::A* a = new A::A(); //这边会报错error: 'a' was not declared in this scope
B::B* b = new B::B();
a = b;
a->func();
return 0;
}
上面的代码都是错误,下面是正确的代码
class A
{
public:
A()
{
}
virtual ~A()
{
}
virtual func()
{
std::cout << "it is A func()" << endl;
}
};
class B : public A
{
public:
B()
{
}
virtual ~B()
{
}
virtual func()
{
std::cout << "it is B func()" << endl;
}
};
int main()
{
A* a = new A;
B* b = new B;
a->func();
a = b;
a->func();
return 0;
}
//it is A func()
//it is B func()
这边还有个问题就是,类的方法实现可以写在类里吗。保底查找资料是可以的。
类方法可以在类里面进行实现,也可以只在类里声明,在类外实现。只不过在类外实现时要加作用域访问符。
具体文章为此文:类的实现_类可以实现?-CSDN博客
这牵扯到另一个问题,就是为什么要区分cpp和h文件。查找资料这个篇文章是这么说的:
C++把类函数的实现写到头文件能对编译结果有影响吗?
对编译结果(运行效率)没有影响,其实函数实现写到cpp主要只是为了编译效率而非运行效率。
因为头文件不光是你自己用,还需要给「用到你的模块」做include的。
所以,如果你的代码写到头文件内,不光你自己得编译一遍,所有include到你头文件的.cpp代码,也都得把你的代码编译一遍。
不光是上面这个问题,假如你的实现,依赖某个特定的库,那么,如果你在 .cpp 内写实现,那么你只需要在你的 .cpp 文件内包含你引用的第三方库头文件即可。
如果你在 .h 内写实现,那么你就必须把你引用的第三方库头文件也放进你自己的头文件。如此一来,其它include你的头文件的代码也就被迫include了你用到的第三方库头文件。这会造成一些依赖方面的问题。(比如循环依赖)
2023年11月28日11:10:41
关于上面的一个知识点,用派生类指针引用基类的对象,无论怎么强制转换都是不安全的。还是记混了!!
11月20号
1、题目1
在重载运算符函数时,下面()运算符必须重载为类成员函数形式()
A、+
B、-
C、++
D、->
不能重载
5个 | ". " | " .* " | "::" | "sizeof" | "?:" |
前2个不能被重载是保证能正常访问成员,域运算和sizeof不能被重载是因为运算对象是类而不是对象
只能使用成员函数重载,4个: "=" "[]" "->" "()"
只能使用成员函数重载是为了防止出现: 1 = x, 1[x], 1->x, 1(x)这样的合法语句
2、题目2
在32位编译器下sizof(P)为()
class P
{
private:
int ival;
public:
P();
~P();
int GetVal(){
return ival;
};
virtual int SetVal(int val)
{
ival=val;
};
};
A、4
B、8、
C、12
D、16
解释:答案B。
类的大小为类的非静态成员数据的类型大小之和,也就是说静态成员数据不作考虑。 2.普通成员函数与sizeof无关。 3.虚函数由于要维护在虚函数表,所以要占据一个指针大小,也就是4字节。 4.类的总大小也遵守类似class字节对齐的,调整规则。 本题中,int ival占4个字节,同时virtualintSetVal(intval)占用4个字节,一共是占用8个字节,选B
3、题目3
如下程序
1 2 3 4 5 6 |
|
该程序输出结果为()
A、truetrue
B、falsefalse
C、truefalse
D、falsetrue
答案:D。解释:强制类型转换(int)、(int&)和(int*)的区别_int &-CSDN博客这个帖子说的比较好。
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int main()
{
float a = 1;
float b = 2.3;
printf("b:%d\n", (int)b);
printf("%d\n", (int)a);
printf("%d\n", (int &)a);
float c = 2.75;
printf("%d\n", (int)c);
printf("%d\n", (int&)c);
return 0;
}
//b:2
//1
//1065353216
//2
//1076887552
//自己验证,小数强制类型转换成整型,取整数值
//补充2.75的转换成二进制的值为1011,所以2.75的内存中存储形式为0(127+1)11000..(共23位)..0。即【01000000001100000000000000000000】,对应十进制为1076887552。
小数转成二进制怎么转:小数求二进制的方法 - 百度文库
11月27号
1、题目1
int Func(int,int);不可与下列哪个函数构成重载 。答案B;
A、int Func(int,int,int);
B、double Func(int,int);
C、double Func(double,double);
D、double Func(int,double);
解释:重载的参数列表必须要不同。函数的返回类型不能作为区分是否为重载函数。
重载:是指同一可访问区内被声明几个具有不同参数列(参数的类型、个数、顺序)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
2、题目2
哪些因素可能会影响到一个类的大小(多选)答案:A、C、F
解释:
这里A选项描述不够准确,个人觉得不选它是存在争议的,“成员个数”,成员应该包括:静态数据成员,非静态数据成员,静态成员函数,非静态成员函数。成员函数(包括静态和非静态)和静态数据成员都是不占存储空间的。
对象大小 = 虚函数指针 + 所有非静态数据成员大小 + 因对齐而多占的字节
不论有多少个虚函数,都有一个指向虚函数表的指针,占用4字节(32位系统)。
3、题目3
下列函数中,能声明为虚函数的是()?答案BCD
A、构造函数
B、公有成员函数
C、析构函数
D、私有成员函数
解释:
声明虚函数不是为了派生类对基类成员函数的覆盖 既然无论哪种继承方式派生类都不能访问基类的私有成员 那么声明私有成员函数为虚函数还有必要么?
什么样的函数不能声明为虚函数?
- 不能被继承的函数。
- 不能被重写的函数。
1)普通函数
普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,因此声明为虚函数没有意义。因为编译器会在编译时绑定函数。
而多态体现在运行时绑定。通常通过基类指针指向子类对象实现多态。
2)友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
3)构造函数
首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类可以继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另外一个角度来讲,多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。因此构造函数不允许继承。
4)内联成员函数
我们需要知道内联函数就是为了在代码中直接展开,减少函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
5)静态成员函数
首先静态成员函数理论是可继承的。但是静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。
2023年12月9日16:42:56
看上述的这句话:【普通函数不属于成员函数,是不能被继承的。】还蒙了,我是把普通函数和成员函数搞混了。
4、题目4
以下关于类占用内存空间的说法正确的是()答案BC
A、类所占内存的大小是由成员变量(静态变量除外)决定的
B、空类的内存大小是1个字节
C、类中无论有多少个虚函数,只会多占一个虚表指针空间
D、子类的内存大小等于父类的内存大小加上子类独有成员变量的内存大小
解释:选错为ABD
D选项派生对象的大小才为基类存储空间+派生类特有的非static数据成员的空间,D选项共有几点错误:1.派生对象才为两个相加;2.派生类必须为非static数据
另外针对D选项,有人在评论区还提出:D选项:可能子类自身有单独的虚函数,因此仅加上成员变量的内存大小不严谨。
这个倒是之前没有在意。
验证一下:
class A
{
public:
A()
{
publicAData = 1;
protectedAData = 2;
privateAData = 3;
}
virtual ~A()
{
}
public:
void publicAFunc()
{
}
virtual virtualFunc()
{
cout << "this is virtualFunc" << endl;
}
int publicAData;
int protectedAData;
int privateAData;
};
class B : public A
{
public:
B()
{
}
virtual ~B()
{
}
public:
void publicBFunc()
{
}
virtualFunc()
{
cout << "this is virtualFunc" << endl;
}
virtual virtualBFun()
{
//派生类加上virtual, 派生类大小不变
}
int Bdata;
};
int main()
{
int a = sizeof(A); //16
int b = sizeof(B); //20
cout << "A size:" << a << endl; //预测正确
cout << "B size:" << b << endl;
return 0;
}
//A size:16
//B size:20
由验证代码可见,派生类增加的虚函数不会增加派生类的大小。
没查到关于上述说法的贴子,不过查到一片帖子挺不错的,提到虚继承不共享虚表指针。
2024年2月24日15:06:13更新
我今天看这段代码的时候,还在疑惑为什么B size的值是20而不是24。然后我还在codeblock验证了一下,确实是20,我尝试修改代码如下,
...
class B : virtual A
{
...
virtual virtualBFun()
{
//派生类加上virtual, 派生类大小不变
}
int Bdata;
};
int main()
{
int a = sizeof(A); //16
int b = sizeof(B); //24
cout << "A size:" << a << endl; //预测正确
cout << "B size:" << b << endl;
return 0;
}
//虚继承打印结果是16和24
11月28号
1、题目一
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
int pos = 1;
while (pos <= 3) {
st.push(pos++);
}
cout << st.top();
while (pos <= 5) {
st.push(pos++);
}
while (!st.empty()) {
cout << st.top();
st.pop();
}
return 0;
}
述程序的输出为(B)
A、35421
B、354321
C、12453
D、123453
2、题目2
下面程序的输出是(B)
class A
{
public:
void foo()
{
printf("1");
}
virtual void fun()
{
printf("2");
}
};
class B: public A
{
public:
void foo()
{
printf("3");
}
void fun()
{
printf("4");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
A *ptr = (A *)&b;
ptr->foo();
ptr->fun();
return 0;
}
A、121434
B、121414
C、121232
D、123434
解释:1,首先声明为A类型的指针指向实际类型为A的对象,调用的肯定是A的方法,输出1 2, 2,然后声明为A类型的指针指向实际类型为B的对象,则非虚函数调用A的方法,输出1,虚函数调用实际类型B的方法,输出4 3,声明类型为A的指针指向实际类型为B的对象,进行一个强制类型转换,其实这种父类指针指向子类会自动进行类型转换,所以是否强制类型转换都不影响结构,原理同上一步,结果输出1 4 所以最终输出为121414
这边还涉及到一个隐藏的点,评论区别人的解释如下:
大家都觉得很自然,但是没有注意到一个小插曲,就是这个foo()触发的隐藏机制: 派生类的foo()由于函数名,参数与基类都相同,然而又没有virtual修饰,因此不可避免地会触发隐藏。
(一旦有virtual修饰就成覆盖了!搞不清楚隐藏何时触发的同学请百度:重载、覆盖、隐藏的区别)
问题是,看到有同学问: 为什么此处触发隐藏了,p和ptr在调用foo()的时候仍然调用基类的,不是被隐藏了么???
这么问的原因是,很多同学知道了有隐藏这么回事,但是不清楚隐藏触发后会发生什么。 隐藏机制触发之后,指针的调用取决于指针的类型。如果定义的是派生类指针,则该基类成员不可见(隐藏),但是若为基类指针,该基类成员仍然是可见的啊!因为此处的p和ptr均为基类指针,只是分别指向了基类和派生类对象,所以调用foo()的时候仍然是基类的成员。
但是如果定义个派生类指针pb,如下:
B *pb=&b;
pb->foo();
这时只会调用派生类的foo(),虽然B继承自A,但是基类的foo()会被隐藏。
这样看起来似乎莫名其妙,因为你想当然地认为派生类的指针肯定调用自己的成员啊,隐藏存在的意义是什么?就像此题,不用考虑它我也能做对!
但是一旦foo()里有参数的时候,你就会大吃一惊!
假设A中为void foo(float a),B中为void foo(int a):
做如下调用:
B *pb=&b;
pb->foo(3.14);
到底会调用谁?你可能会想: 首先foo()成 员不是虚函数,但是B继承A,B中有两个foo(),调用foo(3.14)时根据参数类型应该匹配基类的void foo(float)成员。
然而并不是!
因为触发了隐藏机制,基类的void foo(float)会被隐藏,所以即使你调用foo(3.14)仍然只会调用派生类的void foo(int)成员。
你的惊讶正好解释了隐藏机制存在的意义。
(PS:牛客网上C/C++专项训练上有专门一道题考察这种情况,当时解释里提出隐藏机制时大多数人也是一脸懵逼)
总结:
1.判断要点:如果不是重载也不是覆盖,派生类和基类中一旦出现同名函数,一定触发隐藏机制(这是个简便判断技巧,你可以考虑除去重载和覆盖的任何同名函数情况,一定满足隐藏机制触发的两条规则)。
2.隐藏触发的结果:指针对成员的函数调用取决于指针类型。
若本身是基类指针(不管指向基类还是派生类)则仍然调用基类成员(不会牵扯到派生类,此处是隐藏,和多态没关系,按第1点已说明隐藏的触发可以首先排除覆盖,也就是多态问题);
若本身是派生类指针,这时你就会看到隐藏的威力!此时不是简单地继承基类的成员,然后根据参数匹配调用,而是隐藏基类成员,只会调用派生类成员。
总结一下几个关键词
1、 | 重载 | 同一个类中,参数列表要不同,返回值可以相同。 |
2、 | 重写 | 子类和父类中,使用virtual实现多态。 函数名和参数列表必须相同。返回值涉及到协变。 |
覆盖 | ||
3、 | 隐藏 | 子类和父类中,只需要函数名一样,父类同名函数都被隐藏。 如果指针是指向父类,还是调用父类同名函数函数。指针是指向字类,只调用子类同名函数,父类全部隐藏。 |
4、 | 重定义 | 同一个类中,参数列表不同,返回值不同。 |
这边再次做的时候还是写成了121412。
3、题目3
有以下程序
#include <iostream>
using namespace std;
class D{
int d;
public:
D(int x=1):d(x){}
~D(){cout<<"D";}};
int main(){
D d[]={_____________};
D* p=new D[2];
delete[]p;
return 0;
}
程序运行的结果是DDDDD,请为横线处选择合适的程序( AB )
A、3,3,3
B、D(3), D(3), D(3)
C、3,3,3,3
D、D(3,3),D(3,3)
解释:程序运行结果为DDDDD说明调用了5次析构函数,所以需要创建5个对象,D* p=new D[2]创建了两个对象D,那么对象数据 D d[]需要创建3个对象D因此排除 选项C,选项的D的初始化方式错误、A、B是对象数组的两种初始化方式,正确
4、题目4
关于C++中的友元函数说法正确的是( BC )
A、友元函数需要通过对象或指针调用
B、友元函数是不能被继承的
C、友元函数没有this指针
D、友元函数破环了继承性机制
11月29号
1、题目1
在c++中,下列描述错误的是( A )
A、在创建对象前,静态成员不存在
B、静态成员是类的成员
C、静态成员不能是虚函数
D、静态成员函数不能直接访问非静态成员
解释:
静态的使用注意事项:
1.静态方法只能访问静态成员(包括成员变量和成员方法)
非静态方法可以访问静态也可以访问非静态
2.静态方法中不可以定义this,super关键字
因为 一个类中,一个static变量只会有一个内存空间,虽然有多个类实例,但这些类实例中的这个static变量会共享同一个内存空间。静态方法在优先于对象存在,所以静态方法中不可以出现this,super关键字。
3.主函数是静态的。
程序运行的时候,静态成员已经加载在内存里面了,但是包含静态成员的对象共享这些静态成员,
比方说,A有一个静态成员public static int i;那么程序运行的时候,这个i就加载进内存了,A的所有对象的i变量都指向这个静态空间的i,也就是说创建对象之前,它就占空间了1:只有类的成员函数才能说明为虚函数;
2:静态成员函数不能是虚函数;
3:内联函数不能为虚函数;
4:构造函数不能是虚函数;
5:析构函数可以是虚函数,而且通常声明为虚函数
2、题目2
若执行以下程序段
int x=3,y=6,z; z=x^y<<2;
则z的二进制值是______A_________
A、00011011
B、00010100
C、00011000
D、00000110
3、题目3
以下说法正确的是( ABCDE)。
A、内联(inline)函数改善了函数调用的执行效率。
B、类A的友元(friend)函数可以访问类A的私有成员。
C、类A的友元(friend)类B可以访问类A的私有成员。
D、类A的静态数据成员可以被类A的所有对象调用。
E、类A的静态成员函数没有传递this 指针作为参数。
4、题目4
下面关于面向对象的一些理解哪些是错误的( C )
A、面向对象的最重要的特性是支持继承、封装和多态
B、系统设计应该遵循开闭原则,系统应该稳定不可修改,但应支持通过继承、组合等方式进行扩展
C、函数式的语言必然是面向对象的语言
D、面向对象设计时,每个类的职责应该单一,不要再一个类中引入过多的接口
E、过程式语言和面向对象的语言各有其优势,过程式语言更加灵活,面向对象语言更加强调抽象和封装
F、Java和C++都是静态类型的面向对象编程语言
解释:错选为C、F
动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。 ...
11月30号
1、题目1
下面题目的输出结果为:答案B。
using namespace std;
class A{
public:
virtual void f() { cout << "A::f() "; }
void f() const { cout << "A::f() const "; }
};
class B : public A {
public:
void f() { cout << "B::f() "; }
void f() const { cout << "B::f() const "; }
};
void g(const A* a) {
a->f();
}
int main(int argc, char *argv[]) {
A* p = new B();
p->f();
g(p);
delete(p);
return 0;
}
A、B::f() B::f() const
B、B::f() A::f() const
C、A::f() B::f() const
D、A::f() A::f() const
解释:
该问题包含两个关键点:(1)基类指针指向派生类的问题;(2)const相关
当基类指针指针指向派生类:a. 如果调用的成员函数在基类中定义为虚函数,在派生类中重写了(函数覆盖),则该成员函数指的是派生类中的;b. 相反如果未在基类中定义成为虚函数,则调用的函数其实是基类中的。
函数覆盖(多态)的条件:
- 1: 基类中的成员函数被virtual关键字声明为虚函数;
- 2:派生类中该函数必须和基类中函数的名称、参数类型和个数等完全一致;
- 3:将派生类的对象赋给基类指针或者引用,实现多态。
至于const, 加了const的成员函数可以被非const对象和const对象调用 但不加const的成员函数只能被非const对象调用
2、题目2
下面关于迭代器失效的描述哪个是错误的(A)
A、vector的插入操作不会导致迭代器失效
B、map的插入操作不会导致迭代器失效
C、vector的删除操作只会导致指向被删除元素及后面的迭代器失效
D、map的删除操作只会导致指向被删除元素的迭代器失效
解释:vector 动态增加大小时,并不是在原空间后增加新空间,而是以原大小的两倍在另外配置一个较大的新空间,然后将内容拷贝过来,接着再原内容之后构造新元素,并释放原空间,由于插入操作改变了空间,故迭代器会失效。
3、题目3
下面有关函数模板和类模板的说法正确的有?(AB)
A、函数模板的实例化是由编译程序在处理函数调用时自动完成的
B、类模板的实例化必须由程序员在程序中显式地指定
C、函数模板针对仅参数类型不同的函数
D、类模板针对仅数据成员和成员函数类型不同的类
解释:
C:函数模版还可以将 函数返回值类型 作为模版参数。
D:类模板还可以针对 继承的基类类型 作为模板参数
11月31号
1、题目1
如果类的定义如下,则以下代码正确并且是良好编程风格的是:答案A
class Object
{
public:
virtual ~Object() {}
//…
};
A、std::auto_ptr<Object> pObj(new Object);
B、std::vector<std::auto_ptr<Object*> > object_vector;
C、std::auto_ptr<Object*> pObj(new Object);
D、std::vector<std::auto_ptr<Object> > object_vector;
解释:
auto_ptr已弃用,应当使用unique_ptr。
不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr
如果使用shared_ptr , 可以使用vector来把指针存到一起
2、题目2
从c++文件到生成exe文件经过哪些步骤?
A、预处理、汇编、编译和链接
B、编译、预处理、汇编和链接
C、预处理、编译、汇编和链接
D、预处理、编译、链接和汇编
解释:
1、预处理
在预处理阶段,编译器主要作加载头文件、宏替换、条件编译的作用。一般处理带“#”的语句。
2、编译
在编译过程中,编译器主要作语法检查和词法分析。我们可以通过使用 -S 选项来进行查看,该选项预处理之后的结果翻译成汇编代码。
3、汇编
在汇编过程中,编译器把汇编代码转化为机器代码。
4、链接
链接就是将目标文件、启动代码、库文件链接成可执行文件的过程,这个文件可被加载或拷贝到存储器执行。
11月31号下午
1、题目1
以下程序的输出是:答案C。
class Base {
public:
Base(int j): i(j) {}
virtual~Base() {}
void func1() {
i *= 10;
func2();
}
int getValue() {
return i;
}
protected:
virtual void func2() {
i++;
}
protected:
int i;
};
class Child: public Base {
public:
Child(int j): Base(j) {}
void func1() {
i *= 100;
func2();
}
protected:
void func2() {
i += 2;
}
};
int main() {
Base * pb = new Child(1);
pb->func1();
cout << pb->getValue() << endl; delete pb;
}
A、11
B、101
C、12
D、102
解释:
可以让成员函数操作一般化,用基类的指针指向不同的派生类的对象时, 基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数, 而不是基类中定义的成员函数(只要派生类改写了该成员函数)。 若不是虚函数,则不管基类指针指向的哪个派生类对象,调用时都 会调用基类中定义的那个函数。
这句话我一开始根本看不懂,然后就去百度,还写了下面的验证代码。现在理解了一些,我来重新自己写一下:基类可以指向其子类对象,在实现调用过程中,基类指针调用虚函数时,是调用实际对象的成员函数。如果调用不是虚函数,调用的是基类的函数。如果这个是子类指针,就调用子类的函数。
class A
{
public:
A()
{
}
virtual ~A()
{
}
void funWithoutVirtual()
{
std::cout << "it is A funWithoutVirtual()" << endl;
}
virtual func()
{
std::cout << "it is A func()" << endl;
}
};
class B : public A
{
public:
B()
{
}
virtual ~B()
{
}
void funWithoutVirtual()
{
std::cout << "it is B funWithoutVirtual()" << endl;
}
virtual func()
{
std::cout << "it is B func()" << endl;
}
};
int main()
{
A* a = new A;
B* b = new B;
a->func();
a = b;
a->func();
A* test1 = new B;
test1->funWithoutVirtual();
A* test2 = new A;
test2->funWithoutVirtual();
B* test3 = (B*)new A; //有风险
//B* test3 = new A; //报错
test3->funWithoutVirtual();
B* test4 = new B;
test4->funWithoutVirtual();
return 0;
}
//it is A func()
//it is B func()
//it is A funWithoutVirtual()
//it is A funWithoutVirtual()
//it is B funWithoutVirtual()
//it is B funWithoutVirtual()
另外搜索了下面的这个帖子,里面这样描述:
- 父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public)
- 子类指针指向父类对象是不安全的
记录一下:
12-C++面向对象(父类指针、子类指针、多态、虚函数、虚表)_c++ 父类指针_get-yuan的博客-CSDN博客
2024年3月27日10:18:04更新
现在再看这个题目写的关于解释的疑惑,就感觉是很简单的,说明有进步了。但是今天再看这个题目,关于为什么结果是12还是一脸懵逼的。看了解释大概意思是fun2调用了子类的虚函数,但是是作用域基类的i?难道不是最用与子类的i吗?看结果应该是作用域基类的i,是出乎我的理解的。
总结
转接下一贴:C++牛客知识点2-CSDN博客