c语言指针

概述

定义变量时,相应的内存大小被保留,变量名称因此对应于变量所在的地址。保留的内存位置的数量取决于数据类型。(&Name/Function)可以来确定内存地址,%p表示输出地址类型数据

#include <stdio.h> 
struct 
{
    int x, y, z;
} var3;
union  {char a;short b;int c;} var4;
enum {ROT,GRUEN,BLAU} var5;
int func(void) {return 1;}            
int main() { 
    int var1; 
    var1=7;
    printf("Var1=%d Adresse=0x%x %p\n", var1, (int)&var1, &var1);
    int var2;
    printf("var2=%d   &var1=%p &var2=%p\n",var2,&var1,&var2);
    printf("&var3=%p  &var3.x=%p  &var3.y=%p  &var3.z=%p\n",&var3, &var3.x, &var3.y, &var3.z);
    printf("&var4=%p  &var4.a=%p  &var4.b=%p  &var4.c=%p\n",&var4, &var4.a, &var4.b, &var4.c);
    printf("&var5=%p\n", &var5);
    printf("func=%p\n", func);
    printf("&func=%p\n", &func);
}

输出结果

Var1=7 Adresse=0x61fe1c 000000000061FE1C
var2=16 &var1=000000000061FE1C &var2=000000000061FE18
&var3=0000000000407978 &var3.x=0000000000407978 &var3.y=000000000040797C &var3.z=0000000000407980
&var4=0000000000407974 &var4.a=0000000000407974 &var4.b=0000000000407974 &var4.c=0000000000407974
&var5=0000000000407970
func=0000000000401550
&func=0000000000401550

注:

  • 指针变量所需的内存大小与底层变量数据类型无关,但取决于系统的计算宽度为4-字节/8-字节大。
  • 显示的地址为该数据类型的第一个地址位置

指针变量

语法:数据类型 *指针变量名
指针变量接收一个地址。它相当于一个指针变量,只不过它的变量内容表示一个地址(其他变量的起始地址)。

#include <stdio.h>
#include <string.h>
int main()
{
    int var1, var2;
    int *ptr1;
    ptr1 = &var1;                                    //Zuweisung eines Pointers mit einer Adresse
    int *ptr2 = &var2;                               //Zuweisung eines Pointers
    int var2a, *ptr2a = &var2a; //OK
    // int *ptr2b = &var2b, var2b;                      //KO
    int *ptr3a = (int *)100;                         //不报错但不正确
    // int *ptr3b = 100;                                //KO
    // int *ptr4 = &7;                                  //KO
    ptr1 = ptr2;
    char str[] = "Hallo";
    char *start1 = str;     
    char *start2 = &str[0]; 
    char *ende = &str[strlen(str)-1];
}

*引用指针变量

*可以操作该指针变量所指向的地址

#include <stdio.h>
#include <string.h>
int main()
{
    int var1, *ptr1 = &var1;
    var1 = 7; //Direktes schreiben des Wertes 7 in
    *ptr1 = 7; //Indirektes schreiben des Wertes 7
    printf("%x %x %d %d", ptr1, &var1, *ptr1, var1);
    //Bei Strukturen
    struct xyz
    {
        int x, y, z;
    } var2;
    struct xyz *ptr2;
    ptr2 = &var2; //Startadresse der Struktur zuweisen
    var2.x = 1;
    var2.y = 1;
    var2.z = 7; //Direktes schreiben
    //*ptr2          ‐‐> Zugriff auf die vollständige Struktur (12‐Byte)
    (*ptr2).x = 2;
    (*ptr2).y = 2; //Indirektes Schreiben über deferenzierung
    ptr2 -> x = 3;
    ptr2 -> y = 3;                         //Indirektes Schreiben über deferenzierung
    int *ptr3 = &var2.y; //Adresse eines Strukturelementes
    int *ptr4 = &ptr2 -> y;                //Adresse eines Strukturelementes
    // *ptr2.x = 7;                          //报错
}

指针的算数运算

编译器通常将连续的地址分配给源代码中一个接一个定义的变量。

