一、指针基础
一个变量,变量名,变量值,变量地址。指针变量存放变量的地址,同时,指针变量有自己的地址,指针必须与变量的类型一致 指针变量本质上是一个变量,只不过这个变量是用来存放其他变量的地址,这是其与其他变量的不同之处,需要用*加以标识,与其他变量一样,使用时也需要提前声明
1.1 符号说明
1.2 代码举例
1.3 空指针
在C语言中,如果定义的指针没有确切指向的地址,则赋值为NULL,被称为空指针。在操作系统中,内存地址为0的内存是不可以访问的,如果指针为0,则假定它不指向任何东西
- 代码实现:
#include <stdio.h>
int main()
{
int *ptr = NULL;
int *ptr1;
int *ptr2;
if (!ptr)
{
printf("ptr地址指向为空\n");
}
printf("ptr 的地址是 %p\n", ptr);
printf("ptr1 的地址是 %p\n", ptr1);
printf("ptr2 的地址是 %p\n", ptr2);
return 0;
}
- 运行结果:
ptr地址指向为空
ptr 的地址是 (nil)
ptr1 的地址是 0x7ffd516bdd40
ptr2 的地址是 (nil)
二、指针的算数运算
C指针是一个用数值表示的地址。因此,可以对其进行有关的数值运算:+, -, *, /, <, >, ++, --。只不过,这种运算与指针的类型有关,以指针类型为单位进行运算。例如,一个指向整型变量的指针,初始值ptr为1000,则ptr++之后,其值变为1004,如果是字符,则为1001
- 代码实现:
#include <stdio.h>
int array[] = {1,2,3,4};
int main()
{
int a_size = sizeof(array)/sizeof(array[0]);
printf("递增************\n");
int i, *p;
p = array;
for(i = 0; i < a_size; i++){
printf("array[%d]=%d,地址:%p\n",i,array[i],p);
p++;
}
printf("递减************\n");
int i1, *p1;
p1 = &array[a_size-1];
for(i1 = a_size - 1; i1 >= 0; i1--){
printf("array[%d]=%d,地址:%p\n",i1,array[i1],p1);
p1--;
}
printf("比较************\n");
i = 0;
p = array;
while(p <= &array[a_size-1]){
printf("array[%d]=%d,地址:%p\n",i,array[i],p);
p++;
}
return 0;
}
- 运行结果:
递增************
array[0]=1,地址:0x55edd61c3010
array[1]=2,地址:0x55edd61c3014
array[2]=3,地址:0x55edd61c3018
array[3]=4,地址:0x55edd61c301c
递减************
array[3]=4,地址:0x55edd61c301c
array[2]=3,地址:0x55edd61c3018
array[1]=2,地址:0x55edd61c3014
array[0]=1,地址:0x55edd61c3010
比较************
array[0]=1,地址:0x55edd61c3010
array[0]=1,地址:0x55edd61c3014
array[0]=1,地址:0x55edd61c3018
array[0]=1,地址:0x55edd61c301c
三、指针数组和数组指针
3.1 指针数组
- 定义:
指针数组是一个数组,只不过这个数组中,存放的是指向int或char等其他类型的指针,在同一个数组中,只能存放指向同一个类型的指针 - 格式:type *ptr[]
- 代码实现:
#include <stdio.h>
int main()
{
printf("存放整数地址的数组\n");
int array_int[] = {0, 1, 2};
int *array_int_ptr[3];
int array_int_size = sizeof(array_int) / sizeof(array_int[0]);
for (int i = 0; i < array_int_size; i++)
{
array_int_ptr[i] = &array_int[i];
}
for (int i = 0; i < array_int_size; i++)
{
printf("array_int[%d] = %d,ptr=%p\n", i, *array_int_ptr[i], array_int_ptr[i]);
}
printf("存放字符地址的数组\n");
char array_char[] = {'a', 'b', 'c'};
char *array_char_ptr[3];
int array_char_size = sizeof(array_char) / sizeof(array_char[0]);
for (int i = 0; i < array_char_size; i++)
{
array_char_ptr[i] = &array_char[i];
}
for (int i = 0; i < array_char_size; i++)
{
printf("array_char[%d] = %c,ptr=%p\n", i, *array_char_ptr[i], array_char_ptr[i]);
}
}
- 运行结果:
存放整数地址的数组
array_int[0] = 0,ptr=0x7ffc60d0c9c4
array_int[1] = 1,ptr=0x7ffc60d0c9c8
array_int[2] = 2,ptr=0x7ffc60d0c9cc
存放字符地址的数组
array_char[0] = a,ptr=0x7ffc60d0ca15
array_char[1] = b,ptr=0x7ffc60d0ca16
array_char[2] = c,ptr=0x7ffc60d0ca17
3.2 数组指针
- 定义:
数组指针是一个指针,其存放着一个数组的首地址,长度固定(系统不同,长度不同) - 代码实现:
#include<stdio.h>
void main(){
int arr[] = {0,1,2};
int (*p)[3];
p = &arr;
for(int i = 0; i < 3; i++){
printf("arr[%d]=%d,ptr=%p\n",i,(*p)[i],p[i]);
}
printf("p的大小:%ld\n",sizeof(p));
}
- 运行结果:
arr[0]=0,ptr=0x7ffed0e7a53c
arr[1]=1,ptr=0x7ffed0e7a548
arr[2]=2,ptr=0x7ffed0e7a554
p的大小:8
四、字符串与指针
4.1. 字符串的定义与遍历
字符串其实就是字符数组,归根结底是一个数组
- 代码实现:
#include<stdio.h>
#include<string.h>
void main(){
char str[] = "hello world!";
int len = strlen(str);
int size = sizeof(str);
printf("字符串长度(strlen):%d\n",len);
printf("字符串长度(sizeof):%d\n",size);
printf("每次输出一个字符:");
for(int i = 0; i < len; i++){
printf("%c",str[i]);
}
printf("\n");
printf("直接输出字符串:%s\n\n",str);
char str1[] = "hello worl\0d!";
int len1 = strlen(str1);
int size1 = sizeof(str1);
printf("字符串长度(strlen):%d\n",len1);
printf("字符串长度(sizeof):%d\n",size1);
printf("每次输出一个字符:\n");
printf("通过len1:");
for(int i = 0; i < len1; i++){
printf("%c",str1[i]);
}
printf("\n");
printf("通过size1:");
for(int i = 0; i < size1; i++){
printf("%c",str1[i]);
}
printf("\n");
printf("直接输出字符串:%s\n",str1);
}
- 运行结果:
d102@d102-W65KJ1-KK1:/media/d102/EPAN/Desktop/c++_ubuntu/c++_01/workspace$ gcc test2.c -o t2.o
d102@d102-W65KJ1-KK1:/media/d102/EPAN/Desktop/c++_ubuntu/c++_01/workspace$ ./t2.o
字符串长度(strlen):12
字符串长度(sizeof):13
每次输出一个字符:hello world!
直接输出字符串:hello world!
字符串长度(strlen):10
字符串长度(sizeof):14
每次输出一个字符:
通过len1:hello worl
通过size1:hello world!
直接输出字符串:hello worl
4.2 使用指针输出字符数组
- 代码实现:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
char *str_p = "hello world";
char *p = str;
printf("指针的内存大小:%ld\n", sizeof(str_p));
printf("str_p:%s\n", str_p);
printf("str:%s\n", str);
printf("str_p首地址:%p\n", str_p);
printf("str首地址:%p\n", str);
printf("str_p str_p[4]:%c\n", str_p[4]);
printf("str str[4]:%c\n", str[4]);
printf("str_p str_p[4]:%c\n", *(str_p + 4));
printf("str str[4]:%c\n", *(str + 4));
int len = strlen(str);
printf("单个字符打印 *(p+i) :");
for (int i = 0; i < len; i++)
{
printf("%c", *(p + i));
}
printf("\n");
printf("单个字符打印 *(str+i):");
for (int i = 0; i < len; i++)
{
printf("%c", *(str + i));
}
printf("\n");
printf("使用p[i]打印:");
for (int i = 0; i < len; i++)
{
printf("%c", p[i]);
}
printf("\n");
return 0;
}
- 运行结果:
指针的内存大小:8
str_p:hello world
str:hello world
str_p首地址:0x563c2f0bb008
str首地址:0x7ffdf7a46fdc
str_p str_p[4]:o
str str[4]:o
str_p str_p[4]:o
str str[4]:o
单个字符打印 *(p+i) :hello world
单个字符打印 *(str+i):hello world
使用p[i]打印:hello world
4.3 字符数组(字符串变量)与字符指针(字符串常量)
4.3.1 区别与联系
从4.2小结可以看出,字符数组和字符指针在使用上没有任何区别,都是字符串的两种表示形式,但是它们并不完全一样。字符数组是可以读写的,而字符指针只可以读,不可以写
- 字符数组形式:
char str[] = "hello world";
- 字符指针形式:
char *str = "hello world";
- 代码实现:
#include<stdio.h>
int main(){
char str[] = "hello world";
str[0] = 'x';
str[1] = 'x';
printf("str:%s\n",str);
char *str_p = "hello world";
str_p = "xiao ma ge";
printf("str_p:%s\n",str_p);
}
- 运行结果:
str:xxllo world
str_p:xiao ma ge
4.3.2 存储方式
- 字符数组由若干元素组成,每个元素存放一个字符
- 字符指针只存放字符串的首地址,不是整个字符串
4.3.3 赋值方式
4.3.4 可否被修改
字符指针可以改变其指向的内容,也可以改变其指向,但是不能更改其指向内容里的某一元素
- 代码实现:
#include<stdio.h>
int main(){
char str[] = "hello world";
str[0] = 'x';
str[1] = 'x';
printf("str:%s\n",str);
char *str_p = "hello world";
str_p = "xiao ma ge";
printf("str_p:%s\n",str_p);
char c[] = "abc";
str_p = c;
printf("str_p:%s\n",str_p);
}
- 运行结果:
str:xxllo world
str_p:xiao ma ge
str_p:abc
4.3.5 初始化
五、指针的指针
指针用来存放变量的地址,同时,指针也有自己的地址,因此,就可以设置一个指针变量,用来存放指针的地址,也就是指针的指针,他存放的是一个地址,通过取值符*可以取出相应位置的值
-
格式:**p -
图片解释: -
代码实现: #include<stdio.h>
int main(){
int a = 10;
int *p1;
int **p2;
int ***p3;
p1 = &a;
p2 = &p1;
p3 = &p2;
printf("a的值:%d\n",a);
printf("p1地址的值(a的地址):%p\n",p1);
printf("p1地址存放的值(a的值):%d\n",*p1);
printf("p2地址的值(p1的地址):%p\n",p2);
printf("p2地址存放的值(p1的值,a的地址):%p\n",*p2);
printf("p2地址存放的整数的值(a的值):%d\n",**p2);
printf("p3地址的值(p2的地址):%p\n",p3);
printf("p3地址存放的值(p2的值,p1的地址):%p\n",*p3);
printf("p3地址存放的整数的值(a的值):%d\n",***p3);
return 0;
}
-
运行结果: a的值:10
p1地址的值(a的地址):0x7ffd25ad858c
p1地址存放的值(a的值):10
p2地址的值(p1的地址):0x7ffd25ad8590
p2地址存放的值(p1的值,a的地址):0x7ffd25ad858c
p2地址存放的整数的值(a的值):10
p3地址的值(p2的地址):0x7ffd25ad8598
p3地址存放的值(p2的值,p1的地址):0x7ffd25ad8590
p3地址存放的整数的值(a的值):10
六、传递指针给函数
C语言允许传递指针给函数
- 代码实现:
#include<stdio.h>
#include<time.h>
void getSeconds(unsigned long *p){
*p = time(NULL);
}
double getAverage(int *arr, int size){
int i, sum;
double avg;
for(i = 0; i < size; i++){
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
int main(){
unsigned long second;
int balance[5] = {1,2,3,4,5};
getSeconds(&second);
printf("当前时间:%ld\n",second);
double avg;
avg = getAverage(balance,5);
printf("平均值:%f\n",avg);
return 0;
}
- 运行结果:
当前时间:1657786996
平均值:3.000000
七、从函数返回指针
C语言支持从函数返回指针
- 代码实现:
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
int *getRandom(){
static int r[10];
int i;
srand((unsigned)time(NULL));
for(i = 0; i < 10; i++){
r[i] = rand();
}
return r;
}
int main(){
int *p;
int i;
p = getRandom();
for(i = 0; i < 10; i++){
printf("*(p + %d):%d\n",i,*(p+i));
}
return 0;
}
- 运行结果:
*(p + 0):406451966
*(p + 1):1274401563
*(p + 2):1286473475
*(p + 3):95700003
*(p + 4):1434496087
*(p + 5):1913921399
*(p + 6):1648156783
*(p + 7):475047123
*(p + 8):1060638060
*(p + 9):378428022
八、指针的一些复杂说明
- int p; – 这是一个普通的整型变量
- int * p; – 首先从 p 处开始,先与结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
- int p[3] – 首先从 p 处开始,先与[] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
- int *p[3]; – 首先从 p 处开始, 先与 [] 结合, 因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。
- int (*p)[3]; – 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。
- int **p; – 首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
- int p(int); – 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明函数的返回值是一个整型数据。
- int (*p)(int); – 从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
- int *(*p(int))[3]; – 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。
参考链接
|