【20180823】【C/C++基础知识】指针与结构,二重指针,指针数组和字符指针数组,动态内存空间的分配和释放

结构与指针

  • 定义:

/* 定义 */
struct info     // 定义了一个info结构,里面有两个成员num和name[5]
{
    short num;    // 两个成员的类型分别为short型和char型
    char name[5];
}
struct info
myinfo1,myinfo2,*p_info1,*p_info2;  // 定义两个结构变量和两个指针变量
p_info1=&myinfo1;
p_info2=&myinfo2;   // 对指针变量进行初始化,让其分别指向结构变量的地址
p_info1->num=2;  // 修改p_info1中的num值为2(用指针来访问对象所指的成员,用的是成员指针访问符->)
strcpy((*p_info1).name,”good”);  // 将”good”拷贝到p_info1中的name[5]中(用结构成员点的访问方式:用*p_info1访问myinfo1,再访问myinfo1中的name)
*p_info2=*p_info1;  // 把*p_info1中的全部赋给*p_info2(结构变量可以整体相互赋值)

请问:p_info2->num和p_info2->name[3]的值分别是多少?

答:p_info2->num=2;p_info2->name[3]=d;

 

请问:如果上面修改为p_info2=p_info1有什么影响?

答:这样p_info2不再指向myinfo2,而是和p_info1一样,也指向myinfo1!此时myinfo2中的数据是未初始化的,是乱的,因此会出错!

 

*p_info2=*p_info1和p_info2=p_info1的区别在于:

(1) 前者是把指针变量1指向的地址中的内容给指针变量2指向的地址;

(2) 后者是修改了指针变量本身中的内容,即把指针变量指向的地址修改了。

 

  • 存取结构成员的方式:

(1) 通过结构变量名;

(2) 通过指向结构的指针和间接运算符(*);

(3) 通过指向结构的指针和指向成员运算符(->)。

 

  • 等价引用:

myinfo.num

(*p_info).num

p_info->num

 

注意:操作符->只能使用在结构指针指向结构变量的情况下!

 

二重指针(指向指针的指针)

  • 定义:

如果在一个指针变量中存放的是另一个变量的指针的地址,称该指针为指向指针的指针,即二重指针。

例如: int v=120, *q=&v, **p=&q;  // q指针指向变量v,p二重指针指向指针q。即:**p=*(*p)=*q=v;  // 访问v可用v, *q **p!

例1:类型不匹配的问题。

main()
{
    int x,*q,**p;
    x=10;
    q=&x;
    p=&x;  // 会出错!(类型不匹配:x的地址是变量的地址,不是地址的地址)
    printf(“%d”,**p);
}

 

例2:指针数组和二重指针。

char *pc[]={“aaa”,”sss”,”ddd”};
char **ppc; 
ppc=pc;   // pc是数组首地址,定义时再加一个*表示它“相当于”二重指针(即指针数组的第一个元素也是一个指针)

请问:ppc和pc可以相互赋值吗?

答:可以!类型匹配可以相互赋值!(pc是一个指针数组,pc是数组名,数组名本身就是地址,再加一个*就表示是一个二重指针)

心得:指针数组相当于二重指针!数组名“相当于”指针:因为数组名和指针一样都是表示地址,而“相当于并不真的是”表示数组名是常量。(参考:点击此处查看

 

例3:用指针型指针输出数组元素。

/* 用指针型指针输出数组元素 */
/* 二重指针和指针数组结合使用 */
#include<stdio.h>
#include<stdlib.h>
int main()
{
    int a[5]={1,3,5,7,9};
    int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};  // 定义指针数组
    int **p,i;    // 定义二重指针
    p=num;   // 二重指针指向数组的首地址
    for(i=0;i<5;i++)
    { 
        printf("%d\t",**p);  // 注意**p才是数据,*p还是地址!
        p++;
    }
    system("pause");
    return 0;
}

字符指针(操作字符串)

char *pch1=buffer1;  // buffer1是一个字符数组,字符指针pch1指向该字符数组

printf(“%s”,pch1);   // 输出字符串(输出字符指针,就是输出指针指向的字符串)

printf(“%c”,*pch1);   // 输出字符指针指向的目标对象,就是输出指针指向的单个字符

区分:

printf—pch1(输出字符指针)