#include <stdio.h>
#include <string.h>
int main()
{
    int arr[3] = {123, 456, 789}; //Alle 3 Arrayelemente liegen im Speicher direkt hintereinander
    printf("%x %x %x\n", &arr[0], &arr[1], &arr[2]);
    int var1 = 1, var2 = 2, var3 = 3; //Alle 3 Variablen liegen im Speicher direkt
    int *ptr2 = &var2;                //hintereinander. (Ausnahme bei lokalen Variablen,
                                      //hier optimiert der Compiler ggf. die erste lokale Variable)
    printf("%x %x %x\n", &var1, &var2, &var3);
    printf("%x %x %x\n", ptr2 - 1, ptr2, ptr2 + 1);
    printf("%d %d %d\n", var1, var2, var3);
    printf("%d %d %d\n", *(ptr2 - 1), *(ptr2), *(ptr2 + 1));
}

运行结果:

61fe0c 61fe10 61fe14
61fe08 61fe04 61fe00
61fe00 61fe04 61fe08
1 2 3
3 2 1

Int var[2]={7,8},*ptr=&var;

在上面的代码中ptr+1 相当于 ptr+(1*sizeof(int))

  • 指针与一个整数的加减法意味着该指针的首地址加上该整数与sizeof(该指针指向的类型)的积
  • 两个指针作加法乘法除法操作和逻辑运算是不允许的,编译器会报错
  • 两个指针作减法操作是可以的

