[深蓝学院] C++基础与深度解析 Week 2: Object and Basic Type

[深蓝学院] C++基础与深度解析 Week 2: Object and Basic Type

  • 1. 初始化和赋值
  • 2. 类型
    • 为什么需要类型?
    • 字符串结尾
    • 变量的声明与定义
    • 变量的初始化
    • 变量的(隐式)类型转换
  • 3. 指针
  • 4. 引用
  • 5. const 常量类型与常量表达式
  • 6. 类型别名 类型自动推导
    • 类型别名 (typedef / using a = b)
    • 类型自动推导 (auto)
  • 7. 域(scope)与对象的生命周期
    • 域(scope)
    • 生命周期

本文是深蓝学院C++课程的课堂笔记。

1. 初始化和赋值

初始化基本操作:

  • 在内存中开辟空间,保存相应的数值
  • 在编译器中构造符号表,将标识符与相关内存空间关联起来

2. 类型

为什么需要类型?

  1. 类型其实是一种编译器概念,实际可执行文件中不存在类型
  2. C++是强类型语言,Python是弱类型语言
  3. 引入类型是为了更好地描述程序,防止误用。
  4. 类型描述的内容有:
    • 存储所需的尺寸 (sizeof)
    • 取值空间 (std::numeric_limits,超过范围可能溢出)
    • 对齐信息 (alignof)
    Struct Str
    {
    	// 8000~8001
    	char b;
    	// 对齐信息:8002这个位置不能对齐int类型(4 byte),所以从8004开始
    	// 8004~8007
    	int x;
    }
    
    • 可执行的操作

字符串结尾

“Hello” -> char[6]

因为字符串最后一个字符是“\0”

变量的声明与定义

  • int x: 定义一个变量x
  • extern int x: 声明一个外部变量x (千万不要再初始化了哟)

变量的初始化

缺省值

  • 全局变量会在程序初始化的时候,自动赋值
    • eg., int global_x 会自动赋值0
    • 但如果是在main()函数里面的本地变量,那么程序为了节省运算,只是开辟一段内存空间,但是不会缺省赋值,以前那个内存空间里面是什么就是什么,所以可能会出现奇奇怪怪的数。

拷贝初始化

直接初始化

变量的(隐式)类型转换

3. 指针

  • 指针:
    地址 0x5678 -> 地址 0x1234
    内容 0x1234 -> 内容4

  • 指针特点:
    可以指向不同类型
    尺寸相同

int* p1 =  &x;
char* p2 = &y;
sizeof(p1) = 8
sizeof(p2) = 8
  • 为什么尺寸相同呢?

    • 通常电脑都是64位机,也就是内存地址可以用64 bit来表示,那就是8 byte
      那么指针的内容,就都是8字节,即size = 8 byte
    • 如果是32位机,就是32 bit / 8 = 4 byte
  • 为什么指针需要指明目标的类型呢?

    • 因为在知道目标地址之后,我们要知道取目标位置后面几个byte的内容,作为返回值。
    • 比如,如果我们目标地址是0x1000,且目标已知是一个int类型,那么我们就取0x1000~0x1003
  • 指针有缺省值吗?

    有2种缺省情况:

    1. 指针是局部变量,那么指向的地址是随机的。
    2. 指针是全局变量,那么指向的地址是0x0000,但是系统是不允许访问0x0000的,于是系统就会报错。
  • 不知道初始值是多少的指针,怎么初始化呢?
    赋地址0x0000,简单来说就是: int* p = 0;
    这样肯定会报错,因为系统不允许访问0x0000地址。但只要我们后面赋值了,代码run了,就说明指针很鲁棒。

  • 上面的初始化int* p = 0其实是有潜在风险的:
    如果系统函数能够重载,那么0这个type它会默认选择最match的一种重载函数:

void fun(int)
{}

void fun(int *)
{}

fun(0);
int* p = 0;
fun(p)
  • 解决方案: C++ 11中引入了nullptr,指向0x0000地址
int* p = nullptr;
fun(p);

if(p) // 也即,if(p != nullptr)
{}
else
{}
  • 指针的解引用,增加,减少
    指针p和指针p+1的地址相差为这个指针类型的size,而不是地址+1。

  • void*指针

    • 丢掉了对象的尺寸信息,可以保存任意地址
    • 支持判等操作
  • int **p指针的指针
    [深蓝学院] C++基础与深度解析 Week 2: Object and Basic Type_第1张图片

  • 指针 vs 对象

    • 优点:
      • 指针的复制成本很低,读写成本高
        例子,函数的形参,如果是一个很大的矩阵赋参,那么函数就会把矩阵拷贝到形参里面去,超级占据空间。
      • 指针形参,可以修改指针里面的数据内容。
    • 缺点:
      • 可以为
      • 地址信息可能非法
    • 解决方案:引用

