C语言深度剖析之查漏补缺

C语言深度剖析之查漏补缺,进行了总结。转载请注明链接,有问题请及时联系博主:Alliswell_WP

今日总结:

一、const修饰指针的三种效果,C语言const修饰指针详解

二、char *str 和 char str[] 的区别

三、malloc在堆上动态分配内存

四、结构体中字符数组赋值字符串问题

 

一、const修饰指针的三种效果,C语言const修饰指针详解

问题描述:const修饰的指针变量?

前面讲过,当一个变量用 const 修饰后就不允许改变它的值了。那么如果在定义指针变量的时候用 const 修饰会怎样?同样必须要在定义的时候进行初始化。比如:

    int  a;
    int  *p = &a;

当用 const 进行修饰时,根据 const 位置的不同有三种效果。原则是:修饰谁,谁的内容就不可变,其他的都可变。这三种情况在面试的时候几乎是必考的,在实际编程中也是经常使用的,所以初学者一定要掌握。

1) const int*p=&a;

同样 const 和 int 可以互换位置,二者是等价的。我们以放在最前面时进行描述。

当把 const 放最前面的时候,它修饰的就是 *p,那么 *p 就不可变。*p 表示的是指针变量 p 所指向的内存单元里面的内容,此时这个内容不可变。其他的都可变,如 p 中存放的是指向的内存单元的地址,这个地址可变,即 p 的指向可变。但指向谁,谁的内容就不可变。

这种用法常见于定义函数的形参。前面学习 printf 和 scanf,以及后面将要学习的很多函数,它们的原型中很多参数都是用 const 修饰的,这样做的好处是安全!我们通过参数传递数据时,就把数据暴露了。而大多数情况下只是想使用传过来的数据,并不想改变它的值,但往往由于编程人员个人水平的原因会不小心改变它的值。这时我们在形参中用 const 把传过来的数据定义成只读的,这样就更安全了。这也是 const 最有用之处。

所以如果你不想改变某个参数传过来的值,那么定义函数时就最好用 const 修饰这个参数,否则就不要用 const 修饰了。

2) int*const p=&a;
此时 const 修饰的是 p,所以 p 中存放的内存单元的地址不可变,而内存单元中的内容可变。即 p 的指向不可变,p 所指向的内存单元的内容可变。

3) const int*const p=&a;
此时 *p 和 p 都被修饰了,那么 p 中存放的内存单元的地址和内存单元中的内容都不可变。

综上所述,使用 const 可以保护用指针访问内存时由指针导致的被访问内存空间中数据的误更改。因为指针是直接访问内存的,没有拷贝,而有些时候使用指针访问内存时并不是要改变里面的值,而只是要使用里面的值,所以一旦不小心误操作把里面的数据改了就糟糕了。

但是这里需要注意的是,上面第 1 种情况中,虽然在 *p 前加上 const 可以禁止指针变量 p 修改变量 a 中的值,但是它只能“禁止指针变量 p 修改”。也就是说,它只能保证在使用指针变量 p 时,p 不能修改 a 中的值。但是我并没有说 const 可以保护 a 禁止一切的修改,其他指向 a 的没有用 const 修饰的指针变量照样可以修改 a 的值,而且变量 a 自己也可以修改自己的值。下面写一个程序看一下:

    # include 
    int main(void)
    {   
        int a = 10;
        const int *p = &a;
        int * q = &a;
        *q = 20;
        printf("a = %d\n", a);
        a = 30;
        printf("a = %d\n", a);
        //*p = 30;  //这么写就是错的
        return 0;
    }

输出结果是:
a = 20
a = 30

可见,只有用 const 修饰过的指针变量 p 不能修改 a 中的内容,而没有用 const 修饰过的指针变量 q 照样可以修改 a 中的内容,而且 a 自己也可以重新给自己赋值。

#include 
#include 
#include 

int main(void)
{
    //const修饰一个变量为只读
    const int a = 10;
    //a = 100; //err

    //指针变量, 指针指向的内存, 2个不同概念
    char buf[] = "aklgjdlsgjlkds";

    //从左往右看,跳过类型,看修饰哪个字符
    //如果是*, 说明指针指向的内存不能改变
    //如果是指针变量,说明指针的指向不能改变,指针的值不能修改
    const char *p = buf;
    // 等价于上面 char const *p1 = buf;
    //p[1] = '2'; //err
    p = "agdlsjaglkdsajgl"; //ok

    char * const p2 = buf;
    p2[1] = '3';
    //p2 = "salkjgldsjaglk"; //err

    //p3为只读,指向不能变,指向的内存也不能变
    const char * const p3 = buf;

    return 0;
}

 

扩展:const修饰结构体指针形参变量

//结构体类型的定义
struct stu
{
    char name[50];
    int age;
};