printf—*pch1(输出字符指针指向的对象)

 

char *p1=”good”;

char *p2=”good”;

printf(“%d”,p1-p2);   // 输出值为0

解释:

因为字符串常量存储在数据区的const常量区,同一个字符串常量在内存中存在同一块内存,因此p1和p2都指向同一个”good”。

 

动态内存分配与释放——用指针按需分配、释放空间

  • C语言中变量的内存分配方式:

(1)从静态存储区分配:全局变量和静态变量。

(2)栈(stack):函数参数值、局部变量等、以及执行函数调用时,系统在栈上为函数内的局部变量及形参分配内存,函数之行结束时,自动释放这些内存。

(3)堆(heap):程序运行期间,用动态内存分配函数来申请的内存都是从堆上分配的,动态内存的生存期是由程序员自己决定。

 

  • 动态内存分配函数:

头文件 #include<stdlib.h>

(1) void* malloc(unsigned int size);  // 向系统申请大小为size的内存块,把首地址返回,若申请不成功,则返回NULL。

malloc函数只有一个参数,就是要分配多大空间的字节数(需要自己计算)

int *p=(int *)malloc(10*sizeof(int));   // 分配10个整数空间

 

(2) void* calloc(unsigned int num,unsigned int size); // 向系统申请大小为num*size的内存块,把首地址返回,若申请不成功,则返回NULL。

calloc函数有两个参数,第一个参数是分配的元素的个数,第二个参数是每个元素占据的字节数,size这个参数是由sizeof()函数获得所需字节数,即size=sizeof()。因此不需要自己计算。

int *p=(int *)calloc(10,sizeof(int));   // 分配10个整数空间

 

注意:使用内存空间前,一定要判断是否分配成功!

if(p==NULL)   // 使用分配的地址前,一定要判断是否分配成功!
{
    printf(“malloc error\n”);
    return;
}

 

分配1个整数(4个字节空间)

p=(int *)malloc(1*sizeof(int));   // 因为定义时的函数类型是void(空类型),因此通常需要用强制数据类型转换(int *)转换成所需的类型!
p=(int *)calloc(1,sizeof(int));   // 强制转换成整型空间!

 

注意:void*不等于NULL!

NULL是空指针(无地址),在C中,NULL通常取0,常表示指针不指向任何地方。

void*表示指针指向的目标对象无数据类型,但它是有地址的(无数据类型)!

void*类型的指针可以指向任意类型的变量,通常强转(Type*)为其他类型。

 

例4:分配10个整数空间,并把1~10存储在这个空间内。

/* 分配10个整数空间,并把1~10存储在这个空间内 */
#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p=(int*)malloc(10*sizeof(int));  // 先分配一块连续的整型空间,p指向空间的首地址
    if(p==NULL)        // 判断是否分配成功!
    {
        printf("内存分配失败");
        return;
    }
    for(int i=0;i<10;++i)   // 用p指针一个一个地存储数据
        *(p+i)=i+1;
    system("pause");
    return 0;

}

 

  • 动态内存释放函数:void free(void* p);

(1)释放由malloc或calloc申请的内存块;

(2)p是指向该内存的指针;

(3)free之后,系统标记p指针指向的这块内存没有被占用,可被重新分配;

(4)free之后,强制令p=NULL,避免后面误用这块内存。

 

例5:动态内存的分配与释放。

/* 动态内存分配与释放 */
#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p=NULL,n,i;  // 定义整型指针p
    double aver,sum;
    printf("How many students?");
    scanf("%d",&n);   // 获取要分配的个数
    p=(int *)malloc(n*sizeof(int));  // p指针指向空间的首地址
    if (p==NULL)
    {
        printf("No enough memory!\n");
        exit(1);     // 异常退出
    }
    for(i=0;i<n;++i)
    {
        scanf("%d",&p[i]);  // 读数据:读到p指针所指空间中
        sum+=p[i];   // 求和
    }
    aver=sum/n;   // 计算平均值
    printf("aver=%.1f\n",aver);
    free(p);   // 使用完毕释放空间
    system("pause");
    return 0;
}

 

例6:内存空间使用完毕,一定要释放!(malloc/calloc和free一定要成对使用!)

