关于指针
- 1.看一个简单的程序,来接触下指针
- 2. 常见疑问:指针就是地址,那么int的指针和double的指针有什么区别 了
- 3. 常见疑问:指针运算
- 4. 为什么存在奇怪的指针运算符
- 5. 试图将数组作为函数的参数进行传递。
- 6. 什么是空指针
- 5.1 声明函数形参的方法
- 6. 指向函数的指针
- 7. 什么是指向数组的指针
- 7.1 将数组类型解读成指针
- 8. const 修饰指针(常量指针/指针常量)
- 9. typedef 给指针定义别名
- 10. 指针和字符串常量
- 11. 关于指向函数指针引起的混乱
- 附录:代码示例
本章节,我们重点分析C指针,首先我们从指针的概念谈起
💚💚💚
- 指针是一种保存变量地址的变量,并在C中频繁使用
- 在C语言标准中:最初出现指针,也有这样一段话
🧡指针类型(pointer type)可由:函数类型,对象类型或不完全的类型派生,派生指针类型的类型称为引用类型。
🧡指针类型,描述一个对象,该类对象的值提供对该引用类型实体的引用。由引用类型T 派生的指针类型有时称为 “指向T的指针”
从引用类型构造指针类型的过程称为“指针类型的派生”。
🧡 如由 int 类型派生的指针,称为 “指向int 类型的指针”。
下面,我们对指针的研究,主要从如下几个方面展开。
3. 指针类型
4. 指针类型变量
5. 指针类型的值(即内存地址)
1.看一个简单的程序,来接触下指针
#include<iostream>
using namespace std;
int main()
{
int hoge = 5;
int piyo = 10;
int* hoge_p;
// 输出每个变量的地址
printf("&hoge...%p\n",&hoge);
printf("&piyo...%p\n",&piyo);
printf("&hoge_p...%p\n",&hoge_p);
// 将 hoge的内存地址赋值给 hoge_p
hoge_p = &hoge;
// 打印指针变量的值
printf("hoge_p...%p\n",hoge_p);
// 通过hoge_p输出 hoge的内容
printf("*hoge_p...%d\n",*hoge_p);
// 通过 hoge_p修改 hoge的内容
*hoge_p = 10;
printf("*hoge...%d\n",hoge);
return 0;
}
// 打印内容
&hoge...000000000061fe1c
&piyo...000000000061fe18
&hoge_p...000000000061fe10
hoge_p...000000000061fe1c
*hoge_p...5
*hoge...10
我们用一个图来说明下。
- 指针变量 hoge_p保存了另外一个变量 hoge地址,我们认为 “hoge_p” 指向 “hoge”。
- 对 hoge变量实施 &运算符得到 “hoge地址” 。 有时也称:"hoge的地址"的为 “指向 hoge的指针” (实际上这里的指针指:指针类型的值)
- 在 指针前面加上 * 可以表示指针指向的变量。(hoge_p 指向hoge), 所以 , *hoge_p 等同于 hoge , 那么一旦要求输出 *hoge_p 就会输出 hoge保存的值 。
🧡🧡🧡 要点
- 对变量使用 & 运算符,可以取得该变量的地址。 这个地址称为指向 该变量的指针。
- 指针变量 hoge_p 保存了指向其他变量的地址情况下(如保存 hoge 地址)可以说 “hoge_p 指向 hoge” 。
- 对指针变量运算 *运算符,就等同于它指向的变量。(如 hoge_p指向 hoge, *hoge_p就等同于 hoge)。
2. 常见疑问:指针就是地址,那么int的指针和double的指针有什么区别 了
- 如果从指针变量的角度说,指向这两个类型的指针没有区别,都保持相同的表现形式,与指针类型无关。
💚(尽管C标准没有规定所有数据类型的长度,但通常是这样一种情况:整数类型指针长度是一样的, char 类型指针和 结构体指针 长度一致,函数指针长度与数据指针长度不同)
💚 指针长度取决于使用的机器和编译器,通常在 现代 windows上,指针是32 位或64位长,对于DOS来说是 16位长。 - 从指针运算的角度来说,就需要关注指针类型了
💚(如:对指针加 N 即 :指针前进 “当前指针指向的数据类型的长度 * N”) - 指向任何类型的指针类型 ------ void* 类型
void test_voidPointer()
{
int hoge = 5;
void* hoge_p;
// void 类型指针保存 hoge内存地址,这个是没问题的
hoge_p = &hoge;
// 但是由于编译器并不知道 hoge_p 指针类型,仅仅知道内存地址,不知道保存数据的类型,这样是不能取值的。
printf("%d", *hoge_p); // error :'void*' is not a pointer-to-object type
}
💚💚💚 改正
void test_pointer_02()
{
int hoge = 5;
void* hoge_p;
// void 类型指针保存 hoge内存地址,这个是没问题的
hoge_p = &hoge;
// 将指针强制转换成 指向 int 类型指针,这样编译器就指针取出来的是 int 类型的值
printf(" %d",*(int*)hoge_p); // 打印5
}
3. 常见疑问:指针运算
- C指针运算是其他语言没有的
- 指针运算是针对指针进行整数加减运算
// 指针的运算
void test_pointer_03(){
int hoge;
int *hoge_p;
// 将指向 hoge的指针赋予 hoge_p
hoge_p = &hoge;
// 输出 hoge_p的值
printf("hoge_p...%p\n",hoge_p);
// 给 hoge_p 加1
hoge_p++;
// 输出 hoge_p 的值
printf("hoge_p...%p\n",hoge_p);
// 输出 hoge_p后 加3 的值
printf("hoge_p....%p\n",hoge_p +3);
}
// 打印结果
hoge_p...000000000061fde4
hoge_p...000000000061fde8
hoge_p....000000000061fdf4
🧡从上面的打印可知:指针加1 ,前进的字节是4个。这个结论和简单,也很直观。但是,我想通过:指针和数组的关系来证明下这个结论。请看下面示例
// 指针和数组之间关系
void test_pointer_04()
{
int array[5];
int *p;
int i;
// 给数组 array 的各元素设定值
for(i=0; i<5;i++)
{
array[i] = i;
}
// 输出数组各元素的值(指针版本)
for(p = &array[0]; p != &array[5]; p++)
{
printf("%d\n",*p);
}
}
// 打印结果
0
1
2
3
4
💚💚💚
4. 为什么存在奇怪的指针运算符
在第三章节中,我们知道访问数组的内容,老老实实用下标就可以了,为什么C语言需要存在指针运算符这样奇怪的功能了 ?
- C继承了 早期的 B 语言影响
- 使用指针运算可以写出高效的程序。
▲●通过角标的方式访问数组,array[i] 在循环中会出现多次,每次都要进行相当于 *(array + i) 的加法运算,效率自然是比较低的
▲●但是通过 p+i 的方式,加法运算只有在循环结束的时候执行一次。
💚 如今,编译器在不断被优化,对于循环内部重复的表达式会集中处理,是编译器优化的基本内容,对于现在一般的C编译器,无论你使用的数组角标还是指针来访问 数组元素,效率上都不会出现明显的差距,基本删都是输出完整的机器码。
5. 试图将数组作为函数的参数进行传递。
从上几章节我们知道:
**💚 数组 buf[ ] 可以解读成 :
- 指向它的初始元素的指针
- *buf[ len] 是 (buf + len) 的语法糖
下面,我们来看一个实用性的例子:从英文的文本中将单词 一个一个取出来 。
// 试图将数组作为函数参数来传递
void test_pointer_05(char* buf, int buf_size)
{
}
int main()
{
char buf[256];
test_pointer_05(buf,256);
return 0;
}
6. 什么是空指针
💚 空指针是一个特殊的值
- 空指针是指可以确保没有指向任何一个对象的指针,通常使用 宏定义 NULL 来表示空指针常量值
- 空指针确保它和任何非空指针进行比较都不会相等,因此经常作为函数发生异常时的返回值使用
- 在现今的操作系统下,应用程序一旦视图通过空指针引用对象,就会马上招致一个异常并且当前应用程序会被操作系统强制终止
💚 常量0 和 NULL 的关系 。
4. 在C中,在为0的地址上,是不能保存数据的,放什么都不能起作用。所以标准运行 将 NULL 定义成 ( void*)0
int* p = 3; // error :invalid conversion from ‘int’ to ‘int*’
这是因为:编译器会认为 3 是int 类型,但是 p 指针 int* 指针类型,int类型和 指针类型肯定是有区别的int* p1 = 0; // OK
std::cout <<*p1; // Segmentation fault
这是因为编译器根据上下文 “将常量0应该作为指针使用” , 这个时候编译是可以通过的,但是在运行时,获取指针类型保存的地址指向的值 是无效的,所以会出现 Segmentation fault
5.1 声明函数形参的方法
6. 指向函数的指针
函数在表达式中被解读成 “指向函数的指针” , 它本质上也是指针(地址) ,所以可以将它赋给指针型变量 。
下面我们用一个图分析下,指向函数的 指针和 指向函数的指针的数组。
7. 什么是指向数组的指针
- “数组”和“指针”都是派生类型,他们都是由基本类型开始重复派生生成的。也就是说派生出“数组”后,再派生出“指针”,就可以生成“指向数组的指针”。
- 我们需要区分:指向数组的指针和 指针数组初始元素的指针
// 指向数组的指针
void test_pointer_06()
{
// array_p 是指向int 数组(元素个数为3个)的指针
int (*array_p)[3];
// p_array 是指向数组初始元素的指针
int array[4];
int *p_array = &array[0];
}
7.1 将数组类型解读成指针
💚 单目运算符& 被称为地址运算符 : &将一个左值作为操作数,返回指向该左值的指针
💚 单目运算符* 被称为解引用:将指针作为操作数,返回指针所指向的对象或函数。
💚 ->运算符:此运算符没有明确的定义,但可以将其称为“箭头运算符”,通过指针访问结构体成员时候,就会使用 ->运算符
💚 [ ] 下标运算符:在C语言中,遇导下标运算符[ ] 可以将元素个数省略不写,但是不同 编译器针对不同的情况,有不同的解释。
void test(){
int hoge[10]{1,2,3,4,5,6,7,8,9,10}; // 这是一个数组
// & 运算符
int *p = &hoge[0]; // 将数组解读成指针
// * 运算符
int hoge_first = *p; // 将指针解读成数组的值
cout << hoge_first; // 1
// [] 小标运算符
int hoge_second = hoge[1];
int hoge_third = *(p+2);
cout << "\n" << hoge_second;
cout << "\n" << hoge_third;
// ->运算符
hoge_struct* hogeStru_p = &hogeStru;
cout<< "\n"<< hogeStru_p->hoge;
cout<< "\n"<< (*hogeStru_p).hoge;
}
int main()
{
test();
return 1;
}
💚下标运算符使用场景
8. const 修饰指针(常量指针/指针常量)
9. typedef 给指针定义别名
10. 指针和字符串常量
💚 使用 " " 包围起来的字符串被称为 字符串常量,字符串常量的类型是 " char的数组",因此在表达式中,可以被解读为指针 。
// 声明一个: 指向 char 类型的指针 str
char *str;
// 将 [指向 “abc” 的初始化元素的指针] 赋值给 str
str = "abc";
// note : 针对 字符串常量“abc” ,编译器事实上会将字符分开处理 即:
"abc" -----》 {'a','b','c','\0'}
11. 关于指向函数指针引起的混乱
💚
附录:代码示例
/**
1: 指针: 指针类型,指针变量,指针变量的值
2:指针就是存储的内存地址的
3:将内存分区和指针结合起来
4:void 指针,NULL指针
*/
#include<iostream>
using namespace std;
// 指针的基本操作:定义和概念
void test_pointer_01()
{
int hoge = 5;
int piyo = 10;
int* hoge_p;
// 输出每个变量的地址
printf("&hoge...%p\n",&hoge);
printf("&piyo...%p\n",&piyo);
printf("&hoge_p...%p\n",&hoge_p);
// 将 hoge的内存地址赋值给 hoge_p
hoge_p = &hoge;
// 打印指针变量的值
printf("hoge_p...%p\n",hoge_p);
// 通过hoge_p输出 hoge的内容
printf("*hoge_p...%d\n",*hoge_p);
// 通过 hoge_p修改 hoge的内容
*hoge_p = 10;
printf("*hoge...%d\n",hoge);
}
// 指针的转换
void test_pointer_02()
{
int hoge = 5;
void* hoge_p;
// void 类型指针保存 hoge内存地址,这个是没问题的
hoge_p = &hoge;
// 但是由于编译器并不知道 hoge_p 指针类型,仅仅知道内存地址,不知道保存数据的类型,这样是不能取值的。
// printf("%d",*hoge_p); // error :invalid operands of types 'const char [4]' and 'void*' to binary 'operator*'
// 将指针强制转换成 指向 int 类型指针
printf(" %d",*(int*)hoge_p); // 打印5
}
int main()
{
test_pointer_02();
return 0;
}