4. 引用

  • 引用和指针都不能赋字面值(Literal), eg. int& age = 1或者int& age = &1都是不对滴~
  • 引用 vs 指针
    • 指针可以改变所指向的内容
    • 引用相当于是所指向的内容的别名,是不能够更换另一个绑定对象的。
    • 引用的赋值操作会直接改变对象内容
    • 指针的赋值操作有2种:
      • 一种是给指针的内容赋值
      • 一种是给指针所指向的对象的内容赋值(需要解引用)
    • 指针的指针是可以的, int** pp = p (正确)
    • 引用的引用是不可以的, int& & ref2 = ref1 (错误)
    • 引用是一个编译器概念,它的底层汇编实现其实和指针是一样的。

5. const 常量类型与常量表达式

  • const是编译器概念,编译器用它来:

    • 防止非法操作(比如不小心==写成了=)
    • 优化程序逻辑
      eg. 如果已知变量是const,那么编译出来的代码可以直接把5赋值给y
      const int x =4;
      // ....
      int y = x + 1;
      
  • const 指针分为两种类型:

    • 指针的内容(所指向的地址)不能改变:
      int* const ptr = &x;
      ptr = &y; (错误,因为ptr里面存储的内容是不能改变的,只可以指向&x)
    • 指针所指向的地址的内容不能改变:
      const int* ptr = &x;
      *ptr = 3;(错误,因为ptr指向的地址的内容是不能改变的,但可以改变ptr指向其他地址)
  • const 引用:

    • 可读,不可写
    • 引用本来就只能指向一个地址,const引用连指向地址的内容都不能改了
    • 用途: 函数的形参
  • const 引用 vs const指针:

    • const 指针作为函数形参传入的话,可能传入无效地址,eg. fun(nullptr);
    • 所以传指针需要在函数内部作一个指针所指向 的内容的判断
    • const 引用就更加适合形参啦!主要用于很大的Struct结构体的传入。
    • const 引用还特别允许const int& param = 3,使用字面值来赋值。这是C++标准为了函数传参能够直接用字面值进行赋值,而专门网开一面。而如果不是const 引用,是不可以int& param = 3,这样没有const是不能用字面值给引用的。
  • constexpr 常量表达式 (c++ 11)

    • 语义上:
      • constexpr:告诉编译器我可以是编译期间可知的,尽情的优化我吧。
      • const:告诉程序员没人动得了我,放心的把我传出去;或者放心的把变量交给我,我啥也不动就瞅瞅。
    • 意义:constexpr不是一个类型,只是一个修饰

6. 类型别名 类型自动推导

类型别名 (typedef / using a = b)

typedef int MyInt;using MyInt = int;

类型自动推导 (auto)

int x = 3.5 + 18l;
-->
auto x = 3.5 + 18l;

问题:auto可以推测类型,但是有可能类型会产生退化

int n = 10;
int& ref = n;
auto ref_auto = ref;
//此时ref_auto的类型是int,发生了类型退化!

解决:

//推导出引用类型,并且避免类型退化
auto& ref_auto = ref;

decltype(exp): 返回exp的类型,左值加引用
decltype(val): 返回val的类型
decltype(auto): (c++14开始支持)既不会发生类型退化,也不用写一大堆(直接用auto代替)
concept auto: (c++20开始支持)可以限制auto推导类型的范围,比如std::integral auto x = 3.5;就会报错,因为限制在了std::integral类型范围内,而3.5是一个double

//例子
int x = 3;
int* ptr = &x;

decltype(x);    // val --> int
decltype(*ptr); // exp --> int& (*ptr是一个expression,而不是value啦,所以左值加引用)

7. 域(scope)与对象的生命周期

域(scope)

  • 全局域 (Global Scope)
  • 块域 (Block Scope)
  • 类域 (Class Scope)
  • 命名域 (Namespace Scope)

生命周期

  • 对象的生命周期起始于被初始化的时刻,终止于被销毁的时刻
  • 通常来说
    1. 全局对象的生命周期是整个程序的运行期间
    2. 局部对象生命周期起源于对象的初始化位置,终止于所在域被执行完成

你可能感兴趣的:(c++,c++,指针)