前言
学习了C++语法这么久了,我其实觉得,我们学习一门语言应该更加注重使用性,对于语法的细节可以通过具体的项目去重新造轮子的时候再去抠细节,也就是说你得学会先走,在去想我们如何走的,身体的哪些肌肉在发力,这些对于我们目前可以不用在意细节,结合实际来去记忆,这个才是高效可取的,今天的前言唠叨就到这人呀!如果你有兴趣,就接下来看下面的几个问题。
1.C++是如何实现函数重载的呢?
为什么要有函数重载呢?我们发现在C语言中我们不同的参数就会重新定义一个一模一样的函数来去调用这个接口,这对我们来说违背设计原则,代码的复用低,整个项目就会看着非常庞大。C++在这个方面提出来第一个改进。
回答:C++在编译阶段 ,允许同一作用域下的功能相近的不同形参的函数定义为同一函数名,在底层汇编根据不同参数形成不同的函数名放入符号表中,在后续代码生成可以根据函数名的不同来实现对不同参数的同名函数调用。
除此之外,使用extern关键字可以使用C语言的风格来进行编译。就不支持函数重载。
2.指针和引用的区别?
为什么要有引用呀?打个比方我们通常对于其他的经常使用的数据,我们每一次开一个空间,再把他的地址存进去,在解引用,会不会显示的很繁琐,也很浪费CPU资源,本质是是没有问题的,可是我们直接操作变量里面的数据是不是更加便利一点,引用的概念就被提出来了。
回答:①指针是地址,引用是给变量起别名。指向的是变量里面的数值,可以直接操作数据,不用接引用,我们对指针++是地址++,对引用++是里面的数值++,考虑一些特殊情况,指针可以为空或者野指针,因为新的地址变量可以为空,但是引用不能为空,他必须指向一个对象。
②指针和引用可以作为形参,指针可以修改指向的变量,所以可以通过传递大型对象的指针,来避免开辟空间,指针可以为悬空只要不接引用就不会报错,但是引用直接传的就是变量,我们操作变量就能操作数据,更加简洁。
③因为指向的是底层变量的数据,所以他的底层实现还是用指针的思想实现的。除此之外,指针和引用做返回值代表的意思也是不一样的。指针可以返回动态开辟的空间,引用可以直接返回可以操作的对象,比如类成员或者全局的变量 。
④指针的具体场景:内存分配,数组操作,链表,图等,引用的场景参数避免复杂开销,返回值。
3.nullptr和NULL的区别?
为什么要提出nullptr呢?其实就是对0这个值的再次细粒度划分,避免二义性,到底是空地址还是数值0。
回答:nullptr是一个类型安全的空指针常量。可以调用nullptr_t 得构造方法去隐式的转换不同的类型,而NULL确实需要自己去进行强制类型转换,并不安全,压根就不知道是整数类型还是之指针类型。
4.如何替换宏?
我们知道程序进行预处理的第一步就是处理#子开头的程序,比如程序替换,头文件展开等等,但是宏不进行类型安全检查,然后运算过程时十分容易出现问题。这个时候就得想到替换方案。
宏的优点:代码的复用性高,提高性能。
宏的缺点:会因为运算符的优先级导致运算出错,没有类型检查不安全,不方便调试。
回答: const 和enum 来替换宏成员,内连函数可以替换宏函数。
5.类与对象?
5.1理解面向你对象和面向过程?
面相对象更加关注的是类与类对象之间的交互。面向过程更关注具体过程的实现。在解决实际问题上,面相对象更加完整,每个对象都有自己的属性和方法,而且更容易扩展。耦合度关联度低,比较松散,面向过程是一些结构体和函数,比较紧凑。
面相对象的特性:封装、继承、多态。
封装:
第一层理解用C语言实现一个栈,定义结构体,变量和函数,把结构体当做参数传进去。
第二层理解:为了更规范的去使用数据。避免一会访问数据一会访问方法。C++把想访问的定义为公有,不想 访问的去定义为私有。同时只能通过函数去调用数据。这样的管理更符合高内聚低耦合的设计原则,也更方便管理数据。
继承:
类层次的复用,给类之间加上关系,更加简化代码逻辑。
多态:
静态的多态和动态的多态,静态的多态就是函数重载或者cout自动识别类型,以及函数模版。动态的多态是运行时多态,对象里有虚表指针,指向虚表,虚表里存放虚函数,子类对象可以重写父类对象的虚函数,指向父类调父类,指向子类调子类。
5.2 默认成员函数
简单理解为初始化,清理,括号(),等号=,移动构造,移动复制, 取地址,const &取地址。实现一个简易的string。见下面代码
#include <iostream>
#include <cstring>
#include <utility> // for std::swap
using namespace std;
class String {
public:
// 默认构造函数
String() : data_(new char[1]) {
*data_ = '\0';
}
// 构造函数
String(const char* str) : data_(new char[strlen(str) + 1]) {
strcpy(data_,str);
}
// 拷贝构造函数
String(const String& rhs) : data_(new char[rhs.size() + 1]) {
strcpy(data_, rhs.c_str());
}
// 移动构造函数
String(String&& rhs) noexcept : data_(rhs.data_) {
rhs.data_ = nullptr;
}
// 拷贝赋值运算符
String& operator=(const String& rhs) {
if (this != &rhs) {
String temp(rhs);
swap(temp);
}
return *this;
}
// 移动赋值运算符
String& operator=(String&& rhs) noexcept {
if (this != &rhs) {
delete[] data_;
data_ = rhs.data_;
rhs.data_ = nullptr;
}
return *this;
}
// 析构函数
~String() {
delete[] data_;
}
// 获取字符串大小
size_t size() const {
return strlen(data_);
}
// 获取原始C字符串
const char* c_str() const {
return data_;
}
// 交换函数
void swap(String& rhs) {
std::swap(data_, rhs.data_);
}
// 下标运算符
char& operator[](size_t index) {
return data_[index];
}
const char& operator[](size_t index) const {
return data_[index];
}
// 比较运算符==
bool operator==(const String& rhs) const {
return strcmp(data_, rhs.data_) == 0;
}
// 比较运算符<
bool operator<(const String& rhs) const {
return strcmp(data_, rhs.data_) < 0;
}
private:
char* data_;
};
int main() {
String s1("Hello");
String s2("World");
String s3 = s1;
s3 = std::move(s2); // 使用移动赋值运算符
std::cout << "s1: " << s1.c_str() << std::endl;
//std::cout << "s2: " << s2.c_str() << std::endl;
std::cout << "s3: " << s3.c_str() << std::endl;
return 0;
}
5.3其他成员
static修饰类成员
const修饰成员函数
this指针可以为空指针,不解引用就不会崩
this指针在栈区,因为是形参,VS为了提高效率,放入寄存器中,他指向的对象在堆栈或者全局,但他本身计算临时变量,通常在栈区。
友元,友元函数可以直接访问全部的成员。加一个friend关键字,一般用于重写<<和>>
内部类,内部类就是外部类的友元,反过来不是,内部类可以通过外部类的对象参数进行访问外部类,枚举类型和static可以直接访问。