C语言——指针&数组

C语言专题之指针&数组篇

C语言——指针&数组

指针

指针是一种编程概念,它是一个变量或数据类型,用于存储内存地址。指针允许程序直接访问和操作内存中的数据,而不仅仅是访问变量的值。指针通常在低级编程语言(如C和C++)中使用,以及某些高级编程语言中的底层编程任务中。

关键要点关于指针包括:

  1. 内存地址:指针存储一个内存地址,该地址指向计算机内存中的某个位置。
  2. 指向:指针可以指向内存中的数据,这可以是基本数据类型(如整数、字符)或复杂的数据结构(如数组、结构体)。
  3. 解引用:通过解引用指针,可以访问指针所指向的内存地址上的值。解引用操作使用 * 符号。
  4. 地址运算:指针可以进行地址运算,例如指针加法或减法,以访问相邻内存位置。
  5. 动态内存分配:指针在动态内存分配中非常有用,允许程序在运行时分配和释放内存,避免静态内存分配的限制。
  6. 传递参数:指针允许将变量的地址传递给函数,以便在函数内部修改变量的值。

好处

  1. 指针可以动态分配内存
  2. 在链表中可以方便修改链表的节点
  3. 解析字符串
  4. 相同类型的指针可以直接复制

调用Free释放内存后,指针还能用吗

Free释放掉内存后,只是把内存的使用权就被归还给系统,内存里面的东西可能被清除也可能是垃圾值,但是指向这个内存的指针还是指向这块内存,并不会NULL

指针不能加指针

指针之间可以做减法,但不能做加法

空指针是指指向地址为0的地方

数组

初始化

1
2
3
4
5
int a[][2]; //不允许
int b[][2]={1,2,3,4}; //可以
int c[] = {1,2,3};  // 可以
int c[];  //不可以
int d[][]; //不允许,第二个[]必须填,不管有没有初始化

数组名num / &num的区别

对于一维数组来说

num+1是偏移到下个元素,&num+1是偏移整个数组

对于二维数组来说

num+1是偏移一个一维数组,&num+1是整个数组

