C/C++指针

指针(pointer)是C/C++语言中的一种数据类型。指针与int、char等数据类型相似,都是在内存中开辟相应类型的数据区域使用,不同的是int存储的是整数值,而指针存储的是内存地址。指针是在内存中开辟指针类型的区域存储内存地址,通过指针存储的内存地址找到对应内存区域的值。简单讲就是通过一个内存地址找到另一个内存地址的值。虽然指针都是用来存储内存地址,但是在定义指针时要明确指针指向的内存区域的类型,比如int*整数型指针指向整数型地址,char*字符型指针指向字符型地址,void*无类型指针指向任何类型地址,void*指针指向的内容可以转换为任何类型的值。

一、什么是内存地址

指针是用来存储内存地址的数据类型,那么内存地址是什么?计算机软件在运行过程中使用内存进行数据存储,操作系统开辟的每一块内存区域都有相应的内存地址,每块内存区域又可以存储不同的数据,计算机通过内存地址来读取和写入内存区域的数据。当然开辟内存区域存储的内容为何类型,是整数还是字符串需要在定义时明确。

二、指针运算符

(一)运算符*

*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在定义时表示定义和初始化,在定义和初始化后表示所指向的变量。

int myValue;
// 这里的*pint表示定义
int *pint = &myValue;
// 这里的*pint实际上就是myValue
*pint = 6;

(二)运算符&

&是单目运算符,其结合性为自右至左,功能是取变量的地址。任何在内存中开辟的区域都可以通过&来获取地址,可以是变量、常量、指针。

// 变量地址
int myValue;
int *pint = &myValue;

// 指针地址
int **newPint = &pint;
// 赋值myValue为6
**newPint = 6;

// 常量地址
const int myValue1 = 10;
const int *pint1 = &myValue1;
内存区域:变量
内存地址
名称
类型
内存区域:一级指针
内存地址
名称
类型
内存区域:二级指针
内存地址
名称
类型
指向
指向
6
int
myValue
0x325658
0x325658
int*
pint
0x3848921
0x3848921
int**
newPint
0x3256567

三、指针定义与初始化

指针与普通的常量变量一样,使用前需要先定义,意思是让操作系统开辟内存区域。指针定义后必须立即初始化,否则编译将会报错。初始化可以赋值某个内存地址,当不确定目标类型时可以初始化为空指针。

(一)初始化内存地址

int myValue;
int *pint = &myValue;
// 变量与指针同时定义
int myValue, *pint = &myValue;

指针指向的地址必须与目标地址的数据类型一致,如指针时int类型,指向的地址也必须时int类型,反之为非法赋值。

(二)初始化为常量

// 合法,表示pint指针指向一个无名字符串常量的地址,里面存放的数据是字符数组abc,
// 指针初始化为数组不需要&运算符,自动指向数组首地址。
char * pint = "abc";
// 非法
*pint = "def";

初始化指针时赋值字符串,表示在内存中开辟区域,并把首地址存储到pint指针中,指针指向地址为常量不可更改。

(三)初始化为数组

int ages[20];
int *pint = ages;
// 变量与指针同时定义
double readings[50], *marker = readings;

数组不需要&寻址符,赋值时将会把数组首地址赋值给指针。

指针指向数组项

int ages[20];
// 指针指向ages数组索引5的子项
int *pint = &ages[5];

指针指向数组项时需要使用&运算符。

(四)初始化空指针

1.空指针0

因为指针定义后必须赋值初始化,当未确定指针存放的地址时,可以赋值为0初始化为空指针。

int *ptrToint = 0;
double *ptrToDouble = 0;

0表示分配给当前未指向一个有效内存位置的指针,用户程序不能访问地址为 0 的内存,因为它被操作系统数据占用,这使得 0 成为指示无效内存位置值的安全选择。

2.空指针NULL

某些头文件(如iostream、fstream 、 cstdlib)定义了NULL常量为0地址,当程序包含这些头文件时只需要赋值NULL给指针即可定义空指针。

int *ptrToint = NULL;
float *ptrTofloat = NULL;

3.空指针nullptr

C++11 定义了关键字 nullptr 来指示一个无效的内存地址

int *ptrToint = nullptr;
double *ptrToDouble = nullptr;

4.空指针的判断

指针在使用前可以先判断是否为空指针。

