赞
踩
一、
- #include <iostream>
- using namespace std;
-
- 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;
- return 0;
- }
输出的结果是(102)
官方解释:
基类Base
:定义了一个整数成员i
和两个虚函数func1
和func2
。func1
函数首先将i
乘以10,然后调用func2
。func2
在基类中的实现是将i
加1。
派生类Child
:从Base
类公开继承。它重写了func1
和func2
函数。在Child
中,func1
首先将i
乘以100,然后调用func2
。func2
在派生类中的实现是将i
加2。
main
函数:创建了一个指向Child
对象的Base
指针pb
。通过pb
调用func1
函数。由于多态性,将调用Child
类中的func1
。最后,输出i
的值并删除动态分配的对象。
程序的输出将取决于func1
和func2
的调用顺序以及它们在基类和派生类中的实现。在这个例子中,由于Child
类重写了func1
和func2
,所以最终i
的值将会是1 * 100 + 2 = 102
(Child
类的func1
乘以100,然后Child
类的func2
加2)。输出将是102
。
自己的理解:
假设现在我们有两个这样的魔法盒子,一个是爸爸的,一个是孩子的。爸爸的盒子只有“变大10倍并加1”这个按钮,而孩子的盒子既有“变大10倍并加1”按钮,又有“变大100倍并加2”按钮。
但是,孩子很调皮,他把他的盒子给了爸爸,并且让爸爸以为这就是他的盒子。当爸爸按下“变大10倍并加1”这个按钮时,其实他按的是孩子盒子的按钮,所以盒子的东西会按照孩子的规则来变大。
假设一开始盒子里有1个苹果,当爸爸按下按钮后,这个苹果会先变成100个(因为是按照孩子的盒子的“变大100倍”规则),然后再加2个,最后盒子里就有102个苹果了。
最后,爸爸会告诉我们盒子里有多少个苹果,我们就知道了。
在这个例子中,爸爸的盒子就像是基类Base
,孩子的盒子就像是派生类Child
。爸爸按下的按钮就像是基类的函数func1
,而孩子盒子的特殊功能就像是派生类重写的函数。因为爸爸实际上拿的是孩子的盒子,所以按下按钮后,会按照孩子的规则来变化
考点:
多态性:通过基类指针调用派生类对象的虚函数时,实际调用的是派生类中定义的版本。这里 pb->func1();
和 pb->func2();
展示了多态性的使用。
虚函数:func2
被声明为 protected
和 virtual
,这意味着它可以在派生类中被重写,同时保证多态性的行为。
构造函数和析构函数:构造函数用于初始化对象,析构函数用于清理对象。这里 Base
和 Child
类的构造函数展示了如何初始化基类成员变量,而析构函数则展示了如何安全地销毁对象。
继承:Child
类公开继承自 Base
类,这意味着 Child
对象可以使用 Base
类的公有和保护成员。
访问控制:Base
类中的 func2
和 i
被声明为 protected
,这意味着它们可以在派生类中被访问,但不能从 Base
类的对象直接访问。
内存管理:使用 new
和 delete
进行动态内存分配和释放是 C++ 中的一个重要概念,这里展示了如何正确地使用它们。
错误处理:虽然代码中没有显式展示错误处理,但在实际编程中,处理动态内存分配失败(虽然在现代系统上很少见)或其他异常情况也是重要的考点。
1)
- void GetMemory(char*p)
- {
- p=(char*)malloc(100);
- }
- void Test(void)
- {
- char *str=NULL;
- GetMemory(str);
- strcpy(str,"hello world");
- printf(str);
- }
问题一:GetMemory
函数中的指针修改不反映到 Test
函数中。
在 GetMemory
函数中,p
是一个指向 char
的指针的本地副本。当你将 p
指向 malloc
分配的内存时,Test
函数中的 str
并没有改变,因为它传递的是 str
的一个副本,而不是 str
本身的地址。因此,Test
函数中的 str
仍然是 NULL
。
问题二:内存泄漏。
由于 GetMemory
函数中的 malloc
分配的内存没有被释放,这会导致内存泄漏。
问题三:strcpy
可能导致未定义行为。
由于 str
是 NULL
,尝试使用 strcpy
将字符串复制到它指向的位置会导致未定义行为,通常是程序崩溃。
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- void GetMemory(char **p) {
- *p = (char*)malloc(100);
- if (*p == NULL) {
- // 错误处理,分配内存失败
- exit(EXIT_FAILURE);
- }
- }
-
- void Test(void) {
- char *str = NULL;
- GetMemory(&str); // 传递str的地址
- if (str != NULL) {
- strcpy(str, "hello world");
- printf("%s\n", str);
- free(str); // 释放分配的内存
- }
- }
-
- int main() {
- Test();
- return 0;
- }
GetMemory
函数现在接受一个指向 char
指针的指针(即 char **
),这样它就可以修改 Test
函数中的 str
。malloc
分配内存后,我们检查是否成功分配了内存。Test
函数中,我们检查 str
是否为 NULL
,然后才尝试使用 strcpy
。Test
函数中释放它,以避免内存泄漏。malloc
分配的内存应该用 free
来释放,而且应该在所有使用完这块内存的地方都这么做 自己的理解:
想象你有一个存钱罐,你想要放更多的钱进去,但是你还没有足够的硬币。于是,你决定去银行取一些钱。
GetMemory
函数就像是你去银行取钱的过程。你告诉银行你想要100个硬币(这就像是分配100个字节的内存)。银行给你一个装满硬币的袋子(这就像是malloc
返回的内存地址)。
Test
函数就像是你回到家,想要把取回来的钱放进存钱罐里。但是,如果你忘记从银行带钱回来(也就是说,str
仍然是NULL
),你尝试往存钱罐里放钱时,就会出问题,因为没有钱可以放(这会导致程序崩溃)。
为了避免这个问题,你在GetMemory
函数里不是直接告诉银行你要取钱,而是告诉银行你存钱罐的地址(这就像是传递str
的地址,即&str
)。这样,银行就能直接把装满硬币的袋子放到你的存钱罐里。
当你把钱放进存钱罐后,记得以后不用了要把钱放回银行,不然你的家就会堆满钱,没地方放了(这就像是内存泄漏)。所以,在Test
函数最后,我们使用free(str)
来释放我们之前分配的内存。
这样,你就学会了如何正确地取钱(分配内存)和存钱(释放内存),而不会让家里变得乱七八糟。
2)
- #include<iostream>
- using namespace std;
- class A
- {
- public:
- A()
- {
- cout<<"new A"<<endl;
- m=(char*)malloc(10);
- }
- ~A()
- {
- cout<<"del A"<<endl;
- }
- protected:
- char *m;
- }
- class B:public A
- {
- public:
- B()
- {
- cout<<"new B"<<endl;
- m=(char*)malloc(100);
- }
- ~B()
- {
- cout<<"del B"<<endl;
- }
- protected:
- char*m;
- };
-
- int main()
- {
-
- A*c=new B;
- delete c;
- }
错误和需要注意的地方:
class B
中重定义了 m
成员变量。在继承关系中,如果基类 A
和派生类 B
中都有名为 m
的成员变量,那么它们将被视为两个不同的变量。这可能会导致混淆和错误。
在 main
函数中,你使用 new B
创建了一个 B
类的对象,但是将其赋值给了一个 A
类型的指针 c
。虽然这是合法的(因为 B
是从 A
公有继承的),但需要注意,当你通过 A
类型的指针访问 m
成员时,你会访问到基类 A
中的 m
,而不是派生类 B
中的 m
。
在 A
和 B
的构造函数中,你使用 malloc
为 m
分配了内存,但在析构函数中并没有使用 free
来释放这些内存。在C++中,更推荐使用 new
和 delete
来管理动态内存,因为 new
会自动调用对象的构造函数,而 delete
会自动调用析构函数。
- #include<iostream>
- using namespace std;
-
- class A {
- public:
- A() {
- cout << "new A" << endl;
- m = new char[10]; // 使用 new 分配内存
- }
- virtual ~A() { // 声明为虚析构函数,确保派生类的析构函数也会被调用
- cout << "del A" << endl;
- delete[] m; // 使用 delete[] 释放内存
- }
- protected:
- char *m;
- };
-
- class B : public A {
- public:
- B() {
- cout << "new B" << endl;
- // 注意:这里不应该再次分配 m,因为 m 已经在 A 的构造函数中被分配了。
- // 如果你确实需要在 B 中分配新的内存,你应该使用一个不同的变量名。
- }
- ~B() {
- cout << "del B" << endl;
- // 在这里,我们不需要释放 m,因为 A 的析构函数会负责释放它。
- // 如果 B 有自己的内存需要释放,应该在这里做。
- }
- };
-
- int main() {
- A* c = new B; // 创建 B 的对象,但用 A 的指针指向它
- delete c; // 使用 delete 释放 c 指向的对象,这会先调用 B 的析构函数,然后调用 A 的析构函数
- return 0;
- }
在这个修改后的版本中,我将 A
的析构函数声明为虚函数(virtual ~A()
)。这是为了确保当通过基类指针删除派生类对象时,派生类的析构函数也会被调用。此外,我还使用了 new
和 delete[]
来分配和释放内存,这是C++中更推荐的做法。
3)
- #include<iostrem>
- using namespace std;
-
- class Test
- {
- public:
- long a;
- long b;
- virtual void fun(){}
-
- Test(long temp1=0,long temp2=0)
- {
-
- a=temp1;
- b=temp2;
- }
-
- long getA(){return a;}
- long getB(){return b;}
- };
-
- int maint()
- {
-
- Test obj(5,10);
- long * pint =(long*)&obj;
- *(pint)=100;
- *(pint+1)=200;
- cout<<"a="<<obj.getA()<<endl;
- cout<<"b="<<obj.getB()<<endl;
- return 0;
- }
程序在64位机器上打印输出结果是(a=100,b=200)
官方解释:
在 main
函数中,你创建了一个 Test
类的对象 obj
,并初始化了 a
为5,b
为10。然后,你创建了一个指向 long
的指针 pint
,并将其设置为指向 obj
的地址。接下来,你通过 pint
修改了 obj
的内存内容。
- long * pint =(long*)&obj;
- *(pint)=100;
- *(pint+1)=200;
这里,*(pint)=100;
将 obj
的起始地址(即 a
的值)设置为100。*(pint+1)=200;
将 obj
起始地址后8字节的位置(即 b
的值)设置为200。
因此,obj
的成员变量 a
和 b
现在分别被设置为100和200。
输出将是:a=100 b=200。
自己的理解:
当然可以,让我们用小学生能懂的方式和生活中的例子来解释这段代码。
首先,想象一下你有一个魔法盒子,这个盒子里面可以放两个魔法球,每个魔法球都有一个数字。这个魔法盒子就是我们代码中的Test
类,而两个魔法球就是a
和b
这两个变量。
现在,我们有一个特别的咒语,可以让这个魔法盒子里的魔法球变出数字来。这个咒语就是Test
类的构造函数,它可以在我们创建魔法盒子的时候,给两个魔法球分别赋予数字。
- Test(long temp1=0,long temp2=0)
- {
- a=temp1;
- b=temp2;
- }
比如说,我们念这个咒语:“魔法盒子,给我一个5和一个10!”然后,魔法盒子里的两个魔法球就分别变成了5和10。
在main
函数中,你创建了一个这样的魔法盒子,并给它念了咒语,让里面的两个魔法球变成了5和10。
接下来,你有一个魔法棒,这个魔法棒可以让你直接看到魔法盒子里面的东西。但是,你这次没有用魔法棒去看,而是直接用手去摸魔法盒子,并试图改变魔法球里的数字。
- long * pint =(long*)&obj;
- *(pint)=100;
- *(pint+1)=200;
你用手伸进了魔法盒子,摸到了第一个魔法球,然后把它变成了100。接着,你又摸到了第二个魔法球,把它变成了200。
最后,你用魔法棒去看魔法盒子里的魔法球,发现它们已经变成了100和200,而不是原来的5和10了。
- cout<<"a="<<obj.getA()<<endl;
- cout<<"b="<<obj.getB()<<endl;
所以,这个程序在64位机器上打印的输出结果是:a=100 b=200;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。