31.&&和&、||和|有什么区别
运算符:&&是逻辑与, 而&是按位与;||是逻辑或,而|是按位或
30.short i = 0; i = i + 1L;这两句有错吗
i + 1L, short和long相加, 会发生long到short的转换,需要强制转换i + (short) 1L
29.谈谈你对编程规范的理解或认识
编程规范是编写高质量、可维护和可重用代码的重要保障。遵循编程规范能够提高代码的可读性、可维护性、可复用性和可靠性,促进团队协作和提高开发效率,是每位开发者都应该重视和遵守的基本原则。
28.简述队列和栈的异同
队列:先入先出,
栈:先入后出,
27.链表和数组有什么区别?
存储:数组是一整块连接的存储区域,链表则可能是多块存储区域;
访问:数组可以通过下标访问,而链表只能通过从头节点逐次移动指针进行访问;
链表便与插入和删除,并且不会出现越界的情况。
26.简述多态
对于一个类,如果存在虚函数,则编译器会为其生成一个虚表(虚函数表),还会插入一根虚指针,指向这个虚表,虚表中每一个包含指向对应虚函数的指针。当调用构造函数时候,编译器会自动将虚表指针与虚表关联,将vptr指向vtable。
主要用法是基类的指针指向子类的对象,在调用父类子类同名虚函数的时候,会调用派生类的虚函数,引用也是同理,调用的函数取决于指针指向对象的类型。
class Base
{
public:
void fun1()
{
fun2();
}
virtual void fun2() // 虚函数
{
cout << "Base::fun2()" << endl;
}
};
class Derived : public Base
{
public:
virtual void fun2() // 虚函数
{
cout << "Derived:fun2()" << endl;
}
};
int main()
{
Derived d;
Base * pBase = & d;
pBase->fun1();
return 0;
}
上述代码的输出是 “Derived:fun2()”,如不解,可以将基类中的func2()替换成this->func2();在Base::fun1()
成员函数体里执行this->fun2()
时,实际上指向的是派生类对象的fun2
成员函数。
在非构造函数,非析构函数的成员函数中调用「虚函数」,是多态!!!
「多态」的关键在于通过基类指针或引用调用一个虚函数时,编译时不能确定到底调用的是基类还是派生类的函数,运行时才能确定。
每一个有「虚函数」的类(或有虚函数的类的派生类)都有一个「虚函数表」,该类的任何对象中都放着虚函数表的指针。「虚函数表」中列出了该类的「虚函数」地址。
25.简述类成员函数的重写、重载和隐藏的区别
重写:显示声明为overrid,用于派生类重写基类的函数,参数列表,函数名一定相同,若有差异,编译会报错;重写后;重写的函数在基类中必须被virtual修饰。
重载:是一个类中重载函数,函数参数列表一定不同;
隐藏:隐藏和被隐藏的函数不在同一个类中,函数名相同;当参数列表不一样时,无论基类的函数是否被virtual修饰,基类的函数都会被隐藏,而不是重写。
重载:静态多态。重写:动态多态。
class Base { public: virtual void f0() { cout << "Base::f0()...." << endl; } }; class Derive : public Base{ public: void f0(int a) { cout << "Derive::f0(int a)..." << endl; } // void f0() override { // cout << "Derive::f0() override..." << endl; // } void f0() { cout << "Derive::f0() hide..." << endl; } }
24.访问基类的私有虚函数
#include <iostream>
#include <stdio.h>
using namespace std;
class person
{
public:
virtual void name()
{
cout<<"A::name"<<endl;
}
private:
virtual void sex()
{
cout<<"A::sex"<<endl;
}
};
class student : public person
{
public:
virtual void name()
{
cout<<"B::name"<<endl;
}
virtual void address()
{
cout<<"B::address"<<endl;
}
private:
virtual void ID()
{
cout<<"B::ID"<<endl;
}
};
typedef void (*Fun)(void);//定义一个函数指针
int main()
{
student stu;
for(long i = 0; i < 4; i++)
{
Fun p = (Fun)*((long*) * (long*)(&stu)+i);
p();
}
/**
*(long*)(&stu) 根据对象的虚函数表指针找到对应的虚函数表,指向虚函数表的首地址
*((long*) *(long*)(&stu)+i) 对虚函数表的函数进行偏移调用(虚函数表中的函数也是一个个的指针,指向代码区中存放函数的地方)
*/
return 0;
}
如图:
person类的虚函数表

student类的虚函数表

student派生类因为重写基类的name()函数,所以虚函数表指向重写后的自己的name()
24.用C++设计一个不能被继承的类
思考:如果直接把构造函数和析构函数设置为私有的话,虽然该类不能被继承,但同样不能实例化;所以使用友元,因为友元的关系是不能通过继承获得的,所以先让A的构造和拷贝成为私有,然后让B成为A的友元,并且让B虚继承A;此时如果C继承B的话,C是没有办法添加B的虚表中,所以无法通过B调用A的构造函数。
template<typename T>
class A {
public:
friend T;
private:
A() {};
virtual ~A(){};
};
class B : public virtual A<B> {
public:
B(){std::cout << "B\n";}
};
// Class C : public B {};
class TaskManager final {/*..*/} ;
class PrioritizedTaskManager: public TaskManager {
}; //compilation error: base class TaskManager is final
这里需要说明的是:我们设计的不能被继承的类B对基类A的继承必须是虚继承,这样一来C类继承B类时会去直接调用A的构造函数,而不是像普通继承那样,先调用B的构造函数再调用A的构造函数;
虚继承时子类的虚函数不再是添加到父类部分的虚表中,而在普通的继承中确实直接添加到父类的虚表中,这就意味着如果虚继承中子类父类都有各自的虚函数,在子类里面就会多出一个虚表指针,而普通的继承却不会这样。
23.谈谈堆拷贝构造函数和赋值运算符的认识
拷贝构造函数:通过一个已经存在的对象来生成一个新的对象实例;是一种构造函数,创建新的实例;拷贝构造函数必须以引用的方式传递参数。
赋值运算符:将一个已经存在的对象的值复制给另一个已经存在的实例;是一种运算;
class Person
{
public:
Person(){}
Person(const Person& p)
{
cout << "Copy Constructor" << endl;
}
Person& operator=(const Person& p)
{
cout << "Assign" << endl;
return *this;
}
private:
int age;
string name;
};
void f(Person p)
{
return;
}
Person f1()
{
Person p;
return p;
}
int main()
{
Person p;
Person p1 = p; // 1
Person p2;
p2 = p; // 2
f(p2); // 3
p2 = f1(); // 4
Person p3 = f1(); // 5
getchar();
return 0;
}
拷贝和复制运算符都涉及到深浅拷贝,主要是当类成员包含指针时,浅拷贝会使得两个指针指向同一块区域(指针只是简单的值拷贝),此时需要自行编写拷贝构造及复制运算符。
// 深拷贝
Person(const Person& p) {
this->age = new int;
std::memcpy(this->age, p.age, sizeof(int));
}
int *age;
22.C++的空类有哪些成员函数?
默认(缺省)构造函数,默认(缺省)析构函数,默认(缺省)拷贝构造,默认(缺省)赋值运算符,默认(缺省)取址运算符,默认(缺省)取址运算符const。
编译器只有在你使用到对应的函数时候,才会去定义。
21.面向对象的三大特征
封装:明确标识出允许外部使用的所有成员函数和数据;将客观事物抽象成类,每个类可以构建自己的数据和方法;通常类的成员是私有的,一部分方法是私有的,一部分方法是共有的;封装能够使得程序更模块化,更易读写,提升了代码的重用性;
继承+多态:
继承:继承基类的方法,并做出自己的改变或扩展(解决了代码重用问题);声明某个子类兼容于基类,使得外部调用这无需关注其差别;
多态:基于对象所属类的不同,外部对同一个方法的调用,实际的执行逻辑不同;多态依附于继承。
22.设置地址为 0x67a9 的整型变量的值为 0xaa66
*(int *)0x67a9 = 0xaa66; // store int value 10 at address 0x16
请注意,这假定地址0x67a9 是可写的 – 在大多数情况下,这会产生异常。
通常,您只会对没有操作系统且需要写入特定内存位置(例如寄存器、I/O 端口或特殊类型的内存(例如 NVRAM))的嵌入式代码等执行此类操作。
您可以像这样定义这些特殊地址:
volatile uint8_t * const REG_1 = (uint8_t *) 0x67a9;
然后在您的代码中,您可以像这样读取写入寄存器:
uint8_t reg1_val = *REG_1; // read value from register 1
*REG_2 = 0xff; // write 0xff to register 2