数组下标是负数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int a[5] = {0,1,2,3,4};
int *p = &a[4];
for(int i=-4; i<=0; i++)
{
  printf(%d %d \n", p[i], &p[i])
}
// 0  1310572
// 1  1310576
// 2  1310580
……

二维数组

int a[3][3];

  1. int a[3][3];表示是个三行三列的二维数组
  2. 数组名表示数组首元素的地址,即第0行第0个地址
  3. a+1表示地址偏移一个一维数组的地址,即三列int大小=34 = 12
  4. *a 表示去二维变一维,*a就相当于一维数组的数组名,比如 *a +1 表示第0行下标为1的元素地址,只是偏移一个Int地址
  5. 若要表示a[2][2]的元素 即 *(*(a+2)+2)

指针的运算

1
2
3
4
5
6
7
8
9
// 例子1:
int *ptr;//假设指针指向的地址是0x 00 00 00 00
Ptr++; //运算之后指针指向0x 00 00 00 04

Char *p;
P++;//地址偏移1

// 注意:对于一级指针的++操作偏移量要看指针指向的是什么类型
// 对应二级指针偏移量,对于32系统是4个字节,因为二级指针指向的类型就是指针,所以永远是一个指针类型的大小
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 例子2:
#include<stdio.h> 
int main() 
{ 
   char a[20]="You_are_a_girl"; 
   char *p=a; char **ptr=&p; 
   printf("**ptr=%c\n",**ptr); 
   ptr++; 
   printf("**ptr=%c\n",**ptr); 
}
// 在这个例子中是无法确定二级指针++之后指向的地址内容,因为二级指针(ptr)指针指向的一级指针的地址,如果二级指针(ptr)++之后,那么就会指向一级指针的后4个字节(对于32位操作系统来说指针类型是4字节),至于这个地址里面是啥无从得知
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// *p++   *(p++)     (*p)++     *++p   ++*p  的运算
int num[] = {1, 2, 3, 4};

1. ++先不用管,即先把地址里面的值给别人,再去++,可以是地址或值(看括号是否包住*p,是则是值++),后++有三种如下:
(*p)++   地址前后都不会变化,变化的是地址里面的值,先赋值给别人,*P再++
*(p++)*p++一样  地址发生变化,先把*P赋值给别人,再++地址

2.++,先++操作,可以是++地址或者值,(++符号靠近p就是地址++,靠近*P就是值++),再把值给别人
*++p   地址发生改变,先把地址++,再把地址变化后的里面的值给别人
++*p   地址不发生变化,*P的值++之后再赋值给别人

注意:指针++,到底加几个字节要根据指针指向的类型决定,指针在32系统中永远是4个字节
举例子:
Int * a;//假设指针指向的地址0
a++;//此时指针指向后移4个字节,即指向4

Char *b;//假设指针指向的地址0
b++//此时指针指向后移1个字节,即指向1
1
2
3
4
5
// sizeof(数组名)和sizeof(&数组)
int Num[100];
printf("%ld\n",sizeof(Num));//400
printf("%ld\n",sizeof(&Num));//8起始就是打印int *指针大小
printf("%ld\n",sizeof(int *));//8

指针数组

1
2
3
4
5
// 首先是个数组,这个数组里的元素是指针
int *p [4];
int a[4]={1,2,3,4};
p[0]=&a[0];
printf("%d\n",*p[0]);
1
2
3
4
5
6
7
8
9
// 首先是个指针,这个指针指向数组例如:
Int (*p) [4];//表示一个指向有4个int 元素的数组的指针,所以p+1,加的是4个int

int num[8] ={1,2,3,4,5,6,7,8};
int (*p)[4] ;
p = num;
printf("%d\n",sizeof(p));//8 因为p是指针,64位系统8个字节
printf("%p\n",p);//0x7ffe11d8a4e0 
printf("%p\n",p+1);//0x7ffe11d8a4f0 //加的是指针指向的类型大小,这里指针指向的是有四个4int元素的数组,所以加的是16个字节
1
2
3
4
5
// 双指针
int b[12] = {1,2,3,4,5,6,7,8,9,10,11,12}
int (*p)[4];
p = b;
printf("%d\n", **(++p));  // 5

指针函数

1
2
3
4
// 函数的返回值是个指针类型
int * fun(int x,int y){
   
}

函数指针

1
2
3
4
5
int (*pf)(float);

// 函数指针调用函数事注意以下几点:
1.	函数类型必须和函数指针的类型一样,比如参数类型,返回值
2.	给函数指针赋值是可以&也可以不要

指针和数组的区别

  1. 数据类型:
  • 指针是一种数据类型,用于存储内存地址。指针可以指向不同数据类型的内存位置。
  • 数组是一种数据结构,用于存储相同数据类型的一组连续内存单元。
  1. 大小:
  • 指针的大小通常与系统架构相关,它存储一个内存地址,因此大小在不同系统上可能会有所不同。
  • 数组的大小是由其包含的元素数量决定,每个元素的大小也是相同的。
  1. 初始化和赋值:
  • 指针需要显式初始化为一个有效的内存地址,可以通过将其设置为某个变量的地址来初始化。
  • 数组在声明时需要指定大小,而且在创建时会自动初始化,可以直接为数组元素赋值。
  1. 地址运算:
  • 指针允许进行地址运算,例如指针加法或减法,以访问内存中的相邻位置。
  • 数组的元素在内存中是连续存储的,因此可以通过索引来访问不同的元素。
  1. 传递给函数:
  • 指针可以用于将变量的地址传递给函数,以便在函数内部修改变量的值。
  • 数组在传递给函数时通常会退化为指针,因此函数接收到的是指向数组第一个元素的指针。
  1. 动态内存分配:
  • 指针可用于动态内存分配,例如使用 mallocnew 来分配内存,然后通过指针访问分配的内存。
  • 数组的大小通常在编译时确定,但C99标准引入了可变长度数组(VLA),允许在运行时指定数组大小。

指针和引用的区别

  1. 指针
  • 指针是一个变量,它存储另一个变量的内存地址。
  • 指针可以为空(null),也可以重新分配给指向不同的变量。
  • 指针需要使用解引用操作符 * 来访问所指向的值。
  • 指针可以进行指针算术,如指针加法或减法。
  • 指针可以指向不同类型的对象,但需要进行强制类型转换。
  • 指针可能需要显式地管理内存,包括分配和释放内存。
  1. 引用
  • 引用是一个别名,它为已经存在的变量提供了另一个名称。
  • 引用在创建时必须与现有变量绑定,无法改变绑定到不同的变量。
  • 引用的语法更简洁,不需要解引用操作符,直接使用引用就可以访问所绑定的值。
  • 引用不支持指针算术。
  • 引用通常用于传递参数给函数,以便在函数内部修改参数的值。
  • 引用不需要显式管理内存,不涉及内存分配和释放。

引用区别于指针的特性是

  1. 不存在空引用(保证不操作空指针)
  2. 必须初始化(保证不是野指针)
  3. 一个引用永远指向他初始化的那个对象(保证指针值不变)

野指针

野指针:是指指针指向的地址是不确定的;

野指针(Dangling Pointer)通常是指指针变量存储了一个无效的内存地址,也就是它指向的内存区域可能已经被释放或不再有效。野指针的操作是不安全的,因为它们可能导致未定义的行为或程序崩溃。

野指针一般来说可以被重新赋值,但这并不会解决野指针的问题。重新赋值一个野指针只是改变了它的目标地址,但仍然可能会导致访问无效内存。在C和C++中,遵循以下最佳实践来处理野指针:

  1. 避免野指针:在使用指针前,确保它指向有效的内存区域。不要让指针指向已释放的内存或未分配的内存。
  2. 初始化指针:在声明指针时,始终将其初始化为NULL(C语言)或nullptr(C++语言)。这可以帮助你检测是否有野指针。
  3. 谨慎使用已释放的内存:如果确实要使用已释放的内存,确保在释放内存后不再访问它。
  4. 不要多次释放相同的内存:释放内存后,不要再次释放相同的内存块,否则会导致问题。

原因

释放内存之后,指针没有及时置空

避免:

  1. 初始化置NULL
  2. 申请内存后判空
  3. 指针释放后置NULL
  4. 使用智能指针
1
2
3
4
5
int *p1 = NULL; //初始化置NULL
p1 = (int *)calloc(n, sizeof(int)); //申请n个int内存空间同时初始化为0 
assert(p1 != NULL); //判空,防错设计
free(p1);  
p1 = NULL; //释放后置空

指针 & const

1
2
3
4
const int* p;         //常量指针----->指针指向的地址的内容不可以改变
int const *p;         //常量指针
int * const p;        // 指针常量-------->指针指向的地址可以不改变
const int *  const p; //指向常量的常量指针

指针减指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 地址相减 =  (地址a -地址b)/sizeof(指针指向的类型)
int a[3];
a[0] = 0;
a[1] = 1;
a[2] = 2;
int *p, *q;
p = a;
q = &a[2];
printf("%p\n",p);//0x7ffe80e38b0c   
printf("%p\n",q);//0x7ffe80e38b14   

printf("%d\n",q-p);//2        ( q - p) /sizeof(int )
// 那么就有
a[q-p]  =  a[2] = 2

指针作为函数参数传递问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//函数1,交换两个数
void swap1(int *p,int *q)
{	
	int num =  *p;
	*p =*q;
	*q  = num;
}
//函数2,让p指针指向a,想重新给p赋值为90,之后再交换p,q
void swap(int *p,int *q)
{	
   int a =90;
  p = &a;
	int num =  *p;
	*p =*q;
	*q  = num;
}
int main()
{
  int a =2,b =3;
  int * j = &a;  // 指针j指向a
  int * k =&b;  //指针 k 指向 b

  swap(j,k); //调用函数
  printf("a =%d   b= %d\n",a,b);// a = 2 b = 90;
  return 0;
}
// 于函数swap1我们可以正常交换两个值,但是swap函数却不是我们想要的,我们想要的答案是 a = 3 b = 90;可是发现,a的值压根没变,这是为什么呢?

// 在swap函数中,你将指针p指向了一个局部变量a,而这个局部变量在swap函数执行完毕后将被销毁。这会导致指针p指向一个不再有效的内存位置

总结:
如果在函数形参里的指针变量不修改指向,那么就会影响传递过来的指针
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void fun(char *s) {
   char a[10];
   strcpy(a, "STRING");
   s = a;//修改了形参指针指向,就不会影响传递过来指针
}
void main() {
   char *p = "PROGRAM";
   fun(p);
   printf("%s\n", p); //PROGRAM
}

// 拓展:通过二级指针操作
void fun(char **s) {
   char a[10];
   strcpy(a, "STRING");
  *s = a;
  // *s = "string";
}
int main() {
   char *p = "PROGRAM";
   fun(&p);
   printf("%s\n", p); //打印空白
  return 0;
}
// 在这里我们通过把一级指针的地址传递给函数,函数二级形参来接受,那么*s表示的就是指针p指向的地址,可以看出这里直接就操作p的指针指向,所以如果  *s = “string”那么指针p就会指向”string”的地址 

二维数组和数组指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int main()
{
   /*二维数组和数组指针*/
   int (*p)[3];
   int s[3][3]={1,4,7,4,9,6,2,7,9};
   p = s;
   printf("p addr is = %p\n",p);//0x7ffde8cdf0a0
   printf("s addr is = %p\n",s);//0x7ffde8cdf0a0
   /*可以发现p+1和s+1是一样的都是加一个一维数组*/
   printf("p + 1 addr is = %p\n",p + 1);//0x7ffde8cdf0ac
   printf("s + 1 addr is = %p\n",s + 1);//0x7ffde8cdf0ac
   /*可以发现使用p表示二维数组的元素和二维数组名表示元素是一样的用法*/
   printf("*(*(p + 1))  is = %d\n",*(*(p + 1)));//*(*(p + 1))  is = 4
   printf("*(*(s + 1))  is = %d\n",*(*(s + 1)));//*(*(s + 1))  is = 4
   printf("*(p[1]+0)  is = %d\n",*(p[1]+0));//*(p[1]+0)  is = 4
   printf("*(s[1]+1)  is = %d\n",*(s[1]+1));//*(s[1]+1)  is = 9
   return 0;
}

// 一维数组:
int  num[2];   
printf("%p \n",num);  		//0x7fffe2a8cde8
printf("%p \n",num+1);		//0x7fffe2a8cdec   
printf("%p \n",&num+1);  	//0x7fffe2a8cdf0 

// 可以看出:
num +1 加的是一个Int
&num + 1加的是整个数组

// 二维数组;
int  num[2][2];
printf("%p \n",num);  //0x7ffe18f73560 
printf("%p \n",num+1);	 //0x7ffe18f73568 
printf("%p \n",&num+1); //0x7ffe18f73570 

// 可以看出:
num +1 加的是一个一维
&num + 1加的是整个数组
   
// 例子   
int main() 
{ 
   int num[2][3]={1,2,3,4,5,6};
   int (*p)[3];
   p = num;

   printf("%p\n",p);//0x7ffffad71df0
   printf("%p\n",p+1);//0x7ffffad71dfc
   
   printf("%p\n",num[0]+1);//0x7ffffad71df4
   printf("%p\n",*p+1);//0x7ffffad71df4
  // 注意这里&p+1
   printf("%p\n",&num+1);//0x7ffffad71e08
   printf("%d\n",**p);//1
}
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy