C++指针详解

需要注意

  1. 将类型指针看做一个新的数据类型,存放常量或者变量的地址
  2. = 两端数据类型需要相同
  3. & 放在变量前取地址,可赋值给相同类型的指针变量前
  4. 指针类型变量前加 * 获得地址中的数据

指针运算与数组

数组 int a[100]={0}
数组名就是第一个元素的地址,所以可以赋值给指针

int a[100];
int * iptr = a;

此时
a[i]*(a+i)iptr[i]*(iptr+i)等同,等于第i个元素的值
而且
&a[i]iptr+i&iptr[i]a+i、等同,等于第i个元素的地址

注意数组名为指针常量,不可被赋值
注意指针只支持加减运算

关于sizeof()

在C++语言中,sizeof 是一个用于获取数据类型或对象的内存大小(以字节为单位)的运算符。它通常以如下的方式使用:

sizeof(数据类型或对象)

以下是一些关于 sizeof 运算符的重要信息:

  1. 用法sizeof 运算符可以用于任何数据类型、变量、或者表达式,它返回一个 size_t 类型的值,表示其操作数所占用的内存大小。

  2. 示例:以下是一些示例用法:

    int sizeOfInt = sizeof(int); // 获取整数int的大小
    double sizeOfDouble = sizeof(double); // 获取双精度浮点数double的大小
    char myChar = 'A';
    int sizeOfChar = sizeof(myChar); // 获取字符char变量myChar的大小
    
  3. 不同数据类型的大小:不同的数据类型在不同的编译器和平台上可能有不同的大小。通常情况下,sizeof 的结果取决于编译器和目标平台的架构。例如,在大多数系统上,sizeof(int) 通常为4字节,sizeof(double) 通常为8字节。

  4. 结构和类的大小sizeof 运算符也可以用于结构体和类,它将返回这些自定义类型的实例所占用的内存大小。结构体和类的大小通常是其成员变量大小之和,但也可能受到编译器的优化和对齐方式的影响。

  5. 运行时和编译时sizeof 运算符在编译时计算大小,因此它的结果是一个常量表达式。这使得它非常适用于数组大小的计算,例如 int myArray[10]; 的大小可以使用 sizeof(myArray) 来获取。

运用

int myarray[] = {1,2,3,5,6,8}
int array_num = sizeof(myarray)/sizeof(*myarray); //数组中元素的个数

注意

数组名和数组第一个元素的指针并不等同

#include "basic.h"

int8 i = 0;
int array[10] = {0};
int * iptr = array;
int main(){
    cout<<"array.size="<<sizeof(array)/sizeof(*array)<<endl;
    cout<<"array.size="<< sizeof(iptr)<<endl;
    return 0;
}

堆区、栈区、静态区、常量区、代码区以及内存的申请与释放

C++ 程序运行时的内存分为不同的区域,包括栈区、堆区、静态区、常量区、代码区。以下是对每个区域的详细解释:

  1. 栈区(Stack Area)

    • 栈区是用于存储函数调用和局部变量的地方。
    • 每次函数被调用时,函数的局部变量会被分配到栈上。
    • 栈区的内存管理由编译器自动完成,当函数退出时,分配给局部变量的内存会被释放。
    • 栈上的内存分配和释放是快速的,但生命周期有限,函数退出时局部变量就会销毁。
    • 栈区内存通常有限,不适合存储大型对象或长时间存活的数据。
  2. 堆区(Heap Area)

    • 堆区用于存储动态分配的内存。
    • 堆上的内存分配和释放是手动控制的,通常使用 newdeletemallocfree 函数来完成。
    • 堆区的内存生命周期由程序员控制,可以在需要时分配内存,并在不再需要时释放它。
    • 堆区适用于需要在程序的不同部分之间共享数据的情况,但需要谨慎管理以防止内存泄漏或访问非法内存。
  3. 静态区(Static Area)

    • 静态区用于存储全局变量、静态变量和常量。
    • 静态变量的内存分配在程序启动时完成,并在程序运行期间一直存在。
    • 全局变量和静态变量通常存储在静态区,并在整个程序生命周期内保持不变。
    • 静态区的内存由编译器分配和管理。
  4. 常量区(Constant Area)

    • 常量区用于存储字符串字面值和常量数据。
    • 字符串字面值(例如:“Hello, World!”)存储在常量区。
    • 常量区的数据通常在程序启动时加载,并在整个程序运行期间保持不变。
    • 常量区的内存由编译器分配和管理。
  5. 代码区(Code Area)

    • 代码区是存储程序的可执行代码的地方。
    • 它包含程序的二进制指令,用于执行函数和操作。
    • 代码区通常是只读的,不允许写入。
    • 程序的执行从代码区开始。

