C++的一些语言特性
变量
静态局部变量是什么时候初始化的
静态局部变量(static local variable)是在函数第一次调用时初始化的。与普通的局部变量不同,静态局部变量在函数退出后不会被销毁,而是保留其值直到程序结束。这意味着下次调用该函数时,静态局部变量将继续保留其上次调用时的值。
1 | #include <stdio.h> |
无论是静态全局
左值和右值: 区别 引用 转化
左值通常都是存在的持久化对象,有地址,可以通过&来获取地址
右值通常是临时变量,不可以通过&来获取地址
1 | 常见右值: |
函数返回值当然可以是右值
所谓的左值引用底层都是指针实现的。
一个普通的左值引用可以绑定什么?只能绑定左值
1 | int &ref = x; |
但是常量左值引用可以绑定右值, 左值, 常量左值,(这是为什么参数都是常量左值引用的原因)
1 | const int &ref = x+y; |
std::move()函数实现原理
(这里也有点复杂)
为什么switch的case不建议定义变量
简单的说就是生命周期不符合,在switch这个花括号里面,变量的生命周期拉太长,其实只在一两个case里面使用,但是不应该拉到整个花括号里使用
指针
指针及其大小,用法
在64位系统里,一个指针类型占8个字节,也就是64位,专门用来存地址。
1 | char* p = nullptr; |
在C里面实际NULL是个0,而C++里面的nullptr不是。
指针还可以和整型or常量进行叠加运算,或者比较大小。
而且还有各种类型:
- 1 指向普通对象的指针
- 2 指向常量对象的指针 const修饰
- 3 指向函数的指针 typedef int (*funp)(int, int)
- 4 指向成员变量的指针
- 5 指向成员函数的指针 &A::add
- 6 指向静态成员函数的指针 A::get
由于成员函数的多态性,成员函数的地址到运行时才会知道,所以才需要这个&符号
还有对象特有的this指针
函数指针长啥样?以及它的用处
指针和引用的区别
引用是一个变量的别名,本质上是通过存储对象地址来实现的,只是编译器帮忙省略了*号
1 可变:指针指向可以改变,引用不可以
2 占内存:指针本身占内存,引用不占内存
3 为空:指针可以不初始化直接野指针,引用一开始必须初始化绑定对象
4 多级:指针多级可以,引用不可以
常量指针和指针常量
1 | const int * p; |
函数指针的定义
1 | int func1(){} |
第一种取法是把函数当成一个对象,取其地址,返回的是一个指针。
第二种取法是因为func1函数名字存放就是函数首地址,也是一个指针。
野指针和悬空指针详解
野指针就是哪些不确定的尚未初始化的指针,比如:void *p;
悬空指针就是原本指向一块内存,但是被free掉了,这个指针也没有被置空。
1 | //当然很多宏定义free直接把,置空也给搞定了。 |
C++的nullptr和NULL的比较
比如说函数重载的时候,其实传入的是一个NULL指针,但是本质底层NULL是0.这个时候到底是重载哪个函数好呢?而且nullptr是有单独的类型,typdef decltype(nullptr) nullptr_t, 方便类型检查。
stl
迭代器的作用
符合迭代器协议的就是ok。
- vector是随机访问迭代器,输入,输出,向前,双向,指针运算都可以
- List是双向迭代器
- Deque是随机迭代器
- Map是双向
- MultiMap也是双向
- Set也是双向
- Stack没有迭代器
- Queue没有迭代器
- Priority-queue 没有迭代器
迭代容器容易犯下的错:
在于迭代的过程中,碰到某些条件进而添加了某个元素,如果刚好容量不足,则会将旧的内存块复制或移动到新的内存块。然后释放旧的内存块,这个时候迭代器就失效了。
- 1 可以使用reserve()函数
- 2 或者索引遍历
- 3 或者是收集添加的元素,之后添加。
类型
类型转换
有一共4种类型转换:
static_cast:
静态转换,编译期间转换,失败的话会抛出一个编译错误。一般用于如下
数据类型强制转换
基本数据类型转换
基类和子类的指针转换
空指针转变为目标类型的指针
任何类型的表达式转化为void类型
cons_cast:
用于const和非const,volatile和非volatile,之间的转换。只能用取掉指针或引用的常量性。
reinterpret_cast:
指针和引用与整型的互相转换,执行的过程是逐个比特复制。
dynamic_cast:
这种和前3种的巨大区别在于它是运行时态的,主要用于子类和派生类的之间的转换,就是向下转化和向上转化。
p3 = dynamic_cast<Derive *>(p1);
类型萃取
对于普通类型,得到其类型很简单,但是使用模板编程的时候确定其类型就很难。传入的模板为不确定的。而且还要针对不同类型进行处理,比如一个自定义的拷贝函数
bool copy(T *dest, T *src);
如果传入的T是int类型,则只需要 *dest = *src
如果传入的T是char*类型,则需要更复杂的处理
这是类型萃取的来由
#include
template
void checkIntegral() {
if (std::is_integral
std::cout << “T is an integral type” << std::endl;
} else {
std::cout << “T is not an integral type” << std::endl;
}
}
int main() {
checkIntegral
checkIntegral
checkIntegral
return 0;
}
类与结构体
结构体相等判断方式及memcmp函数的使用? :
如果只是对比结构体之间的值,比如两个学生结构体,对比其学号和考试分数,最好使用友元=号运算符重载,因为结构体对齐补充的字节内容是随机垃圾值,memcmp对空间内字节进行逐个逐个比较,容易出错。
模版
模板及其实现
模板很多种黑魔法,但是当前就只介绍一些入门的模板编程
这是函数模板
template
T add_fun(const T & tmp1, const T & tmp2){
return tmp1 + tmp2;
}
类模板
template
class Complex
{
public:
//构造函数
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
//运算符重载
Complex
{
Complex
cout << tmp.a << “ “ << tmp.b << endl;
return tmp;
}
private:
T a;
T b;
};
Complex
Complex
Complex
可惜的是类不能从构造函数的入参来推导类型,所以最后还是得加个<>
变量模板:
在 C++14 以后,变量也可以参数化为特定的类型,这称为变量模板
template
constexpr T pi = T{3.141592653589793238462643383L};
std::cout << pi
std::cout << pi
这里有个问题一个类如果它有静态变量,这个时候如果该类有多个模板对象。
则它含有的静态成员是根据类型数目来的,如果有两个类型,则两个静态变量。
模板特化
很多都是全通用的模板,但是有些场景下,只允许几个类型进入函数体。
template <> //函数模板特化
bool compare(char *t1, char *t2)
{
cout << “特化版本:”;
return strcmp(t1, t2) == 0;
}
还有偏特化,也就是模板参数值确定一部分。
什么是可变参数模板
这个留下次吧