/* 内存空间不再使用时,一定要释放! */
#include<stdio.h>
#include<stdlib.h>
#define N 100000
int main()
{
    int *p,i=0;
    while(1)
    {
        p=(int *)malloc(N*sizeof(int));
        printf("%p ",p);
        *p=i++;
        if(p=NULL)
            break;
    }
    system("pause");
    return 0;
}

 

例7:指针在使用前一定要进行初始化!

/* 使用前要进行初始化 */
#include<stdio.h>
#include<stdlib.h>
int main()
{
    int *p,i=0;
    p=(int *)malloc(sizeof(int));
    if(p)
        printf("%d",*p);
    system("pause");
    return 0;
}

 

例8:指针的越界问题。

/* 指针越界 */
#include<stdio.h>
#include<stdlib.h>
#define N 10
int main()
{
    int *p,i=0;
    p=(int *)malloc(N*sizeof(int));  // 分配了10个整数空间
    if(p)
    {
        for(i=0;i<=N;++i,++p)  // 输出11个(越界),但系统不会提示越界错误!
        {
            *p=i+1;
            printf("%d ",*p);
        }
    }
    system("pause");
    return 0;
}

 

例9:释放内存后仍继续使用内存空间,造成错误!

/* 释放内存后仍继续使用 */
#include<stdio.h>
#include<stdlib.h>
#define N 10
int main()
{
    int *p,i=0;
    p=(int *)malloc(N*sizeof(int));
    if(p)
    {
        for(i=0;i<N;++i,++p) 
        {
            *p=i+1;
            printf("%d ",*p);
        }
    }
    free(p);
    *p=3;   // 运行程序会报错!因为使用了不属于我们的内存空间。因此free之后一定记得令p=NULL,使用前再判断是否p==NULL!
    system("pause");
    return 0;
}
  • 常见的内存错误:

(1)内存分配不成功,就进行使用;(用if判断)

(2)内存分配成功,但使用前没有进行初始化;(定义时令p=NULL)

(3)内存分配成功并初始化,但发生越界使用;(仔细检查)

(4)申请内存后没有及时释放;(注意配对使用的问题)

(5)释放内存后仍然继续使用。(注意free之后跟上p=NULL)

 

  • 关于内存分配编程的建议原则:

(1)需要的时候才使用malloc(或calloc);

(2)malloc(或calloc)和free要配对使用,malloc(或calloc)在函数入口,free在函数出口;

(3)使用malloc(或calloc)时要检查函数返回值(用if判断是否是NULL);

(4)使用free函数之后,将指针设置为NULL;

(5)不要把局部变量的地址作为函数返回值返回。(函数结束后,局部变量和形参空间会被释放掉,因此如果把局部变量的地址作为函数返回值返回就相当于使用了free之后的内存空间一样,使用了不属于我们的空间!)

例10:找错误并修改。

/* 找出其中错误 */
#include<stdio.h>
#include<stdlib.h>
#define N 10
int main()
{
    char data[]="There are some mistakes in the program";
    char *point;
    char array[30];
    int i,length;
    length=0;
    while(data[length]!='\0')
        length++;
    for(i=0;i<length;i++,point++)
        *point=data[i];   // point指针没有初始化就进行使用,会造成问题!
                         // array数组长度30小于data存储的字符串长度
    array=point;   // array是数组名,是常量,不能被修改,不能被赋值!
    printf("%s\n",array);
    system("pause");
    return 0;
}
/* 作如下修改 */
#include<stdio.h>
#include<stdlib.h>
#define N 10
int main()
{
    char data[]="There are some mistakes in the program";
    char *point;
    char array[100];  // 定义一个数组
    int i,length;
    length=0;
    while(data[length]!='\0')   // 求字符串的长度
        length++;
    point=array;   // 给指针赋首地址
    for(i=0;i<length;i++,point++)  // 输出字符串
        *point=data[i];  
    printf("%s\n",array);
    system("pause");
    return 0;
}

例11:使用指针,合并字符串s1和s2。

思路:

输入:两个字符串

输出:合并后的结果

算法分析:

两个字符指针分别指向两个字符串s1和s2。

指针p循环访问s1的字符,直到s1字符串尾部。

指针q依次访问字符串s2,直到s2访问到尾部,每访问一个字符,作如下操作:*p++=*q++(*q赋给*p,然后两个指针都做自增操作,一个语句完成了两个操作)。