堆区内存的申请与释放

1. malloc()free()

void* malloc(size_t size)

申请size 大小的堆内存区域,返回没有类型的指针

#include <cstdlib>
int asize = 8;
int *ap = (int *)malloc(asize * sizeof(int));  //申请内存

此时 ap 可看做有8个整形类型元素的数组

当申请的堆内存不够分配时将返回空指针NULL

void free(void *)

参数为之前调用 malloc() 时返回的地址

free(ap);  //释放内存

2. newdelete

C++ 中的 newdelete 是用于动态内存分配和释放的运算符。它们允许程序在运行时请求内存,而不是在编译时确定内存大小。

  1. new 运算符

    • new 运算符用于在堆区(Heap Area)动态分配内存,并返回分配内存的地址。
    • 可以用于分配单个对象或数组。
    • 使用 new 运算符时,需要指定要分配的数据类型,如 new intnew MyClass
    • 示例:
      int *ptr = new int; // 分配一个整数的内存
      MyClass *obj = new MyClass; // 分配一个自定义类的对象的内存
      
  2. delete 运算符

    • delete 运算符用于释放由 new 分配的动态内存。
    • 必须与 new 配对使用,否则会导致内存泄漏。
    • 使用 delete 时,需要指定要释放的内存的指针。
    • 示例:
      delete ptr; // 释放整数的内存
      delete obj; // 释放自定义类对象的内存
      
  3. new[] 运算符和 delete[] 运算符

    • 除了分配单个对象的内存外,newdelete 还支持分配和释放数组的内存。
    • 使用 new[] 分配数组内存,使用 delete[] 释放它。
    • 示例:
      int *arr = new int[10]; // 分配一个包含10个整数的数组的内存
      delete[] arr; // 释放整数数组的内存
      
  4. 异常安全性

    • 使用 new 时,如果分配内存失败,它将抛出 std::bad_alloc 异常。
    • 在分配内存后,应始终检查指针是否为 nullptr,以确保内存分配成功。
    • 使用 delete 时,应该确保指针不为空,否则可能导致未定义行为。
  5. 智能指针替代

    • C++11引入了智能指针(如 std::shared_ptrstd::unique_ptr),它们提供更安全的内存管理,减少了手动管理内存的需要,有助于避免内存泄漏和释放已释放内存的问题。

指向常量的指针、指针常量、指向常量的指针常量

const int a = 78;
const int * pi = &a; //指向常量的指针

int * const pc = 18; //指针常量
*pc = 16; //可以修改指针指向的数据
p = &a //! error 不能修改常量pc

const int ci = 66;
const int * const pd = &ci; //指向常量的指针常量,指针 pd 和指针指向的区域 *pd 均不能修改

空类型指针 void *

void * 仅表示存放一个地址,不指向任何类型不能进行指针运算,也不能间接引用

int a = 20;
int * pr = &a;

void * p = pr; // 可以将其他类型的指针赋值给空类型指针
pr = p; // error 不能将空类型指针赋值给其他类型指针
pr = (int *) p; // 可以显式转换赋值

字符串与字符指针

字符串为双括号括起来的字符序列

char buffer[] = "hello";

char buffer[] = {'h','e','l','l','0','\0'};

以上两种方法等同,因此可以将字符串当做字符数组来看待 buffer 即第一个元素的首地址

char *pc = "hello";
cout<<pc<<endl; // hello
cout<<*pc<<endl; //h
cout<<*(pc+1)<<endl;//e

指针数组

char * proname[] = {"Lihua","Luke","Alice"};

char name[3][8] = {"Lihua","Luke","Alice"};

节省存储空间

字符串比较

由于字符串的地址特性,因此字符串比较不能直接使用 == ,可以使用 strcmp()


char * p1 = "hello";
char * p2 = "hello";
char * p3 = "helle";

cout<<p1==p2<<endl;         // false
cout<<strcmp(p1,p2)<<endl;  // 0


实际上编译器可能会对此做出优化,在 Clion 中的结果如下

