构造函数与析构函数的执行顺序
#include <iostream>
using namespace std;
class AA
{
public:
AA(int i,int j)
亻
A = i;
B =j;
cout<<"Constructing("<<A<<","<<B<<").\n";
cout<<"Constructing("<<A<<","<<B<<").\n";
}
void print(){cout<<A<<","<<B<<endl;}
private:
int A. B;
};
void main()
{
AA *a1,*a2;
AA a3(3,4),a4(7,8);
al =new AA(1,2);
a2=new AA(5,6);
al->print();
a3. print();
a2->print();
a4. print();
delete al;
delete a2;
}
/*运行结果:
Constructing(3,4).
Constructing(7,8).
Constructing(1,2).
Constructing(5,6).
1,2
3,4
5,6
7,8
Destructed(1,2).
Destructed(5,6).
Destructed(7,8).
Destructed(3,4).*/
结果分析:
本题主要考查构造函数、析构函数的调用时机。
①在定义类对象指针的时候并不会初始化对象,接下来定义类对象后调用构造函数初始化;
②对类指针用new初始化时调用构造函数;
③对指针存储空间的释放需要用delete,这时先调用delete所对应指针对象的析构函数,再根据构造函数执行顺序反序调用对象的析构函数。
注:先delete那个就先调用其析构函数,不需要根据构造函数执行顺序反序
涉及到继承的情况
#include <iostream>
using namespace std;
class Base
{
public:
Base(){a=0;}
Base(int i){ a = i;}
void print(){cout <<"a="<<a<<",";}
int Geta(){return a;}
~Base(){cout <<"Destructor Base a="<<a<<endl;}
private:
int a;
};
class Derived:public Base
{
public:
Derived(){b=0;}
Derived(int i,int j,int k):Base(i),Ba(j){b=k;}
~Derived(){cout<<"Destructor Derived b="<<b<<endl;}
void print();
private:
int b;
Base Ba;
};
void Derived::print()
{
Base::print();
cout<<"b="<< b<<",Ba. a="<< Ba. Geta() << endl;
}
int main()
{
Derived d1,d2(5,3,7);
d1. print();
d2. print();
return 0;
}
/*
a=0,b=0,Ba. a=0
a=5,b=7,Ba. a=3
Destructor Derived b=7
Destructor Base a=3
Destructor Base a=5
Destructor Derived b=0
Destructor Base a=0
Destructor Base a=0
*/
构造函数顺序:先调用继承类的构造函数、再是本类子对象对应类的构造函数、最终是自身构造函数。注:考试的时候不管有没有构造函类的输出,大家最好自己写出构造函数顺序反推机构函数顺序,更保险。
虚继承eg1
#include<iostream>
using namespace std;
class A
{
int x;
public:
A(int xx=0):x(xx){cout<<"A constructor.\n";}//一
~A(){cout<<"A destructor.\n";}
void output(){cout<<x<<" ";}
int Getx(){return x;}
};
class B
{
int y;
A a;//二
public:
B(int xx=0,int yy=0):a(xx),y(yy){cout<<"B constructor.\n";}//三
~B(){cout<<"B destructor.\n";}
int Gety(){return y;}
int Geta(){return a. Getx();}
};
class C:public A,virtual public B
{
public:
C(int x=0,int y=0):A(x),B(x,y){cout<<"C constructor.\n";}//四
void output()
{
A::output();
cout<<Getx()<<" "<<Gety()<<" "<<Geta()<<endl;
}
~C(){ cout<<"C destructor. \n";}
};
int main()
{
C aa(4,8);
aa.output();
return 0;
}
//A constructor.//二
//B constructor.//三
//A constructor.//一
//C constructor.//四
//4 4 8 4
//C destructor.
//A destructor.
//B destructor.
//A destructor.
个人分析:虚继承的构造函数永远是最先执行的,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数。
虚继承eg2
#include <iostream>
using namespace std;
//虚基类A
class A{
public:
A(int a);
protected:
int m_a;
};
A::A(int a): m_a(a){cout<<"A constructor\n"; }
//直接派生类B
class B: virtual public A{
public:
B(int a, int b);
public:
void display();
protected:
int m_b;
};
B::B(int a, int b): A(a), m_b(b){ cout<<"B constructor\n";}
void B::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
}
//直接派生类C
class C: virtual public A{
public:
C(int a, int c);
public:
void display();
protected:
int m_c;
};
C::C(int a, int c): A(a), m_c(c){cout<<"C constructor\n";}
void C::display(){
cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
}
//间接派生类D
class D: public B, public C{
public:
D(int a, int b, int c, int d);
public:
void display();
private:
int m_d;
};
D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ cout<<"D constructor\n";}
void D::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}
int main(){
B b(10, 20);
b.display();
cout<<endl;
C c(30, 40);
c.display();
cout<<endl;
D d(50, 60, 70, 80);
d.display();
return 0;
}
/*
运行结果:
A constructor
B constructor
m_a=10, m_b=20
A constructor
C constructor
m_a=30, m_c=40
A constructor
B constructor
C constructor
D constructor
m_a=50, m_b=60, m_c=70, m_d=80
*/
在最终派生类 D 的构造函数中,除了调用 B 和 C 的构造函数,还调用了 A 的构造函数,这说明 D 不但要负责初始化直接基类 B 和 C,还要负责初始化间接基类 A。而在以往的普通继承中,派生类的构造函数只负责初始化它的直接基类,再由直接基类的构造函数初始化间接基类,用户尝试调用间接基类的构造函数将导致错误。
现在采用了虚继承,虚基类 A 在最终派生类 D 中只保留了一份成员变量 m_a,如果由 B 和 C 初始化 m_a,那么 B 和 C 在调用 A 的构造函数时很有可能给出不同的实参,这个时候编译器就会犯迷糊,不知道使用哪个实参初始化 m_a。
为了避免出现这种矛盾的情况,C++ 干脆规定必须由最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C 对 A 的构造函数的调用是无效的。在第 50 行代码中,调用 B 的构造函数时试图将 m_a 初始化为 90,调用 C 的构造函数时试图将 m_a 初始化为 100,但是输出结果有力地证明了这些都是无效的,m_a 最终被初始化为 50,这正是在 D 中直接调用 A 的构造函数的结果。
另外需要关注的是构造函数的执行顺序。虚继承时构造函数的执行顺序与普通继承时不同:在最终派生类的构造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的。
拷贝构造函数的调用
C++中的拷贝构造函数在下面哪些情况下会被调用()BCD
A、对象创建的时候
B、使用一个类的对象去初始化该类的一个新对象
C、被调用函数的形参是类的对象
D、当函数的返回值是类的对象时,函数执行完成返回调用者
strlen的详细用法
#include<iostream>
#include<cstring>
using namespace std;
void main()
{
char s1[]="Hello C++";
cout<<"sl:"<<s1<<" "<<strlen(s1)<<endl;
char *s2="string";
cout<<"s2:"<<s2<<" "<<strlen(s2)<<endl;
char s3[]="Hello \0backslash C++";
cout<<"s3:"<<strlen(s3)<<endl;
cout<<"s4:"<<strlen("asdf ghjk lmnᵐ)<cndl;
char s5[20];
cin>>s5;//输入hello world
cout<<"s5:"<<s5<<" "<<strlen(s5)<<endl;
}
//s1:Hello C++ 9
//s2:string 6
//s3:6
//s4:13
//s5:hello 5//因为空格相当于结束输入
结果分析:
本题主要考查字符串的概念。在自定义的字符串中,‘\0’为字符串结束的标志,如果在’\0’之后还有内容,不管后面是什么均被忽略,因此在本题中s3的长度仅为’hello^{,}的长度5。对比之下,如果是输入的字符串的话,遇到空格或指标符后,只将第一个空格或制表符前的字符串赋值,后面的均不算入。
表达式优先级集锦
#include<iostream>
using namespace std;
int main()
{
int a,b,c,x,y;
a = 1;
b=2;
c = 0;
cout << a++-1 <<endl;
cout << (a && b || !c) << endl;
cout << b/++a << endl;
cout <<a<<" "<<b<<" "<<c<<endl;
x = ++a || ++b && ++c;
cout <<a<<" "<<b<<" "<<c<<" "<<x<<" "<<endl;
a = b = c = -1;
y = ++a && ++b && ++c;
cout <<a<<" "<<b<<" "<<c<<" "<<y<<" "<<endl;
return 0;
}
/*
0
1
0
3 2 0
4 2 0 1
0 -1 -1 0
*/
运行结果:
结果分析:
①执行a+±1时先计算a-1为0,输出0,此时a自增1变为2
②从左到右顺次执行a&&b||!c,a为2非0,b为2非0,因此a&&b为真。故||左端为真,因此整个表达式为真,值为1,输出1。
③b/++a中,先执行a自增1操作变为3,继续运算b/a即2/3结果为0,输出0。
④这行代码中的运算符优先级顺序是:++,&&,||,=。所以,这行代码的执行顺序是:
++a:先执行自增运算,将变量 a 的值加 1。
++b && ++c:然后执行逻辑与运算。如果 ++b 的值为真,则执行 ++c,将变量 c 的值加 1。
++a || (++b && ++c):接着执行逻辑或运算。如果 ++a 的值为真,则不再执行 ++b && ++c。
x = ++a || (++b && ++c):最后执行赋值运算,将 ++a || (++b && ++c) 的结果赋给变量 x。
⑤abc均被赋为-1 后,执行y=++a&&++b&&++c;, 首先a自增为0,此时已经能判断y的值为0,故++b、++c被逻辑短路,此时b=c=-1。这时,a=0,b=-1,c=-1,y=0。
关于优先级可以看我的另一篇文章C++重载和运算符优先级
析构函数的执行顺序
#include<iostream>
using namespace std;
class A
{
double d;
public:
A(doublei=0){d=i;}
void Print()
{
cout<<"Constructor"<<d<<endl;
cout<<"d="<<d<<endl;
}
~A(){cout<<"Destructor"<<d<<endl;}};
void main()
{
A a;
a=3.693;
a. Print();
}
/*
Destructor3.693
Constructor3.693
d=3.693
Destructor3.693
*/
结果分析:
本题重点在于赋值表达式语句a=3.693;,赋值表达式等号左边是类A的对象,右边是一个double类型的值。左右值虽然不同但是在C++编译器里面却是合法的。因为:类A中具有double类型的构造函数,这样的赋值表达式是将自动调用该参数的构造函数,首先将double类型的值3.693转化为A的临时对象,然后返回,这样左右均是A的对象便可进行赋值操作了。因此第一行的析构是给A转化的临时对象被释放。
临时对象什么时候被释放?
一般来说,C++ 中的临时变量在表达式结束之后 (full expression) 就被会销毁。但也有例外的时候,如果这个临时变量被用来初始化一个引用的话,那这个临时变量的生命周期就会被延长,直到引用被销毁,从而不会因此产生悬空 (dangling)的引用。
求最大公约数
程序简单,但是十分有用的求最大公约数,需要记住
#include <iostream>
using namespace std;
int max_num(int a,int b)
{
while(a != b)
{
while(a > b)
a = a-b;
while(b > a)
b = b-a;
}
return a;
}
int main()
{
int a,b;
cin >> a >> b;
cout << "最大公约数:" << max_num(a,b) << endl;
return 0;
}
求数组中不重复的元素
//输出数组中不重复的元素
#include <iostream>
using namespace std;
int main()
{
int a[10]={1,2,2,2,3,4,4,5,1,5};
int n=0;//用于记录不重复的元素
int i,j,k,c;
for(i=0;i<10-n;i++)
{
c=a[i];
for(j=i+1;j<10-n;j++)
{
if(a[j] == c)
{
for(k=j;k<10-n;k++)
{
a[k]=a[k+1];
}
n++;
j--;
}
}
}
cout<<n<<endl;
for(i=0;i<10-n;i++)
cout<<a[i]<<" ";
cout<<endl;
return 0;
}
//输出:
//5
//1 2 3 4 5
求质数
#include <iostream>
using namespace std;
const int MAX_LEN=20;
int p(int x,int a[])//将[2,x]内的所有质数输出到a数组中
{
int i,j,k=0;
for(i = 2;i <= x;i++)
{
for(j = 2;j <= i;j++)
{
if(!(i%j))
break;
}
if(i == j)
a[k++] = i;
}
return k;//返回的是质数数组的长度
}
int main()
{
int m[MAX_LEN];
int q=p(13,m);
cout<<"count:"<<q<<endl;
for(int i = 0;i<q;i++)
cout<<m[i]<<" ";
return 0;
}
/*
count:6
2 3 5 7 11 13
*/