指针运算应当注意:
指针运算可能导致无效的内存地址------程序员在使用指针运算时应该知道他们在做什么。知道他们在使用指针运算时在做什么。将无效的地址存储在一个指针变量中并不是问题,但引用时会出现问题:

  • 程序崩溃,因为访问了未分配给程序的内存位置 -> 分段故障 被访问 -> 分段故障(在这些情况下,在缓冲区的内容包含在 标准输出缓冲区不输出。因此,打印出下列文件的输出是有帮助的:用fprintf(stderr, …)或用fflush(stdout)发送重要信息。
  • 地址指向一个有效的内存地址,这个地址有不同的 “含义”,例如,分配给另一个变量。例如,属于另一个变量。取消引用这个地址将 另一个变量的内容将发生变化,这只有在使用该变量时才会被注意到。以后使用这个变量。在这种情况下,程序的表现大多很 “奇怪”。这种 错误是很难发现的,因为发现错误的时间和其
    误差的出现在时间上相距甚远

与地址符有关的递增与递减

Var=*ptr++ //Entspricht var=*ptr; ptr=ptr+1;
Var=*ptr‐‐ //Entspricht var=ptr; ptr=ptr‐1;
Var=
++ptr //Entspricht ptr=ptr+1; var=ptr;
Var=
‐‐ptr //Entspricht ptr=ptr‐1; var=*ptr;

#include <stdio.h>
#include <string.h>
int var1=1, var2=2, var3=3; //Bitte als globale Variablen anlegen
int main()
{
    int *ptr = &var2;
    var1 = var2++;
    var1 = ++var2;          //Inkrementieren einer Variablen
    printf("%d ", *ptr++);   //4
    printf("%d ", (*ptr)++); //3
    printf("%d ", *--ptr); //4
    char str[] = "hallo\0";
    char *ptr2 = &str[0];
    for (; *ptr2++;);                                            
    printf("%d", ptr2 - str); //6
    char *ptr3 = &str[0];
    for (; *++ptr3;);                                            
    printf("%d", ptr3 - str); //5
}

指针差值

两个指针的差值表示两个指针的距离,也就是基础数据类型的倍数。
减法的结果数据类型是ptrdiff_t(大小取决于系统,有可能是signed int/ unsigned long/ unsigned long long)

#include <stdio.h>
#include <string.h>
int main()
{
    struct xyz
    {
        int x, y, z;
    } xyz[] = {{1, 2, 3}, {4, 5, 6}, {0, 0, 0}};
    struct xyz *ptr;
    for (ptr = xyz; ptr -> x; ptr++);
    printf("%d", (int)(ptr - xyz)); //2
}

指针的比较

#include <stdio.h>
#include <string.h>
int var1, var2; //Bitte als globale Variablen anlegen
int main()
{
    int *ptr1 = &var1, *ptr2 = &var2;
    printf("%d", (ptr1 == ptr2));
    printf("%d", (ptr1 < ptr2));
    printf("%d", (ptr1 <= ptr2));
    printf("%d", (ptr1 >= ptr2));
    printf("%d", (ptr1 == NULL));
    printf("%d", (ptr1 > NULL));
}

运行结果:

011001

初始化指针

在首次使用指针(引用)之前必须赋予它一个有效的存储地址

char *ptr;
*ptr = 47;

上述代码是错误的,因为第一行定义变量后并没有给他初始化一个存储空间。

- 未被初始化的全局指针被初始化为0,也就是说,引用会导致将返回内存地址0的内容。在Windows/Linux中,这不是分配给进程的,因此导致了程序崩溃。
- 未初始化的本地指针被分配了一个随机值,也就是说,一个解读很可能会读取一个未分配的内存地址的内存,或者导致一个记忆地址或导致程序崩溃。

因此初始化一个指针有如下选择
- 用全局变量的地址进行初始化
全局变量/静态局部变量/函数在整个程序运行期间有效,并且程序运行时,这些地址的指针因此总是有效的。
- 用局部变量的地址进行初始化
局部变量、传参只在函数体或块作用域内有效。对指针的操作只允许发生在函数或块作用域运行期间

```c
//OK
Void func(int par)
{
    int lok;
    int *ptr1 = &par;
    int *ptr2 = &loc;
    *ptr1++;
    *ptr2++;
}
//KO
int *func(void){
	int lok = 7;
	return(&lok);
	// 函数体结束即释放内存
}
Void main()
{
   int *ptr = func();
   *ptr=7;
}
//OK
void func1(int *ptr){
	*ptr = 7;//OK
}
Void func2(void)
{
    int lok = 7;
	func1(&lok);
}
//KO
Int *ptr;
void func(void)
{
    int lok=7;
    ptr=&lok;
	*ptr=8;
}
Void main(void)
{
    *ptr=7;
    func();
    *ptr=7;
}

用堆空间初始化指针:
内存池,所有函数都可以从中请求任何大小的内存。然后,这个被请求的内存保留,直到它被再次明确释放。也就是说,C语言运行环境现在可以将这段内存分配给其他调用者。将此内存分配给其他调用者。因此,在释放后,不再允许对内存的访问。

int main(int argc, char const *argv[])
{
    char *ptr = malloc(100);
    strcpy(ptr, "hello world");
    printf("%s\n", ptr);
    free(ptr);
    ptr = null;
    printf("%s", ptr);
    return 0;
}

在free(ptr)之后最好将该指针指向空

当指针没有被初始化或指针指向的地址失效后,该指针被称为野指针

cast操作符

在c语言中指针的赋值和比较没有隐式的类型转化,因此两个指针必须是相同的类型。否则编译器将报错。
一个指针可以通过显式的类型转换(cast操作符)被转换为另一个指针。没有数据类型的转换,只是改变内存地址的内容的解释。
不管是什么类型的指针变量,所存的值都是地址(int类型的值)。那么声明不同类型的作用是什么?答案是规定指针在内存中每次移动的字节数。例如定义“int *pa = &a”,取值时,int类型占4个字节,指针就从首地址开始移动,读取4个字节。同理,short类型占2字节,指针就移动2字节。通过声明指针类型,告诉指针每次移动多少字节,来获取变量的值。

 	short c[2];          //等价于申请2个连续的内存空间,每个空间2字节
    c[0] = 1;            //为第一个short空间赋值为1
    c[1] = 1;            //为第二个short空间赋值为1
    short *p1 = c;       //p1指向c[]首地址
    int *p2 = (int *)p1; //p2指向c[]首地址,并强制转换类型为 int
    printf("p1指向:%p\np2指向:%p\n", p1, p2);
    printf("p1取出:%d\np2取出:%d\n", *p1, *p2);
    return 0;

运行结果:

p1指向:000000000061FE0C
p2指向:000000000061FE0C
p1取出:1
p2取出:65537

void指针

在编程语言C中,关于指针的分配/比较,唯一的例外是void指针(通用指针、匿名指针、未定型指针)。这与任何其他的数据指针兼容,所以不需要显式转换就可以进行比较和赋值。
由于void数据类型的原因,不能单独使用void指针来引用内容。
在GNU-C中,允许用空指针进行指针运算。这里假设引用的内存大小为1。
应用:

  1. malloc函数返回值类型就是void指针,因此malloc函数可以给每一种指针赋值
    Int ptr1=malloc(10sizeof(int));
    Struct {int x,y,z;} *ptr2=malloc(12);
  2. 如果一个结构要被调用者 “隐藏”,那么结构定义就不会写在头文件中,而是写在C文件中。然后,调用者将该地址存储在一个void指针中。
    在这里插入图片描述

NULL指针

全局非初始化变量在程序启动时被初始化为0。
由于这些准被视为非初始化,有效的内存地址0被认为是无效的内存地址。由于不可能将一个指针与一个整数常数0进行比较(见CAST运算符),所以定义了一个宏(见预处理程序)NULL,它将常数0到一个地址为0的无效指针。
#define NULL ((void *)0)
该宏包含在头文件stddef.h中。

应用:

  1. 用来判断malloc是否分配内存成功
int *ptr=malloc(100);
If(ptr==NULL)
  1. 数组指针。在字符串数组的尾部用NULL标记表示结束
char *strarray[]={"hallo","Du",NULL};
int lauf;
for(lauf=0;strarray[lauf];lauf++)
printf(strarray[lauf]);

注意:
在Windows/Linux系统中,内存地址0没有分配给进程,因此引用会导致运行时错误,而在嵌入式系统中,内存地址0被很好地分配给 “进程”。这通常是中断向量表的位置,因此,向这个地址写入可能会产生严重的后果。

调用函数时的指针

指针一个重要的应用领域就是在函数调用时形参与返回值指针的使用。这里的主要目标是,一方面,避免耗时的数据复制,另一方面,使被调用者能够直接改变调用者的值。
值传递:

void func(int lok) {lok=hallo*2;}
func(7); 
func(var);

在这里常数7和变量var会复制到形参lok中,形参lok的变化对源变量将不会产生变化

址传递:
如果传递的不是变量的内容,而是变量的地址,那么被调用者现在就有可能通过地址改变原始变量的内容。

void func(int *ptr) {*ptr=10;  ptr=ptr+7;}
int main(int argc, char const *argv[])
{
    int var = 1;
    printf("%x ", &var);
    func(&var);
    printf("%d ", var);
    printf("%x ", &var);
}

输出结果:

61fe1c 10 61fe1c

址传递应用:

  • 可以代替有返回值的函数,当
    • 需要返回不止一个值
    • 函数返回值作为状态
  • 可以使程序运行更快,通过不复制内容,而是交换地址
Void swap(int *a, int *b) {int temp=*a; *a=*b; *b=temp;}

返回值:
除了使用指针传递参数外,还可以返回指针。

char *strstr( const char *haystack, const char *needle);
char *strtok(char *str, const char *delim)

在这里,我们也避免了更耗时的复制,而是只返回实际内容所在的地址。
特别注意的是 “寻找地址”。返回的地址在函数结束后必须仍然有效,所以不能返回指向局部变量的指针。只有指向全局变量的指针、静态局部变量或指向堆区的指针才有意义。
函数的状态也经常通过返回值返回。在指针的情况下,函数执行中的错误由一个指向NULL地址的指针表示。在整数作为返回值的情况下,根据POSIX标准返回-1。

const修饰指针

const修饰的是左边的字符,当const位于最左边时,修饰与它紧挨的右边的字符

const int *var1;
int const *var2;

此处const修饰int, 表示地址变量的引用(所指向的值)不可变,地址可变

int *const var

此处const修饰*,表示地址不可变,地址的引用(所指向的值)可变

int const *const var

地址和地址的引用都不可变

应用:

  • 函数体在用指针传参是,为保证指针引用内容不变应当使用const
char *strcpy(char *dst, char const *src);

字符串常量

字符串的存储会被存储在常量区域,字符串指针返回的是第一个字符的地址

char *p1="Hallo"; //Hier müsste eigentlich compiler Warning ausgeben
const char *p2="du";    //Korrekte Schreibweise

因此用指针定义的字符串是不可以改变的,以下代码将会造成程序终止

p1[1]='x';
*((char *)p2)='y';

字符串常量在初始化局部变量时也会产生,比如char arr[] = “hello”; 在编译器内部执行了以下代码

Const char *anonymous3="hallo";
Char arr[5+1];
memcpy(arr,anonymous3);

函数指针

如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

定义方式:函数返回值类型 (* 指针变量名) (函数参数列表);
注:

  • *“(指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。
  • 指向函数的指针变量没有 ++ 和 – 运算。
# include <stdio.h>
int Max(int, int);  //函数声明
int main(void)
{
    int(*p)(int, int);  //定义一个函数指针
    int a, b, c;
    p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);  //通过函数指针调用Max函数
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    return 0;
}
int Max(int x, int y)  //定义Max函数
{
    int z;
    if (x > y)
    {
        z = x;
    }
    else
    {
        z = y;
    }
    return z;
}

输出结果:

please enter a and b:3 4
a = 3
b = 4
max = 4

typedef函数指针:

// 定义一个原型为int Fun( int a );的函数指针
typedef int (*PTRFUN) ( int aPara );
PTRFUN pFun; // pFun 为函数指针变量名
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值