if (p != nullptr) { // 使用指针 P... }
if (p != NULL) { // 使用指针 P"... }
if (p != 0) { // 使用指针 P... }
// 等同于
if (p) { // 使用指针 P... }

四、指针的常量定义

指针的常量有指针常量、常量指针两种,区别在于定义时指针前有无修饰符const。因为指针是内存地址的指向,两种常量涉及到指针本身和指向地址的修改关系,因此指针本身和指向地址到底哪个不可更改很容易混淆。可以理解为定义指针时在*(指针)之后有const为指针常量,之前则为常量指针。另外还有一种*前后都有const,表示指向常量的指针常量。

(一)指针常量

指针常量int * const指针本身是常量不可变,指向地址可以是常量也可以是变量。

1.指向“变量”的指针常量

int myValue;
int newValue;
// 指向“变量”的指针常量,
int * const pint = &myValue;
// 非法
pint = &newValue;
// 合法
*pint = 6;

指针int *前无const修饰符,指针指向地址为int类型,指针本身不可修改,指向的int类型变量可修改。

2.指向“常量”的指针常量

指向的地址是常量,”指针常量“前应加const修饰符,即const int * const

const int myValue = 10;
const int newValue = 20;
// 指向“常量”的指针常量
const int * const pint = &myValue;
// 非法
pint = &newValue;
// 非法
*pint = 6;

指针int *前有const修饰符,指针指向地址为const修饰的int类型,“指针常量”本身不可修改,指向的const修饰的int类型变量也不可修改。

(二)常量指针

常量指针const int *指针本身是变量,指向的地址是常量。

const int myValue = 10;
const int newValue = 20;
// 指向“常量”的常量指针
const int * pint = &myValue;
// 或者
int const * pint = &myValue;
// 合法
pint = &newValue;
// 非法
*pint = 6;

因为定义指针是const修饰的指向类型,表示指向的地址不可更改。pint指针名前没有const修饰说明指针本身是变量,可以重新赋值。

五、多级指针

CC++可以使用多级指针指向同一块内存空间地址,也就是多层指针最终指向一个内存地址,修改任何多级指针的任何一级指针都可以修改指向地址的值。

#include 
int main() {
    int myValue;
    // 一级指针pint
    int *pint0 = &myValue;
    *pint0 = 5;
    printf("Value:%d\n", myValue);

    // 二级指针newPint
    int **pint1 = &pint0;
    **pint1 = 6; // 赋值myValue为6
    printf("Value:%d\n", myValue);

    // 三级指针newPint
    int ***pint2 = &pint1;
    ***pint2 = 7; // 赋值myValue为6
    printf("Value:%d\n", myValue);

    return 0;
}

输出

Value:5
Value:6
Value:7

六、指针调用

C/C++函数是可以将指针作为参数定义的,在函数中定义的是形式参数,当调用该函数时将变量的地址传递给指针,函数中就可以使用指针去修改实际参数。这种调用称为指针调用。

指针调用相当于在调用函数时,将实际参数的内存地址传递给函数的形式参数,这样函数中就可以通过指针修改实际参数的值。

#include 
using namespace std;

int func(int* a)
{
    return ++*a;
}

int main()
{
    int i = 1;
    cout << "Value:" << func(&i) << endl;
    cout << "Value:" << i << endl;
    return 0; 
}

输出

Value:2
Value:2

函数中修改的指针,其实就是main()函数的i变量。

结构体作函数参数

结构体可以跟普通变量一样通过参数传递给函数,传递的可以是结构体也可以是结构体的地址,当传递结构体调用时,实际上的将实际参数拷贝给形式参数,形式参数是函数的局部变量,调用结束即销毁;传递地址调用在被调用函数中访问的实际上就是实际参数本身。

#include 
struct Books
{
    char  title[50];
    char  author[50];
    char  subject[100];
    int   book_id;
};

/* 结构体作为参数 */
int func(struct Books book1)
{
    // 结构体使用(.)访问成员变量
    book1.book_id += 1;
    return book1.book_id;
}

/* 结构体作为指针参数 */
int func1(struct Books* book1)
{
    // 指针指向的结构体,使用->访问成员变量
    // 修改指针指向结构体,就是结构体本身,非函数调用时创建的局部变量
    // book1实际上就是main()函数的book
    book1->book_id += 1;
    return book1->book_id;
}

int main() {
    struct Books book;
	book.book_id = 5;
    int i;

    /* 传值调用 */
	i = func(book);
	printf("func, %d, %d\n", book.book_id, i);

    /* 传址调用 */
    i = func1(&book);
    printf("func1, %d, %d\n", book.book_id, i);

    return 0;
}

输出

func, 5, 6
func1, 6, 6

func1()传递结构体的指针

你可能感兴趣的:(C++,c语言,c++)