我想说
每天坚持一点点,坚持带来大改变
今天是刷题的第_9天,加油!
一、选择题
动态内存开辟和释放需要匹配使用
malloc/realloc/calloc | free new | delete new[] | delete[]
如果不匹配的话可能会出现内存泄漏,或者程序直接崩溃
对于new和delete,(假设对类A)
A* p = new A
- 调用`operator new(size)进行申请空间(operator new的底层调用malloc)
- 调用A的构造函数进行初始化
delete p
- 调用A的析构函数清理对象中的资源
- 调用operator delete释放p所指向的空间(operator delete底层调用free)
对于new[]和delete[]
A* p = new A[N]
- 调用operator new[] (size)申请N个对象的空间(operator new[] ()底层调用operator new)
- 调用N次构造函数
delete p[]
- 调用N次析构函数清理N个对象中的资源
- 调用operator delete[](p)(释放空间(operator delete[](p)底层调用operator delete( p) )
所以本题中 new[5]:调用了五次构造函数
delete :只会调用一次析构函数 答案选A
但选项并不是很严谨,因为对于内置类型来说 new[] 搭配 delete使用不会报错,但是对于自定义类型成员new和delete必须匹配使用,否则就会崩溃!所以理应有个选项为:程序可能崩溃
对于常规来说,malloc和new的空间都在堆上申请
但是new的底层调用operator new来申请空间,而operator new是可以用户自己重载的,如果重载了就会调用自己重载的operator new,而自己重载的operator new可以实现不在堆上申请空间!
对于本题来说,只能选B(题目指的是默认情况下)
成员函数有一个隐藏的形参:this
所以如果参数列表中没有任何参数,他一定是一个一元运算符
对于前缀和后缀,C++是这样规定的:
如果是后缀运算符,在参数列表中加一个int,表示是后缀
所以如果参数列表中一个参数都没有,就是一元前缀运算符
如果有一个int参数,就是一元后缀运算符
所以答案选C
C++中创建对象有两种方式,一种是静态创建:A aa
一种是动态创建:A* ptr = new A
1、静态建立类对象:是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。使用这种方法,直接调用类的构造函数。
2、动态建立类对象,是使用new运算符将对象建立在堆空间中。这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。
如果只能在堆上(即只能用new)创建对象的话,一般就容易想到把构造函数私有,因为构造函数私有之后就不可以在类外部通过调用构造函数来创建对象了,但是!对于new运算符来说,new一个对象分为两步,第一步就是调用operator new开辟空间,第二步就是调用类的构造函数进行初始化。所以很显然如果构造函数私有,new也无法成功创建对象!并且只允许重载operator new,所以我们无法做到调用私有的构造函数
在思考一个问题,如果在栈上创建一个对象,那么他的空间是由编译器分配的。
从调用构造函数来构造创建对象时的空间分配,到对象使用完编译器自动调用析构函数来释放对象所占的空间,都是编译器管理的。那么如果析构函数设置为私有,会怎么样?编译器就无法调用私有的析构函数!事实上,编译器在为类对象分配栈空间的时候,会先检查析构函数以及其他非静态成员函数的访问权。如果析构函数是私有的,那么编译器就不会在栈空间上为类对象分配内存,因为分配了就无法释放了! 所以,析构函数设置为私有,就无法把类对象创建在栈上了!
那如果析构函数设置为私有,如何释放new的对象呢?
在析构函数私有的时候,通过new创建的对象无法直接通过delete来释放对象空间(因为delete会调用析构函数,析构函数私有,私有成员函数类外部无法直接访问),但是可以在类的内部提供一个接口 Destory,Destory为成员函数可以访问析构函数,所以在Destory中显式调用析构函数,这样new出来的对象就可以通过调用Destory来释放了!
验证:当析构函数为私有的时候,根本不会创建对象:
所以此题,选择B,把析构函数设置为私有。
二、编程题
1. 另类加法
思路1:利用构造函数
-
定义一个内部类,和一个静态成员变量count,每次调用该类的构造函数,静态变量count++
-
在接口函数中,new两个内部类对象的数组,一个大小是A,一个大小是B
就相当于count++了(A+B)次
-
注意:有多次测试用例,而静态变量count是在类外部进行初始化,并且所有对象共享一个该成员变量,所以每次调用接口应该先初始化为0
class UnusualAdd {
public:
int addAB(int A, int B) {
count = 0; //每次调用先初始化,防止多次用例
// write code here
Inner* p = new Inner[A];
Inner* p1 = new Inner[B];
return count;
}
class Inner{
public:
Inner()
{
++count;
}
};
private:
static int count;
};
int UnusualAdd::count = 0;
思路2:位运算
对于两个数n1和n2,我们这样做:
- 两个数二进制位依次相加,保存其不考虑进位的结果 a(异或实现)
- 求出二进制位相加时 进位的数值 b
- 然后求二者之和 a+b(这里a作为n1,b作为n2进行递归)
- 当n1和n2有一个为0的时候,另一个就是最终结果
举个例子:1 + 3
class UnusualAdd {
public:
int addAB(int A, int B) {
// write code here
//递归结束条件
if(A==0) return B;
if(B==0) return A;
int a = A ^ B;//求不考虑进位的和
int b = (A & B) << 1;//求进位
//递归
return addAB(a, b);
}
};
2. 走方格的方案数
👉 题目链接
代码如下(示例):
走方格的方案数
思路:动态规划
- 最终结果:[0,0]位置的值就是[0,0]位置到右下角的方案数!
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int n,m;
cin>>n>>m;
//n列格子 m行格子,那么就有n+1和m+1条线
vector<vector<int>> vv;
vv.resize(n+1);
for(size_t i = 0;i<n+1;++i)
{
vv[i].resize(m+1,1);
//把最后一行和最后一列设置为1,所以直接全部设置为1
}
//从[m-1,n-1]向前遍历
for(int i = n-1;i>=0;--i)
{
for(int j = m-1;j>=0;--j)
{
//填表
vv[i][j] = vv[i+1][j] + vv[i][j+1];
}
}
//最后 返回[0,0]位置的元素
cout << vv[0][0] << endl;
return 0;
}
递归写法
因为要走到[i,j]
要么是从[i-1,j]
过来的,要么就是从[i,j-1]
过来的
所以用一个递归来实现也可以
#include<iostream>
using namespace std;
int path(int n,int m)
{
//走到第一行 或者 第一列,从[0,0]走到该位置只有一种走法
if(n==0 || m==0)
{
return 1;
}
//返回左上角到该位置左边 和 该位置上面方案之和
return path(n-1,m) + path(n,m-1);
}
int main()
{
int n,m;
cin >> n >> m;
cout << path(n,m) << endl;
}