C++的一些语言特性

变量

静态局部变量是什么时候初始化的

静态局部变量(static local variable)是在函数第一次调用时初始化的。与普通的局部变量不同,静态局部变量在函数退出后不会被销毁,而是保留其值直到程序结束。这意味着下次调用该函数时,静态局部变量将继续保留其上次调用时的值。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
void counter() {
    static int count = 0; // 静态局部变量
    count++;
    printf("Count: %d\n", count);
}
int main() {
    counter(); // 输出:Count: 1
    counter(); // 输出:Count: 2
    counter(); // 输出:Count: 3
    return 0;
}

无论是静态全局

左值和右值: 区别 引用 转化

左值通常都是存在的持久化对象,有地址,可以通过&来获取地址

右值通常是临时变量,不可以通过&来获取地址

1
2
3
常见右值:
int x = 3 + 1; (右值)
int y = a + b; (右值)

函数返回值当然可以是右值

所谓的左值引用底层都是指针实现的。

一个普通的左值引用可以绑定什么?只能绑定左值

1
2
3
int &ref = x;
int &ref = x+y; (因为右值无法取地址,所以会失败)
int &ref = 10; (因为右值无法取地址,所以会失败)

但是常量左值引用可以绑定右值, 左值, 常量左值,(这是为什么参数都是常量左值引用的原因)

1
2
3
4
const int &ref = x+y;
const int &ref = 30;
const int &ref = x;
const int &ref = y;

std::move()函数实现原理

(这里也有点复杂)

为什么switch的case不建议定义变量

简单的说就是生命周期不符合,在switch这个花括号里面,变量的生命周期拉太长,其实只在一两个case里面使用,但是不应该拉到整个花括号里使用

指针

指针及其大小,用法

在64位系统里,一个指针类型占8个字节,也就是64位,专门用来存地址。

1
2
char* p = nullptr;
sizeof(p); //会打印8

在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
2
3
4
5
6
7
8
9
10
11
12
13
14
const int * p; 
int const * p;
// 都一样的只要const不是临近p,都说明指向常量的指针(常量指针)

int * const p
// p 指针常量,指向不可以变化

int const * const p; 这是一个指向常量的指针常量。
// 双重常量

int ** const p; 一个二级指针常量
int * const * p; 指向常量的指针常量
int const **p; 一个二级常量指针
int * const * const p; 首先是一个指针常量,同时它指向一个指针常量

函数指针的定义

1
2
3
4
int func1(){}
int func2(){}
fun=&func1;
fun=func1;

第一种取法是把函数当成一个对象,取其地址,返回的是一个指针。
第二种取法是因为func1函数名字存放就是函数首地址,也是一个指针。

野指针和悬空指针详解

野指针就是哪些不确定的尚未初始化的指针,比如:void *p;

悬空指针就是原本指向一块内存,但是被free掉了,这个指针也没有被置空。

1
2
3
4
5
6
7
//当然很多宏定义free直接把,置空也给搞定了。
#define FREE(p) do { \
if(ptr) { \
free(ptr); \
ptr=NULL; \
} \
} while(0) \

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::value) {

        std::cout << “T is an integral type” << std::endl;

    } else {

        std::cout << “T is not an integral type” << std::endl;

    }

}

int main() {

    checkIntegral();        // 输出: T is an integral type

    checkIntegral();     // 输出: T is not an integral type

    checkIntegral();       // 输出: T is an integral type

    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 operator+(Complex &c)

    {

        Complex tmp(this->a + c.a, this->b + c.b);

        cout << tmp.a << “ “ << tmp.b << endl;

        return tmp;

    }

private:

    T a;

    T b;

};

 Complex a(10, 20);

 Complex b(20, 30);

 Complex c = a + b;

可惜的是类不能从构造函数的入参来推导类型,所以最后还是得加个<>

变量模板:

在 C++14 以后,变量也可以参数化为特定的类型,这称为变量模板

template 

constexpr T pi = T{3.141592653589793238462643383L};

std::cout << pi << ‘\n’;

std::cout << pi << ‘\n’;

这里有个问题一个类如果它有静态变量,这个时候如果该类有多个模板对象。

则它含有的静态成员是根据类型数目来的,如果有两个类型,则两个静态变量。

模板特化

很多都是全通用的模板,但是有些场景下,只允许几个类型进入函数体。

template <> //函数模板特化

bool compare(char *t1, char *t2)

{

    cout << “特化版本:”;

    return strcmp(t1, t2) == 0;

}

还有偏特化,也就是模板参数值确定一部分。

什么是可变参数模板

这个留下次吧

模板的全特化和特化有什么区别

模板编程在性能上有什么优势?