#include "basic.h"
#include <cstring>
int8 i = 0;
int main(){
    char buffer1[6] = "hello";
    char buffer2[6] = "hello";

    char *p1 = "hello";
    char *p2 = "hello";

    cout<<((buffer1==buffer2)?"equal":"not equal")<<endl; //not equal
    cout<<((p1==p2)?"equal":"not equal")<<endl;  // equal
    cout<<((strcmp(buffer1,buffer2)==0)?"equal":"not equal")<<endl; //equal
    return 0;
}

命令行参数

C++ 程序可以通过命令行参数来接收外部输入,这些参数是在程序运行时从命令行传递给程序的信息。命令行参数允许在不更改程序代码的情况下传递配置选项、文件名、运行参数等信息给程序。

在C++中,主函数 main 可以接受两个参数:argcargv。这两个参数分别表示命令行参数的数量和参数数组。

  1. argc(Argument Count)

    • argc 是一个整数,表示命令行参数的数量。
    • 它包括程序名(可执行文件名)作为第一个参数。
    • 如果没有提供任何参数,argc 的值将为1,因为至少会有一个参数,即程序本身的名称。
  2. argv(Argument Vector)

    • argv 是一个指向字符指针数组的指针,每个指针指向一个命令行参数的字符串。
    • argv[0] 指向程序本身的名称(可执行文件名)。
    • argv[1]argv[2]、…、argv[argc-1] 分别指向额外的命令行参数字符串。
    • argv[argc] 指向空指针(nullptr),用于表示参数的结束。
    • 空指针为假while(NULL) 为假,可用于遍历参数。

以下是一个简单的示例,演示如何使用 argcargv 来访问命令行参数:

#include <iostream>

int main(int argc, char* argv[]) {
    std::cout << "Total number of command line arguments: " << argc << std::endl;
    
    // 输出所有命令行参数
    for (int i = 0; i < argc; ++i) {
        std::cout << "Argument " << i << ": " << argv[i] << std::endl;
    }
    
    return 0;
}

在上述示例中,argc 的值表示传递给程序的命令行参数数量,argv 数组包含这些参数的字符串。

例如,如果运行程序如下:

./my_program arg1 arg2 arg3

则程序将输出:

Total number of command line arguments: 4
Argument 0: ./my_program
Argument 1: arg1
Argument 2: arg2
Argument 3: arg3

函数指针

函数名也是地址,指向代码区

函数指针是C++中非常有用和强大的特性之一。它们允许您在运行时动态选择要调用的函数,从而增强了程序的灵活性。

  1. 函数指针的定义

    函数指针是一个指向函数的指针变量。它的声明通常如下所示:

    return_type (*function_pointer)(parameter_type1, parameter_type2, ...);
    
    • return_type:函数返回的类型。
    • function_pointer:函数指针变量的名称。
    • parameter_type1, parameter_type2, ...:函数的参数类型。

    例如,以下是一个函数指针声明,指向一个接受两个整数参数并返回整数的函数:

    int (*add)(int, int);
    
  2. 初始化函数指针

    函数指针可以初始化为指向特定函数的地址。例如,将上述的 add 函数指针初始化为指向一个加法函数的地址:

    int sum(int a, int b) {
        return a + b;
    }
    
    add = sum; // 初始化函数指针为指向 sum 函数的地址
    
  3. 通过函数指针调用函数

    使用函数指针调用函数与直接调用函数类似,只需像调用普通函数一样使用函数指针即可:

    int result = add(3, 4); // 使用函数指针调用 sum 函数
    

    请注意,函数指针的使用方式与函数名相同,这使得它们可以在运行时动态选择要调用的函数。

  4. 函数指针作为参数

    函数指针还可以作为函数的参数,这样可以实现函数回调或在运行时切换不同的实现。例如:

    void performOperation(int a, int b, int (*operation)(int, int)) {
        int result = operation(a, b);
        std::cout << "Result: " << result << std::endl;
    }
    
    int add(int a, int b) {
        return a + b;
    }
    
    int subtract(int a, int b) {
        return a - b;
    }
    
    int main() {
        performOperation(5, 3, add);      // 调用 performOperation 使用 add 函数
        performOperation(5, 3, subtract); // 调用 performOperation 使用 subtract 函数
    
        return 0;
    }
    

    在上述示例中,performOperation 函数接受一个函数指针作为参数,可以根据传递的函数指针调用不同的操作函数。

  5. 函数指针数组

    您还可以创建函数指针数组,其中每个元素都是指向不同函数的函数指针,以便在运行时选择要调用的函数。

函数指针需要与被调用函数类型保持一致

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值