C++知识整理系列(一)指针和动态空间

目录

  • 1.指针和引用
  • 2. 函数指针和指针函数
    • 指针函数
    • 函数指针
  • 3. C/C++ volatile关键字
  • 4. 易混淆的指针概念
  • 5. 虚函数表和虚函数指针
  • 6. new / delete 和 malloc / free 的区别与联系
    • 相同点
    • 区别
    • new和delete的实现机制
    • delete 和 delete []
  • 7. C++模板
    • 强类型和弱类型
    • 模板的提出
    • 问题:C++模板是在编译阶段还是运行阶段?

1.指针和引用

  • 指针是一个变量,变量存储(的内容)的是一个地址;而引用是某个变量的别名,实质上和原变量是同一个东西。指针和引用存放的都是被指向对象的地址,引用必须在定义的同时进行初始化。
  • 引用和指针,在内存中都占用4个字节(32bit)的存储空间。在底层,引用变量由指针按照指针常量的方式实现。引用变量的地址由编译器支配,程序员无法直接对它操作。
  • 指针在初始化后可以任意改变其指向,而引用在初始化后不可在改变其指向。
  • sizeof引用得到的是引用所指向变量的大小;sizeof指针得到的是指针本身的大小
  • 指针可以多级(int** p),引用只有一级。ps:注意区别C++11的右值引用。
  • 指针变量(p),&p得到的是指针变量自身的地址;而引用&则是得到的是引用指向变量的地址。
  • 指针(p)的自增(++)是自增内存空间,根据指向对象的类型修改p的大小;而引用则是增加被引用对象的值。

2. 函数指针和指针函数

函数指针是指针,指向一个函数的入口地址;指针函数是函数,返回值是指针。

指针函数

int* funTest(int a[])
{
    return a;
}

int main()
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int* p = funTest(arr);
    for (int i = 0; i < 5; i++)
    {
        cout << p[i] << endl;
    }
    
}

定义了一个返回值为int*,输入参数为a[]的指针函数,调用该函数返回数组a的首地址

函数指针

int add(int x, int y)//加
{
    return x + y;
}

int sub(int x, int y)//减
{
    return x - y;
}

typedef int (*pFun)(int, int);
int main()
{
    int (*fun)(int, int);
    fun = add;//指向add函数,注意:fun = add 和 fun = &add一致,都是指向了函数的入口地址
    cout << fun(2, 1) << endl;
    fun = sub;
    cout << fun(2, 1) << endl;
    pFun fp;
    fp = add;
    cout << fp(2, 3) << endl;

    return 0;
}

函数指针,本质上是指针,只不过用来指向函数的地址,可以替代函数,并可切换指向,指向不同的函数。

注意,定义函数指针时,因为*的优先级较小,所以需加括号,如:void (*pfun)()就定义了一个返回值为空、参数列表为空的函数指针,可以指向相同格式的函数。

函数指针可以有两种定义方式,两者用法一致:

//1.
int (*fun)(int, int);
//2.
typedef int (*pFun)(int, int);
pFun fun;

3. C/C++ volatile关键字

从英文翻译中文意思就是:易变的、不稳定的。所以,一个变量在加了volatile修饰后,要求编译器禁止对volatile变量进行优化,在每个变量赋值时需显式地从寄存器拷贝。在嵌入式编程中比较多用到,因为可能发生中断修改了寄存器的值或一段汇编代码修改了值而不让编译器知道,这时候就要加上volatile。

注意,在C++中volatile与并发编程有关是个错误!原因可能是java中的volatile和C++是不一样的。

参考这篇博客:C++volatile的误会

4. 易混淆的指针概念

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]是指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
  • int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
  • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
  • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

