当前位置:   article > 正文

程序中变量定义、指针等问题(static和const、指针 相关概念和使用方式)_static const 指针

static const 指针

1、程序中变量定义存储等(static和const相关概念和使用方式)

1、static 及函数中变量定义存储

修饰对象

static可以修饰:局部变量、全局变量、函数
  • 1

static修饰后改变的东西:

static修饰对象,会改变其**生存周期**和**作用域**,生存周期就是一个变量或函数从内存分配到回收的过程。
作用域主要是指,这个函数或变量可以在哪些文件和地方可以调用得到。
  • 1
  • 2

具体作用

1、修饰 局部变量

局部变量就是函数内定义的变量,普通的局部变量,生存周期是随着函数的结束而结束,每次函数重新执行,局部变量都会分配内存,内部的值自然也是重新赋值,一般如果没有初始化会自动赋值0。
当使用static修饰局部变量时,会保存上一次函数调用时的变量值,相当于改变了局部变量值的生存周期。可以利用这个特点来达到变量值保存的效果,比如利用static修饰的局部变量来作为函数执行次数的计数标志(当然是用全局变量也可以达到相同的效果。)在这个过程中,函数作用域仍然是没有改变的。改变生存周期的原因是被static修饰的局部变量被存放在.bss段或者.data段,而普通的局部变量时存放在栈上面的。
补充:①data段(可读可写):data包含 静态初始化 的数据,有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接是自动分配,它和程序大小没有关系,但和程序使用到的全局变量,常量数量有关。
②text段(只读):text段是程序代码段,在AT91库中表示程序段的大小,它是由编译器在编译连接时自动计算的。
③bss段(可读可写):bss段是英文 Block Started by Symbol的简称,通常是指用来 存放在程序中 未初始化的全局变量 的一块内存区域,在程序载入时由内核清零。bss段属于 静态内存分配
④堆区:堆区(heap)一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式类似链表。
⑤栈区:由编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
⑥全局区/静态区:全局区(静态区)(static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域(.data段),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(.bss段),程序结束后系统释放。
⑦字符串常量区:文字常量区----字符串就是放在这里,程序结束后,由系统释放。 ⑧代码区:存放二进制代码。 【注】:一个程序的3个基本段:text段,data段,bss段。

int a = 0; 全局初始化区   
char *p1; 全局未初始化区   
main()   
{   
  int b;char s[] = "abc";char *p2;char *p3 = "123456"; 123456/0在常量区,p3在栈上。   
  static int c =0; 全局(静态)初始化区   
  p1 = (char *)malloc(10);   
  p2 = (char *)malloc(20);//分配得来得10和20字节的区域就在堆区。   
  strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。   
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
2、修饰全局变量

全局变量用static修饰改变了作用域,没有改变生存周期。普通的全局变量是可以被其他的.c文件引用的,一旦被static修饰,就只能被定义该全局变量的.c文件引用,使得该全局变量的作用范围减小。
作用:当一个全局变量不想被其他.c文件引用时,可以用static修饰,这样其他的文件就不能通过extern的方式去访问,这样主要是为了数据安全。

3、修饰函数

函数用static修饰,改变了作用域。普通的函数是可以通过头文件声名的方式被其他文件调用,被static修饰后就只能在本文件里被调用,这样是为了数据的安全。
作用:有些函数并不想对外提供,只需要在本文件里调用,这时候就可以用static去修饰。
总结:改变了作用域,没有改变其生存周期。

总结

用static修饰全局变量和函数,除了上面说的数据安全,防止被误引用,还有一个作用是解决重名问题。当用static修饰了全局变量和函数后,其他文件里再定义同名的全局变量和函数也是可以的。一般来说,如果不是要对外提供的函数和全局变量,最好都用static修饰。

static法则:

A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;

B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;

C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;

值得注意的是对全局变量加static,定义为静态存储方式,并不意味着是静态存储;而不加static,是动态存储。两种形式的全局变量(外部变量)都是静态存储方式,都是编译时分配存储空间,但作用域不同。使用静态外部变量,有利于隔离错误,有利于模块化程序设计。

D、全局变量的缺省存储方式是外部存储方式。
程序没有见到变量的存储类别定义,实际上采用变量的缺省存储方式。对局部变量采用auto方式,对全局变量采用extern方式。这也是至今为止,程序中没有见到auto、extern等的原因。

总结

至此,我们对变量的存储类别及数据类型进行了全面讨论,在此作个小结。

1.变量定义的一般形式

存储类别数据类型变量表;

2.变量定义的作用

①规定了变量的取值范围。

②规定了变量进行的运行操作。

③规定了变量的作用域。

④规定了变量的存储方式。

⑤规定了变量占用的存储空间。

3.局部变量和全局变量

从作用域角度将变量分为局部变量和全局变量。它们采取的存储类别如下:

局部变量:

①自动变量,即动态局部变量(离开函数,值就消失)。

②静态局部变量(离开函数,值仍保留)。

③寄存器变量(离开函数,值就消失)。

④形式参数可以定义为自动变量或寄存器变量。

全局变量:

①静态外部变量(只限本程序文件使用)。

②外部变量(即非静态的外部变量,允许其它程序文件引用)。

4.动态存储和静态存储

从变量存在时间可将变量存储分为动态存储和静态存储。静态存储是在整个程序运行时都存在,而动态存储则是在调用函数时临时分配存储单元。

动态存储:

①自动变量(函数内有效)。

②寄存器变量(函数内有效)。

③形式参数。

静态存储:

①静态局部变量(函数内有效)。

②静态外部变量(本程序文件内有效)。

③外部变量(整个程序可引用)。

5.静态存储区和动态存储区

从变量值存放的位置可将变量存储区分为静态存储区和动态存储区:

内存中静态存储区:

①静态局部变量。

②静态外部变量。

③外部变量(可被同一程序其它文件引用)。

内存中动态存储区:自动变量和形式参数。

CPU中的寄存器:寄存器变量。

2、const

1、修饰局部变量

const int n=5;
int const n=5;
  • 1
  • 2

这两种意义是相同的,都是表示变量n的值不能被改变了,需要值得注意的是,用const修饰变量时,一定要给变量初始化,否则之后就不能进行赋值了。
接下来看看const用于修饰常量静态字符串,例如:
const char* str=“fdsafdsa”;
如果没有const的修饰,我们可能会在后面有意无意的写str[4]=’x’这样的语句,这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。有了const,这个错误就能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译期被发现。

2、
常量指针
常量指针是指指针指向的内容是常量,可以有以下两种方式:
const int * n;
int const * n;
【注】:①常量指针 是指 不能通过这个指针变量改变变量的值,但是还是可以通过其他的引用来改变变量的值
int a=5;
const int* n=&a;
a=6;
②常量指针,不能通过这个指针改变变量的值,但这并不意味着指针本身不能改变,常量指针可以指向其他的地址。
int a=5;
int b=6;
const int* n=&a;
n=&b;

指针常量 是指指针本身是个常量,不能指向其他的地址。
int *const n;
需要注意的是,指针常量指向的地址不能改变但是地址中保存的数值是可以改变的,可以通过其他指向该地址的指针来修改
int a=5;
int p=&a;
int
const n=&a;
*p=8;
区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量。

3、指向常量的常指针
const int* const p;

这种情况下,指针指向的位置不能改变,也不能通过这个指针改变变量的值,但是依然可以通过其他普通指针改变指向位置的值。

3、修饰函数的参数:
根据常量指针与指针常量,const修饰函数的参数,也是分为三种情况,
①防止修改指针指向的内容
void StringCopy(char *strDestination, const char *strSource);
如果函数体内的语句试图改动 strSource 的内容,编译器将指出错误。
②防止修改指针指向的地址
void swap ( int * const p1 , int * const p2 )
指针p1 p2指向的地址都不能改变
③二者结合

4、修饰函数的返回值
如果如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。

例:const char * GetString(void);
错误赋值:char *str = GetString();
正确赋值:const char *str = GetString();

5、修饰全局变量
全局变量的作用域是整个文件,我们应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现,如果一定要用全局变量,我们应该尽量的使用const修饰符进行修饰,这样防止不必要的人为修改,使用的方法与局部变量是相同的。

2、指针 在程序编写中的作用

指针的基本用法

  • 什么是指针?

    在C语言中,内存单元的地址称为指针。

    专门用来存放地址的变量,称为指针变量

    在不影响理解的情况中,有时对地址、指针和指针变量不区分,通称指针

  • 使用指针的好处?

    1. 使程序简洁、紧凑、高效
    2. 有效地表示复杂的数据结构
    3. 动态分配内存
    4. 得到多于一个的函数返回值
  • 什么是地址和变量?

    在计算机内存中,每一个字节单元,都有一个编号,称为地址

  • 指针变量的说明

    1. 一般形式如下:
      <存储类型> <数据类型> * <指针变量名> ;

      例如,char *pName ;

    2. 指针的 存储类型 是指针变量本身的存储类型。

    3. 指针说明时指定的 数据类型 不是指针变量本身的数据类型,而是指针目标的数据类型。简称为指针的数据类型。

    4. 指针在说明的同时, 也可以被赋予初值,称为指针的初始化

      一般形式是:
      <存储类型> <数据类型> *<指针变量名> = <地址量> ;
      例如:int a, *pa=&a;

    5. 指针指向的内存区域中的数据称为指针的目标

    6. 如果它指向的区域是程序中的一个变量的内存空间, 则这个变量称为指针的目标变量。 简称为指针的目标。

    7. 注意程序中的px、*px 和 &px 三种表示方法的不同意义。int *px,则:

      • px — 指针变量, 它的内容是地址量
      • *px — 指针所指向的对象, 它的内容是数据
      • &px — 指针变量占用的存储区域的地址,是个常量
    8. 指针的赋值运算指的是通过赋值运算符向指针变量送一个地址值

    9. 向一个指针变量赋值时,送的值必须是地址常量或指针变量,不能是普通的整数(除了赋零以外)

    10. 指针赋值运算常见的有以下几种形式:

      //把一个普通变量的地址赋给一个具有相同数据类型的指针 
      double  x=15,  *px;
      px=&x;
      //把一个已有地址值的指针变量赋给具有相同数据类型的另一个指针变量.例如:
      float  a, *px, *py; 
      px = &a;
      py = px;
      //把一个数组的地址赋给具有相同数据类型的指针。例如:
      int  a[20],  *pa;
      pa = a;   //等价 a = &a[0]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

指针的运算

  • 指针运算是以指针变量所存放的 地址量 作为运算量而进行的运算

  • 指针运算的实质就是地址的计算

  • 指针运算的种类是有限的,它只能进行赋值运算、算术运算和关系运算

  • 算术运算表:

  • 指针加减一个n的运算:px + n px - n

  • 注意:

    1. 不同数据类型的两个指针实行加减整数运算是无意义的
    2. px+n表示的实际位置的地址量是:
      (px) + sizeof(px的类型) * n
    3. px-n表示的实际位置的地址量是:
      (px) - sizeof(px的类型) * n
  • 两指针相减运算

    1. px-py 运算的结果是两指针指向的地址位置之间相隔数据的个数。因此,两指针相减不是两指针持有的地址值相减的结果。
    2. 两指针相减的结果值不是地址量,而是一个整数值,表示两指针之间相隔数据的个数。
  • 指针加一、减一运算

    px++, ++px, px–, --px

  • 指针的关系运算符

  • 指针关系运算

    1. 两指针之间的关系运算表示它们指向的 地址位置 之间的关系。指向地址大的指针大于指向地址小的指针。
    2. 指针与一般整数变量之间的关系运算没有意义。但可以和零进行等于或不等于的关系运算,判断指针是否为空。
  • 注意指针的当前值

    指针变量可以指到数组后的内存单元

  • 指针运算的本质是什么?

    指针运算是以指针变量所存放的 地址量 作为运算量而进行的运算。因此,指针运算的实质就是地址的计算。

  • 指针加1,移动多少字节?

    (px) + sizeof(px的类型) * n

指针与数组

  • 在C语言中,数组的 指针 是指 数组 在内存中的 起始地址,数组元素的 地址 是指 数组元素 在内存中的起始地址

  • 一维数组的数组名为一维数组的指针(起始地址)
    例如
    int x[8];
    因此,x为x数组的起始地址

  • 设指针变量px的地址值等于数组指针x(即指针变量px指向数组的首元数)

    则:x[i] 、* (px+i)、* (x+i) 和px[i]具有完全相同的功能:访问数组第i+1个数组元素!

    int a[10], * p; p=a;

  • 注意:

    指针变量和数组在访问数组中元素时,一定条件下其使用方法具有相同的形式,因为指针变量和数组名都是地址量,但指针变量和数组的指针(或叫数组名)在本质上不同,指针变量是地址变量,而数组的指针是地址常量

指针与二维数组

编程实现,使用一级指针遍历二维数组

#include <stdio.h>

int main(int argc, char *argv[])
{
        int a[3][2] = {{1, 6}, {9, 12}, {61, 12}};
        int * p, i, n;

        n = sizeof(a) / sizeof(int);

        p = a[0]; //&a[0][0];
        printf("%p %p\n", p, p+1);
        printf("%p %p\n", a, a+1);

        for (i = 0; i < n; i++)
                printf("%d ", *(p+i));
        puts("");


        return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 二维数组的元素连续存储,按行优先存储。

  • 可把二维数组看作由多个一维数组组成。

  • 二维数组名代表数组的起始地址,数组名加1,是移动一行元素。因此,二维数组名常被称为行地址

  • 行指针(数组指针)
    存储行地址的指针变量,叫做行指针变量。形式如下:
    <存储类型> <数据类型> (*<指针变量名>)[表达式] ;
    例如,int a【2】[3]; int §[3];

    方括号中的常量表达式表示:指针加1,移动几个数据。
    当用行指针操作二维数组时,表达式一般写成1行的元素个数,即列数。

编程实现,使用行指针表示二维数组int a【3】[2]的元素a【1】[1]

#include <stdio.h>

int main(int argc, char *argv[])
{
        int a[3][2] = {{1, 6}, {9, 12}, {61, 12}};
        int (*p)[2], i, j;

        p = a;

        printf("%p %p\n", a, a+1);
        printf("%p %p\n", p, p+1);

        //printf("%d, %d, %d, %d\n", a[1][1], p[1][1], *(*(a + 1)+1), *(*(p + 1) + 1));
        for (i = 0; i < 3; i++) {
                for (j = 0; j < 2; j++)
                        printf("%d, %d, %d, %d ", a[i][j], p[i][j], *(*(a + i)+j), *(*(p + i) + j));
                puts("");
        }


        return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

字符指针与字符串

  • C语言通过使用字符数组来处理字符串

  • 通常,我们把char数据类型的指针变量称为字符指针变量。

  • 字符指针变量与字符数组有着密切关系,它也被用来处理字符串。

  • 初始化字符指针是把内存中字符串的首地址赋予指针,并不是把该字符串复制到指针中
    char str[] = “Hello World”;
    char *p = str;

  • 在C编程中,当一个字符指针指向一个字符串常量时,不能修改指针指向的对象的值
    char * p = “Hello World”;
    *p = ‘h’; // 错误, 字符串常量不能修改

    不利用任何字符串函数,编程实现字符串连接函数的功能。

    #include <stdio.h>
    
    int main()
    {
        char ch[100] = "welcome";
        char *p = "hello world!", *q;
        int i = 0;
        
        q= p;
        while(*(ch+i) != '\0')
            i++;
        while(*p != '\0'){
            *(ch+i) = *p;
            i++;
            p++;
        }
        *(ch+i) = *p;
        
        p = q;
        puts(ch);
        puts(p);
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

指针数组

  • 所谓指针数组是指由若干个具有相同存储类型和数据类型的指针变量构成的集合

  • 指针数组的一般说明形式:
    <存储类型> <数据类型> *<指针数组名>[<大小>];

  • 指针数组名表示该指针数组的起始地址

  • 声明一个指针数组:
    double * pa[2] ,a【2 】[3];

  • 把一维数组a[0]和a[1]的首地址分别赋予指针变量数组的数组元数pa[0]和pa[1]:
    pa[0]=a[0] ; // 等价pa[0] = &a【0】[0];
    pa[1]=a[1]; // 等价pa[1] = &a【1】[0];
    此时pa[0]指向了二维数组a[0]的第一个元素a【0】[0], 而pa[1]指向了二维数组a[1]的第一个元素a【1】[0]。

  • 赋值:
    int main()
    { int b【2】[3];
    int *pb[2];
    pb[0]=b[0];
    pb[1]=b[1];
    ……
    }

  • 初始化:
    int main()
    { int b【2】[3];
    int *pb[ ]={b[0],b[1]};
    ……
    }

  • 指针数组处理二维数组示意图

    image-20221209110503048

编程:利用指针数组处理一个二维数组,要求求出二维数组所有元素的和。

#include <stdio.h>

int main(int argc, char *argv[])
{
        int a[2][3] = {{1, 4, 6}, {12, 9, 7}};//a----a[0]   a[1]
        int * p[2] = {a[0], a[1]};
        int i, j, sum = 0;

        int *  * q;

        q = p;//&p[0]

        printf("total:%d\n", sizeof(p));

        for (i = 0; i < 2; i++) {
                for (j = 0; j < 3; j++) 
                        sum += *(p[i]+j);
        }

        printf("sum=%d\n", sum);

        return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

多级指针

  • 多级指针的定义

    1. 把一个指向指针变量的指针变量,称为多级指针变量
    2. 对于指向处理数据的指针变量称为一级指针变量,简称一级指针
    3. 而把指向一级指针变量的指针变量称为二级指针变量,简称二级指针
    4. 二级指针变量的说明形式如下
      <存储类型> <数据类型> ** <指针名> ;
  • 多级指针的运算

    1. 指针变量加1,是向地址大的方向移动一个目标数据。类似的道理,多级指针运算也是以其目标变量为单位进行偏移。
    2. 比如,int * * p;p+1移动一个int *变量所占的内存空间。再比如int * * * p,p+1移动一个int **所占的内存空间。
  • 多级指针和指针数组

    1. 指针数组也可以用另外一个指针来处理。

    2. 例如:有一个一维字符指针数组ps[5],
      char *ps[5]= { “Beijing city”,
      ……
      “London city” } ;

    3. 定义另一个指针变量pps,并且把指针数组的首地址赋予指针pps
      char *ps[5]={……}; char ** pps = ps;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/算法设计者/article/detail/63272
推荐阅读
相关标签
  

闽ICP备14008679号