/* 使用指针,合并字符串s1和s2 */
#include<stdio.h>
#include<stdlib.h>
int main()
{
    char s1[50],s2[20];
    scanf_s("%s%s",s1,20,s2,20);  // 输入两个字符串
    char *p=s1,*q=s2;
    while(*p!='\0')     // 计算s1的长度
        p++;      // 第一个while循环结束,p此时指向字符串结束符
    while(*q!='\0')  // 将s2合并到s1的后面
        *p++=*q++;   // 将*q中的内容赋给*p,并且都自增
    p='\0';  // 结束后,将字符串结束符拷贝到合并后的字符串最后面
    printf("%s\n",s1);
    system("pause");
    return 0;
}

疑问:为什么后面会有“烫烫烫……”???

例12:输入两个字符串string1和string2,检查在string1中是否包含有string2。如果有,则输出string2在string1中的起始位置;如果没有,则输出“NO”;如果string2在string1中多次出现,则输出在string1中出现的次数以及每次出现的起始位置。

算法分析:

(1)需要对string1和string2进行循环,但不一定同时进行;

(2)string1开始循环后,判断string2的首字母是否与string1循环到的字母相同,若相同则两字符串同时进行循环,并记录开始的位置,然后判断string2中后面的字母是否和string1中的相同,不同则停止,相同则继续,直到string2循环完成。

(3)当前位置减去string2的长度就是string2的起始位置;

(4)string2可能多次出现,那么需要一个数组来保存多次出现的位置。

/* 检查在string1中是否包含有string2 */
#include<stdio.h>
#include<stdlib.h>
void main()  // 访问类型为int时,可以省略int。如果是Dev C++,一定是int,VS下可以是void!
            // 改成void类型之后,最后不再加return返回值!
{
    char string1[100],string2[10];  // 定义两个字符数组,长度分别不超过100和10
    char *p,*q;     // 定义指针遍历字符串
    int locat[10];  // 定义数组保存出现的位置(假定2出现在1中的次数不超过10次)
    int j,len2,i=0,posit=0;  // len2表示第二个字符串的长度
    printf("请输入字符串1:\n");
    gets_s(string1);  // VS 2010之后,都采用加下划线的!和scanf_s一样!
    printf("请输入字符串2:\n");
    gets_s(string2);
    q=string2;  // q指向string2的首地址
    for(j=0;*q!='\0';j++,q++)  // 求第二个字符串的长度
        len2=j+1;
    p=string1;  // 指针赋首地址
    q=string2;
    do   // 循环进行判断
    {
        if(*p!=*q)  // 字符不相同的情况
        {
            p++;
            posit++;
        }  // 第一个字符相同,继续对后面字符进行判断
        else
        {
            while((*q!='\0')&&(*q==*p))
            {
                q++;
                p++;
                posit++;
            }
            if(*q=='\0')  // 第二个字符串循环结束
            {
                locat[i]=posit-len2;  // 当前位置posit减去第二个字符串的长度就是在第一个字符串的起始位置???
                i++;
            }
        }
        q=string2;  // 结束一次循环则重新赋值为string2的首地址,进行多次重复循环
    }while(*p!='\0');  // 一定要有分号!
    if(i<1)
        printf("NO");
    else
    {
        printf("字符串1:%s\n字符串2:%s\n",string1,string2);
        printf("出现%d次,起始位置分别是:",i);
        for(j=0;j<i;j++)  // 输出位置
            printf("%d ",locat[j]);
        printf("\n");
    }
    system("pause");
    //return 0;
}

getchar()函数:读取一个字符。

gets_s()函数:读取一个字符串,可无限读取,以回车结束,换行符会被丢弃,并在末尾加’\0’结束符

请问:输入字符串时,scanf_s和gets_s的区别?

答:scanf_s输入字符串时,如果中间有空格,我们会把它当做两个字符串输入(scanf_s会认为输入空格表示字符串结束)。如果想让它是一个字符串,推荐用gets_s函数(只有输入回车换行才认为字符串结束)。

疑问:为什么当前位置减去string2的长度就是起始位置?

猜你喜欢

转载自blog.csdn.net/weixin_40583722/article/details/81983867