5. 虚函数表和虚函数指针

  • 当一个类在实现的时候,如果存在一个或以上的虚函数时,那么这个类便会包含一张虚函数表。而当一个子类继承并重写了基类的虚函数时,它也会有自己的一张虚函数表。
  • 虚函数表存储虚函数的地址,即虚函数表的元素是指向类成员函数的指针。简单来说,虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。
  • 而类中虚函数的个数在编译时期可以确定,即虚函数表的大小可以确定,即大小是在编译时期确定的。
  • 虚函数表每个类只有一个,只属于类而不属于对象;而虚函数指针每个对象都有且只有一个,*__vptr。虚函数指针自动地指向虚表中对应的位置,找到相应的成员函数。

6. new / delete 和 malloc / free 的区别与联系

相同点

new / malloc都是动态内存的开辟,在堆区开启空间;而delete / free都是动态内存的释放。

区别

  • new / delete是C++的关键字,是C++运算符;而后者C/C++标准库函数,头文件#include
  • new是类型安全的;malloc不是类型安全的,malloc的返回值是void*,这时候我们用一种类型指针接收,但是malloc开辟的空间可能大于设定的类型,理论上会出错但是使用malloc编译器不会出错,而使用new编译器会报错。
  • new会调用构造函数,delete会调用析构函数;malloc和free并没有此功能。

new和delete的实现机制

  • new的实现过程是:首先调用名为operator new的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象;接下来运行该类型的一个构造函数,用指定初始化构造对象;最后返回指向新分配并构造后的的对象的指针。
  • delete的实现过程:对指针指向的对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。

delete 和 delete []

  • delete 释放new分配的单个对象指针指向的内存
  • delete[] 释放new分配的对象数组指针指向的内存

7. C++模板

强类型和弱类型

像C/C++、java、C#等语言都是强类型语言

  • 强类型语言在定义变量时需要显示地指明数据类型。
  • 一旦为变量指明某种数据类型,该变量以后就不能赋予其他类型的数据,除非经过强制类型转换或隐式类型转换。

而JavaScript、Python、PHP、Ruby等语言是弱类型语言,在定义变量时不用显示地指明数据类型,解释器会根据赋给变量的数据自动推导出数据类型。

如下,是JavaScript 中使用变量,var 是 JavaScript 中的一个关键字,表示定义一个新的变量,而不是数据类型。

var a = 10;
a = 13.5;

对比

  • 强类型语言较为严谨,在编译时就能发现很多错误,适合开发大型的、系统级的、工业级的项目;
  • 而弱类型语言较为灵活,编码效率高,部署容易,学习成本低,在 Web 开发中大显身手。

模板的提出

  • C++模板模板的提出,正是为了弥补强类型语言"不够灵活"的缺点。模板所支持的类型是宽泛的,没有限制的,我们可以使用任意类型来替换,这种编程方式称为泛型编程(Generic Programming)

  • C++的STL标准模板库,几乎整个库都是通过模板来完成的。比如各种数据结构:线性表、链表、树、图都定义好一个模板类,程序员只要调用库就可,不用重复造轮子。

问题:C++模板是在编译阶段还是运行阶段?

模板:模板是C++中泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者说公司。

模板在编译阶段就能知道其类型了:

template <typename T>
int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    else if (v1 > v2) return 1;
    else return 0;
}

当我们调用一个函数模板时,cout << compare(1, 2) << endl,编译器通过函数实参推断模板实参,并绑定到模板参数T。如上例子,编译器推断出类型为int,并绑定到模板参数T。

编译器用推断出的模板参数为我们实例化一个特定版本的函数,如上例,T的类型就是int。编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出模板的一个新的"实例":

int compare(const int &v1, const int &v2)
{
    if (v1 < v2) return -1;
    else if (v1 > v2) return 1;
    else return 0;
}

所以说,泛型编程在编译阶段就已经确定了数据的类型,注意区别:多态是运行时确定的。



码字不易,觉得不错的小伙伴可以一键三连支持一下~
C++知识整理系列(一)指针和动态空间_第1张图片

你可能感兴趣的:(C/C++知识整理专栏,c++,指针,编程语言,泛型)