现在的位置: 首页 > 综合 > 正文

C/C++学习札记1:C语言指针5分钟教程

2017年12月17日 ⁄ 综合 ⁄ 共 3297字 ⁄ 字号 评论关闭

指针、引用和取值


    什么是指针?什么是内存地址?什么叫做指针的取值?

    指针是一个存储计算机内存地址的变量。在这份教程里“引用”表示计算机内存地址。从指针指向的内存地址中读取数据称作指针的取值。

    指针可以指向某些具体类型的变量的内存地址,例如 int、long 和 double。指针也可以是 void 类型、NULL 指针和未初始化指针。本文会对上述所有指针类型进行探讨。

    根据出现位置的不同,操作符 * 既可以用来声明一个指针变量,也可以用作对指针的取值。当用在声明一个变量时,* 表示这里声明了一个指针,其它情况用到 * 表示指针的取值。

    & 是地址操作符,用来引用一个内存地址。通过在变量名前使用 & 操作符,我们可以得到该变量的内存地址

// 声明一个 int 指针
int *ptr;
// 声明一个 int 变量
int val = 1;
// 为指针分配一个 int 变量的引用
ptr = &val;
// 对指针进行取值,打印存储在指针指向的内存地址中的内容
int deref = *ptr;
printf("%d\n", deref);

    第 2 行,我们通过操作符 * 声明了一个 int 指针。接着我们声明了一个 int 变量并赋值为1。然后我们用 int 变量的地址初始化我们的 int 指针。接下来对 int 指针取值,用变量的内存地址中的值初始化另一个 int 变量。最终,我们打印输出变量值,内容为1。

    第 6 行的 &val 是一个引用。在 val 变量声明并初始化之后,通过在变量名之前使用地址操作符 & 我们可以直接引用变量的内存地址。

    第 8 行,我们再一次使用操作符 * 来对该指针取值,可直接获得指针指向的内存地址中的数据。由于指针声明的类型是 int,所以取到的值是指针指向的内存地址存储的 int 值。

    这里可以把指针、引用和值的关系类比为信封、邮箱地址和房子。一个指针就好像是一个信封,我们可以在上面填写邮寄地址。一个引用(地址)就像是一个邮件地址,它是实际的地址。取值就像是地址对应的房子。我们可以把信封上的地址擦掉,写上另外一个我们想要的地址,但这个行为对房子没有任何影响。




void 指针、NULL 指针和未初始化指针


    一个指针可以被声明为 void 类型,比如 void *x 。

    一个指针可以被赋值为 NULL 。

    一个指针变量声明之后但没有被赋值,叫做未初始化指针。

int *uninit;	// int 指针未初始化
int *nullptr = NULL; 	// 初始化为 NULL
void *vptr;		// void 指针未初始化
int val = 1;
int *iptr;
int *castptr;
 
// void 类型可以存储任意类型的指针或引用
iptr = &val;
vptr = iptr;
printf("iptr=%p, vptr=%p\n", iptr, vptr);
 
// 通过显示转换,我们可以把一个 void 指针转成
// int 指针并进行取值
castptr = (int *)vptr;
printf("*castptr=%d\n", *castptr);
 
// 打印 NULL 和未初始化指针
printf("uninit=%p, nullptr=%p\n", uninit, nullptr);
// 不知道你会得到怎样的返回值,会是随机的垃圾地址吗?
// printf("*nullptr=%d\n", nullptr);
// 这里会产生一个段错误
// printf("*nullptr=%d\n", nullptr);

执行上面的代码,你会得到类似下面对应不同内存地址的输出:

iptr=0x7fff94b89c6c, vptr=0x7fff94b89c6c
*castptr=1
uninit=0x7fff94b89d50, nullptr=(nil)

    第 1 行我们声明了一个未初始化 int 指针。所有的指针在赋值为 NULL、一个引用(地址)或者另一个指针之前都是未被初始化的。第 2 行我们声明了一个 NULL 指针。第 3 行声明了一个 void 指针。第 4 行到第 6 行声明了一个 int 值和两个 int 指针。

    第 9 行到 11 行,我们为 int 指针赋值为一个引用并把 int 指针赋值给 void 指针。void 指针可以保存各种其它指针类型。大多数时候它们被用来存储数据结构。可以注意到,第 11 行我们打印了 int 和 void 指针的地址。它们现在指向了同样的内存地址。所有的指针都存储了内存地址。它们的类型只在取值时起作用。

    第 15 到 16 行,我们把 void 指针转换为 int 指针 castptr。请注意这里需要显示转换。虽然 C 语言并不要求显示地转换,但这样会增加代码的可读性。接着我们对 castptr 指针取值,值为1。

    第 19 行非常有意思,在这里打印未初始化指针和 NULL 指针。值得注意的是,未初始化指针是有内存地址的,而且是一个垃圾地址。不知道这个内存地址指向的值是什么。这就是为什么不要对未初始化指针取值的原因。最好的情况是你取到的是垃圾地址接下来你需要对程序进行调试,最坏的情况则会导致程序崩溃。NULL
指针被初始化为o。NULL 是一个特殊的地址,用 NULL 赋值的指针指向的地址为 0 而不是随机的地址
。只有当你准备使用这个地址时有效。不要对 NULL 地址取值,否则会产生段错误。



指针和数组


    C 语言的数组表示一段连续的内存空间,用来存储多个特定类型的对象。与之相反,指针用来存储单个内存地址。数组和指针不是同一种结构因此不可以互相转换。

    而数组变量指向了数组的第一个元素的内存地址。一个数组变量是一个常量。即使指针变量指向同样的地址或者一个不同的数组,也不能把指针赋值给数组变量。也不可以将一个数组变量赋值给另一个数组。然而,可以把一个数组变量赋值给指针,这一点似乎让人感到费解。把数组变量赋值给指针时,实际上是把指向数组第一个元素的地址赋给指针。

int myarray[4] = {1,2,3,0};
int *ptr = myarray;
printf("*ptr=%d\n", *ptr);
 
// 数组变量是常量,不能做下面的赋值
// myarray = ptr
// myarray = myarray2
// myarray = &myarray2[0]

    第 1 行初始化了一个 int 数组,第 2 行用数组变量初始化了一个 int 指针。由于数组变量实际上是第一个元素的地址,因此我们可以把这个地址赋值给指针。这个赋值与 *ptr = &myarray[0] 效果相同,显示地把数组的第一个元素地址赋值到了 ptr 引用。这里需要注意的是,这里指针需要和数组的元素类型保持一致,除非指针类型为 void。



指针与结构体


    就像数组一样,指向结构体的指针存储了结构体第一个元素的内存地址。与数组指针一样,结构体的指针必须声明和结构体类型保持一致,或者声明为 void 类型。

struct person {
  int age;
  char *name;
};
struct person first;
struct person *ptr;
 
first.age = 21;
char *fullname = "full name";
first.name = fullname;
ptr = &first;
 
printf("age=%d, name=%s\n", first.age, ptr->name);

    第 1 至 6 行声明了一个 person 结构体,一个变量指向了一个 person 结构体和指向 person 结构体的指针。第 8 行为 age 成员赋了一个 int 值。第 9 至 10 行我们声明了一个 char 指针并赋值给一个 char 数组并赋值给结构体 name 成员。第 11 行我们把一个person 结构体引用赋值给结构体变量。

    第 13 行我们打印了结构体实例的 age 和 name 。这里需要注意两个不同的符号 ’.’ 和 ‘->’ 。结构体实例可以通过使用 ‘.’ 符号访问age变量。对于结构体实例的指针,我们可以通过 ‘->’ 符号访问 name 变量。也可以同样通过 (*ptr).name来访问 name 变量。



总结


    希望这份简短的概述能够有助于了解不同的指针类型。在后续的博文中我们会探讨其它类型的指针和高级用法,比如函数指针。

    欢迎提出提问并给出评论。


转载自:

http://blog.jobbole.com/25409/






抱歉!评论已关闭.