void fun1(struct stu * const p)
{
    //p = NULL; //err
    p->age = 10; //ok
}

//void fun2(struct stu const*  p)
void fun2(const struct stu *  p)
{
    p = NULL; //ok
    //p->age = 10; //err
}

void fun3(const struct stu * const p)
{
    //p = NULL; //err
    //p->age = 10; //err
}

 

二、char *str 和 char str[] 的区别

问题描述:为什么主函数中不能用char *str = "abcdefg";?

 

相同点:
都是定义一个字符串

不同点:

1)含义上的区别
char str[] 是定义一个字符串数组,数组对应的是一块内存区域,而char *str 是定义一个指向字符串的指针,即指向一块内存区域。
数组的内存大小和其地址在作用域里是固定不变的,只有它存储的内容可以改变;而指针却不同,它指向的内存区域的大小随时可以改变,而且当指针指向常量字符串时,它指向的内容是不可以被修改的,否则在运行时会报错。
看一个例子:

#include
#include
int main()
{
    char str1[] = "Hello";
    char str2[] = "World";
    strcpy(str1,str2);
    printf("%s\n",str1);
    return 0;
}

上面这段代码是可以运行的,因为数组的内容是可以被修改的。
再看下面这段代码:

#include
#include
int main()
{
    char *str1 = "Hello";
    char *str2 = "World";
    strcpy(str1,str2);
    printf("%s\n",str1);
    return 0;
}

这段代码在编译时不会报错,但是在运行时结果会出错,原因在于这段代码企图修改 str1 的内容,由于 str1 和 str2 是指向常量字符串的指针,其内容是不可被修改的,所以在运行时会出错。

》扩展:

下面介绍一下char str[]="hello"与char *str="hello"的区别
char str[]="hello";

第一个表达式表示的是在动态变量区中开辟一个能连续放6个字符的数组,数组名称是str.而赋值运算符右边是一个字符串常量,这个字符串常量是存放在常量区的,这个表达式的意思就是将“hello”这个字符串常量拷贝到刚才开辟的数组中。C语言规定,表达式如果是一个数组名,则代表的意思是该数组的起始地址,如果这个数组在一个函数中定义,如果以数组名返回时,因为数组在函数中定义,是个局部变量,函数返回之后,这个数组所占用的空间就被释放掉了,数组也被破坏掉了,因此返回的数组名也就没有意义,不能被其主调函数使用了。

如果我们这样写:

char *foo()
{
char str[]="hello";
return str;
}

在编译的时候就会出现警告:函数返回局部变量的地址

再来看下char *str="hello"

这个表达式的意思是在动态变量区中开辟一个存放指针的存储单元,指针变量名是str,"hello"同样也是一个字符串常量,存储在常量区,在程序的运行过程中一直存在。把字符串“hello”的地址值拷贝到刚才的存储单元中,即指针变量str的初值是字符串“hello”的地址。这时如果char *str="hello"定义在一个函数中并且以return str返回,因为str是一个变量名,返回的仅仅是str的值,所以在其他函数中可以使用该值,照样能够访问到“hello”这个字符串。

#include
#include
char* foo()
{
    char *str="hello";
    return str;
}
int main()
{
    char *q;
    q=foo();
    printf("q=%s\n",q);
}

2)计算内存的区别
用 sizeof 可以直接计算出数组的字节数;而指针的大小只与编译器所处的平台有关,在32位平台上(例如X86)指针占4个字节,而在64位平台上(例如X64)指针占8个字节。
另外在进行参数传递时,数组会退化成为同类型的指针。

练习:字符串反转模型

#include 
#include 
#include 

int inverse(char *p)
{
    if (p == NULL)
    {
        return -1;
    }
    char *str = p;
    int begin = 0;
    int end = strlen(str) - 1;
    char tmp;

    while (begin < end)
    {
        //交换元素
        tmp = str[begin];
        str[begin] = str[end];
        str[end] = tmp;

        begin++;  //往右移动位置
        end--;        //往左移动位置
    }

    return 0;
}

int main(void)
{
    //char *str = "abcdefg"; //文件常量区,内容不允许修改
    char str[] = "abcdef";

    int ret = inverse(str);
    if (ret != 0)
    {
        return ret;
    }

    printf("str ========== %s\n", str);
    return 0;
}

 

三、malloc在堆上动态分配内存

问题描述:malloc在子函数中分配的空间为什么不能通过值传递返回给主函数?

1)通过返回值分配内存

