随笔--类和对象初阶问题总结(面试)
Posted 蚍蜉撼树谈何易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了随笔--类和对象初阶问题总结(面试)相关的知识,希望对你有一定的参考价值。
类和对象初阶必会知识点
面向对象与面向过程的区别
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成,面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向对象的三大特性
三大特性:继承、封装、多态
封装
1.把变量(属性)和函数(操作)合成一个整体,封装在一个类中
2.对变量和函数进行访问控制。
目的:封装的目的是为了保证变量的安全性,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员
继承
继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
很通俗的例子就是你先抽象出了一个person类的对象
它里面有
class person
{
public:
person()
{
}
private:
string name;
int age;
char gender[2];
}
然后呢,根据不同的人再去创建不同的类来继承这个person类,因为person类提供的属性都是每个人所具有的特征,此时再去创建类的话就可以相对于person类做一个简单的扩充。废话这么多,本质目的就是为了提高代码的复用率和可扩展性。
多态
概念:
(1)根据实际的对象类型决定函数调用的具体目标;
(2)同样的调用语句在实际运行时有多种不同的表现形态体现,这就是多态,同一语句具有多种形态。
例子:模板的提出
多态的目的是实现了动态联编,使程序运行效率更高,更容易维护和操作。
const关键字
const修饰普通变量
1.在C语言中,定义在全局变量的数据不可以更改,但定义在栈上(通俗的说就是在函数体内定义的const变量)是可以修改的。
2.在C++中,全局变量的数据不可以修改,但位于函数体内部的形式上可以修改,但是本质上你也不能对它的值产生影响。(语法对,但是没屁用)。
原因是因为常量折叠
通常C++编译器并不为const创建存储空间,相反它把这个定义保存在它的符号表里。 相当于在编译的时候已经将b替换为20了,所以此时就算修改也对它毫无影响。 取决于const变量为内部连接的情况。如果为外部连接或者对它执行取地址操作、或者是加了volatile关键字、亦或将const变量作为左值,则此时编译器不会进行常量折叠,因为此时需要的是一个真正的常量地址。
具体的推理过程放在了初识C++这篇博客里。
const修饰类成员和类成员函数
区分初始化与赋值
区分一下构造函数的初始化与赋值区别
初始化:只能初始化一次
赋值:可以进行多次的赋值
比如 const int a=10;//这就是初始化,因为他自创建起到它的生命周期结束,只可以使用这个值。
而int a=10; a=20;//这就是赋值,只要它定义了,并且它里面的内存没有被回收,就可以对它多次执行赋值的操作。
首先我们看赋值操作:
我们还是看date这个例子:
初始化
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式
const修饰成员变量
定义的两种方式:
const修饰成员函数
方式1:修饰返回值
class date
{
public:
date(int _year, int _month, int _day)
{
year = _year;
month = _month;
day = _day;
}
~date()
{
}
const date* operator&()
{
cout << this << endl;
return this;
}
private:
int year;
int month;
int day;
};
int main()
{
date s(2021, 6, 22);
const date* p = &s;
cout << p << endl;
}
注意:函数返回值为const类型,则接收它的变量应该也是const类型。此举是为了修饰返回值,让接收到它的人不能随意修改,但是自己在调用时可以在内部进行修改。
const修饰this指针
class date
{
public:
date(int _year, int _month, int _day)
{
year = _year;
month = _month;
day = _day;
}
~date()
{
}
const date* operator&()const
{
//this->day = 100;err
//this->year = 2000;err
cout << this << endl;
return this;
}
private:
int year;
int month;
int day;
};
int main()
{
date s(2021, 6, 22);
const date* p = &s;
cout << p << endl;
}
浅析extern “C”
为什么要提出extern “C”
extern "C"的主要作用就是为了实现c++代码能够调用其他c语言代码。加上extern "C"后,这部分代码编译器按c语言的方式进行编译和链接,而不是按c++的方式。
本质原因:两个函数取别名的方式不同,此时按照C++的命名规则命名出来的名字去找c语言函数,是找不到的,首先先看下它们在底层是怎么命名的。
所有实验效果基于Linux下,不同编译器有不同的结果
C语言命名
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main(void)
{
system("pause");
return 0;
}
C++命名
#include<iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int add(int a)
{
return a+10;
}
int main(void)
{
return 0;
}
怎么使用 extern “C”
test.h文件
#pragma once
#include<stdio.h>
//告诉C++编译器,找我这个函数,需要按照C语言的方式去寻找
#ifdef __cplusplus
extern "C"
{
#endif;
int add(int a, int b);
#ifdef __cplusplus
}
#endif
test.c文件
#pragma once
#include "test.h"
int add(int a, int b)
{
return a + b;
}
main函数文件
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
#include"test.h"
int main()
{
cout << add(10, 20) << endl;
system("pause");
return 0;
}
引用与指针
引用的本质
本质:给变量取别名
int &a=b;
底层会将其变为 int *const a=&b;这也说明了为什么引用必须要初始化
a=100;//此时编译器发现a为b的引用,底层会这样做 *a=100;
引用与指针的区别
本质:引用是别名,指针是地址,
①从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。这句话可以理解为:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
②从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。
注:标准没有规定引用要不要占用内存,也没有规定引用具体要怎么实现,
③ 从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。 指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。
④ 不存在指向空值的引用这个事实,意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
⑤理论上,对于指针的级数没有限制,但是引用只能是一级。如下:
int** p1; // 合法。指向指针的指针
int*& p2; // 合法。指向指针的引用
int&* p3; // 非法。指向引用的指针是非法的
int&& p4; // 非法。指向引用的引用是非法的
注意上述读法是从左到右。
基于静态成员函数/变量实现一个单例模式
单例模式
单例模式的定义:一个类只能初始化出一个对象
单例模式的设计思路:
1.把无参构造函数与拷贝构造函数私有化
2.定义一个类内的静态成员指针
3.在类外初始化时,new一个对象
4.把指针的权限设置为私有,然后提供一个静态成员函数让外面获取这个指针
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class student
{
private:
//1.将无参构造函数与拷贝构造函数私有化
student()
{
}
student(const student& m1)
{
}
//4.把指针的权限设置为私有,然后提供一个静态成员函数让外面获取这个指针
public:
static student* get()
{
return p;
}
private:
//2.定义一个静态的类内成员指针
static student* p;
};
//3.在类外进行定义
student* student::p = new student;
void test01()
{
student* m = student::get();//提供一个接口函数,避免用户对类内成员指针的错误修改
student* m1 = student::get();
cout << "*m1 " << m1<< endl;
cout << "*m " << m << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
单例模式设计实例
目的:获取打印次数
//需求,获取打印机的打印次数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class print
{
private:
//1.将无参构造函数与拷贝构造函数私有化
print()
{
mount = 0;
}
print(const print& m1)
{
}
public:
//使用静态成员函数返回指针
static print* get()
{
return p;
}
void dayin(string name)
{
cout << name << "打印" << endl;
mount++;
}
int return_num()
{
return mount;
}
private:
static print* p;//2声明一个静态成员指针
int mount;
};
print* print::p = new print;//3声明一个静态成员指针
void test()
{
print *m1 = print::get();
m1->dayin("生产部");
print* m2 = print::get();
m2->dayin("研发部");
print* m3 = print::get();
m3->dayin("市场部");
cout << "打印次数" << m3->return_num() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
单例模式使用场景
任务管理器,一个电脑无论怎么样,都只能开一个任务管理器。
volatile 关键字
应用:在嵌入式中使用较多
作用:告诉编译器,该变量可能会发生改变,每次使用该值的时候必须去内存中去取。而不是在寄存器
就这么理解:假如在嵌入式中发生了一次中断,此时对寄存器的值做出了修改,此时你再去取数据的话,此时取到的值通常是我们俗称的 “脏数据”。
const结合volatile
const volatile int a=0;//告诉编译器,程序是不能对它做出修改的,只能是cpu内部对它改写。
const volatile表示该变量既不能被修改,又不能被优化到寄存器,即又是可能会被其他编译器不知道的方式修改的。比如一个实时时钟,我们不希望被程序做修改,所以要声明为const,但其他的线程、中断等(可能来自于库)又要修改此时钟的值,编译器不能把它作为const常量优化到寄存器,所以又要声明为volatile。
explicit关键字
explicit关键字 可以避免编译器对需要优化的语句进行优化
这个之前总结过,大家可以去我这篇博客中看,在最后一项。
explicit关键字
以上是关于随笔--类和对象初阶问题总结(面试)的主要内容,如果未能解决你的问题,请参考以下文章