int *getspace()
{   
    //malloc函数的返回值是分配区域的起始地址
    //函数原型为void *malloc(unsigned int size);
    //其作用是在内存的动态存储区中分配一个长度为size的连续空间
    int *p = (int *)malloc(sizeof(int) * 5);//在堆上动态分配内存
    if (NULL == p)
    {
        return NULL;
    }
    //往分配的内存里面写值
    for (int i = 0; i < 5; ++i)//++i 比i++ 的效率高
    {
    //只要是连续的空间都能使用下标的方式访问
        p[i] = 100 + i;
    }
    return p;

}
void  test()
{
    int *ret = getspace();
    for (int i = 0; i < 5; ++i)
    {
        cout << ret[i] << endl;
    }

    free(ret);//释放堆上动态分配的内存
    ret = NULL;//避免出现野指针

}

2)通过参数传递指针分配内存

(1)错误写法
这种写法程式运行会死掉,原因是allocateSpace(ret)函数执行后,参数p指向malloc函数分配的内存0x88(举例),allocateSpace执行完后p被释放0x88内存泄漏,ret指向为空

void allocateSpace(char *p)
{
    p= (char *)malloc(100);
    memset(p, 0, 100);
    strcpy(p,"hello world");
}
void  test1()
{
    char *ret = NULL;
    allocateSpace(ret);
    cout << "ret=" << ret;
}

内存空间模型分析:

C语言深度剖析之查漏补缺_第1张图片

(2)正确的写法
将&ret传入allocateSpace

void allocateSpace(char **p)
{
    char *temp = (char *)malloc(100);
    //memset() 函数可新申请的内存进行初始化工作
    //将指针变量 p 所指向的前 100 字节的内存单元用一个“整数” 0 替换
    memset(temp, 0, 100);
    strcpy(temp,"hello world");
    *p = temp;//*p=ret,所以实际ret指向malloc分配的内存空间
}
void  test12()
{
    char *ret = NULL;
    allocateSpace(&ret);
    cout << "ret=" << ret;//结果ret=hello world
}

 

四、结构体中字符数组赋值字符串问题

问题描述:结构体中的字符数组为什么不能通过s1.c="abc";赋值?

今天在看结构体变量时发现一个问题:
问题代码如下

int main() {
    struct student{
        char c[20];
    }s1;
    s1.c="china";
    printf("%s",s1.c);
    return 0;
}

输出结果为空,不知道是为什么?

经过网上的答案尽是如此:

C语言只有在定义字符数组的时候才能用“=”来初始化变量,其它情况下是不能直接用“=”来为字符数组赋值的,要为字符数组赋值可以用string.h头文件中的strcpy函数来完成。
例如:

char a[10] = "123"; /*正确,在定义的时候初始化*/
char a[10];
a = "123"; /*错误,不能用“=”直接为字符数组赋值*/
strcpy(a, "123"); /*正确,使用strcpy函数复制字符串*/

所以要对game[0][0].cpart赋值应该用strcpy(game[0][0].cpart, "123");才对。注意要使用strcpy函数要用#include 包含string.h头文件。

给C语言结构体中的char数组赋值有两种方式:

(1)在声明结构体变量时赋值:

//#include "stdafx.h"//If the vc++6.0, with this line.
#include "stdio.h"
struct stu{
    int x;
    char name[10];
};

int main(void){
    struct stu s={
     8,"123"};//这样初始化
    printf("%d %s\n",s.x,s.name);
    return 0;
}

(2)向数组直接拷贝字符串:

//#include "stdafx.h"//If the vc++6.0, with this line.
#include "stdio.h"
#include "string.h"
struct stu{
    int x;
    char name[10];
};

int main(void){
    struct stu s;
    strcpy(s.name,"abcd");//向name拷贝字符串
    s.x=128;
    printf("%d %s\n",s.x,s.name);
    return 0;
}

至于为什么不能直接给字符数组赋值字符串呢?网上各大神说的是,因为在初始化字符数组时,数组的内存地址已经确定,不能再做修改。

想要直接给数组赋值字符串除了在初始化时赋值,还可以通过两个函数,void *memcpy(void*dest, const void *src, size_t n);strcpy(str1,str2)。

原因:
(1)首先,其实是忘记了C++的基础问题,C++里面只要涉及char都不能直接通过“=”来赋值,因为C++里面没有提供这个功能。必须使用str开头的函数。只有后来的CString重载来“-,+,=”之后才可以怎么方便的使用。CString str;str=“sasa";.
(2)其次,stu.name="hello";//报错为什么?name[10]是一个10大小的内存空间,而”hello“是一个常量匿名字符串的地址,现在你应该明白了.
你把一个地址赋值给了数组,也就是说现在char[20]="0x51825182"之类的,get it !

总结:结构体字符数组的赋值必须用字符串函数,不能直接进行赋值。

 

 

C语言深度剖析之查漏补缺,进行了总结。转载请注明链接,有问题请及时联系博主:Alliswell_WP

你可能感兴趣的:(C语言深度剖析之查漏补缺)