zoukankan      html  css  js  c++  java
  • c语言提高学习笔记——02-c提高01day

    在学习c语言提高总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。

    02-c提高01day

    目录:

    1、数据类型概念
    2、typedef
    3、void数据类型
    4、sizeof操作符
    5、数据类型总结与扩展
    6、变量
    7、程序的内存分区模型
    8、总结

    C语言函数模板

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<stdlib.h>
     5 
     6 
     7 int main(){
     8 
     9     
    10     system("pause");
    11     return EXIT_SUCCESS;
    12 }

    1、数据类型概念

    什么是数据类型?为什么需要数据类型?

    数据类型是为了更好进行内存的管理,让编译器能确定分配多少内存。
    我们现实生活中,狗是狗,鸟是鸟等等,每一种事物都有自己的类型,那么程序中使用数据类型也是来源于生活。

    当我们给狗分配内存的时候,也就相当于给狗建造狗窝,给鸟分配内存的时候,也就是给鸟建造一个鸟窝,我们可以给他们各自建造一个别墅,但是会造成内存的浪费,不能很好的利用内存空间。

    我们在想,如果给鸟分配内存,只需要鸟窝大小的空间就够了,如果给狗分配内存,那么也只需要狗窝大小的内存,而不是给鸟和狗都分配一座别墅,造成内存的浪费。

    当我们定义一个变量,a=10,编译器如何分配内存?计算机只是一个机器,它怎么知道用多少内存可以放得下10?

    所以说,数据类型非常重要,它可以告诉编译器分配多少内存可以放得下我们的数据。

    狗窝里面是狗,鸟窝里面是鸟,如果没有数据类型,你怎么知道冰箱里放得是一头大象!

    数据类型基本概念:
    >类型是对数据的抽象;
    >类型相同的数据具有相同的表示形式、存储格式以及相关操作;
    >程序中所有的数据都必定属于某种数据类型;
    >数据类型可以理解为创建变量的模具:固定大小内存的别名;


    2、typedef

    (1)为变量设置别名

     1 typedef unsigned int u32;
     2 typedef struct _PERSON{
     3     char name[64];
     4     int age;
     5 }Person;
     6 
     7 void test(){
     8     u32 val;//相当于unsigned int val;
     9     Person person;//相当于struct PERSON person;
    10 }

    (2)同时定义多个指针类型,不会出错

    1 typedef char* PCHAR
    2 
    3 PCHAR p1, p2;

    (3)定义一个类型,换平台后只用更改typedef后的变量

    1  typedef long long mytype_t;
    2 
    3 //有利于程序移植性

    3、void数据类型

    (1)void不能直接定义变量,因为编译器不知道分批额多少内存给变量

      void a;//报错!

    注意:当定义一个变量,编译器必须要知道分配多少内存(struct Person p一直递归,不知道分配多少内存)

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<stdlib.h>
     5 
     6 struct Person
     7 {
     8     char name[64];
     9     int age;
    10     struct Person p;   
    11 };
    12 
    13 void test01()
    14 {
    15     struct Person p;
    16 }
    17 
    18 int main()
    19 {
    20     test01();
    system("pause");
    return EXIT_SUCCESS;
    21 }

    (2)void使用情况:对函数返回的限定;对函数参数的限定

    (3)void* 无类型指针,所有类型指针的祖宗(void *可以指向任何类型的数据)

      void* p = NULL;

      任何类型的指针都可以不经过强制转换,转换成void* 类型

      int* pInt = NULL;

      char* pChar = (char *)pInt; //需要强制转换

      void* pVoid = pInt;//不需要强制转换

    (4)void* 主要用于数据结构的封装

    4、sizeof操作符

    sizeof是c语言中的一个操作符,类似于++、--等等。sizeof 能够告诉我们编译器为某一特定数据或者某一个类型的数据在内存中分配空间时分配的大小,大小以字节为单位。

    基本语法:
    sizeof(变量);
    sizeof 变量;
    sizeof(类型);

    sizeof 注意点:
    >sizeof返回的占用空间大小是为这个变量开辟的大小,而不只是它用到的空间。和现今住房的建筑面积和实用面积的概念差不多。所以对结构体用的时候,大多情况下就得考虑字节对齐的问题了;
    >sizeof返回的数据结果类型是unsigned int
    >要注意数组名和指针变量的区别。通常情况下,我们总觉得数组名和指针变量差不多,但是在用sizeof的时候差别很大,对数组名用sizeof返回的是整个数组的大小,而对指针变量进行操作的时候返回的则是指针变量本身所占得空间,在32位机的条件下一般都是4。而且当数组名作为函数参数时,在函数内部,形参也就是个指针,所以不再返回数组的大小;

     1 //1.sizeof基本用法
     2 void test01(){
     3 
     4 int a=10 5 
     6 printf("1en:%d
    ",sizeof(a));
     7 printf("len:%d
    ",sizeof(int));
     8 printf("len:%d
    ",sizeof a);
     9 }
    10 
    11 //2.sizeof结果类型
    12 void test02(){
    13 
    14 unsigned int a=1015 
    16 if(a-11<0){ //无符号的数a与有符号的数大多数编译器运算结果仍然是无符号的!
    17 printf("结果小于0
    ");
    18 }
    19 else{
    20 printf("结果大于0
    ");
    21 }
    22 int b=523 
    24 if(sizeof(b)-10<0){
    25 printf("结果小于0
    ");
    26 }
    27 else{
    28 printf("结果大于0
    ");
    29 }
    30 
    31 //3.sizeof 碰到数组
    32 void TestArray(int arr[]){
    33 printf("TestArray arr size:%d
    ",sizeof(arr));
    34 }
    35 void test03(){
    36 int arr[]={10,20,30,4050};
    37 printf("array size:%d
    ",sizeof(arr));//20
    38 //数组名在某些情况下等价于指针
    39 int* pArr=arr;
    40 printf("arr[2]:%d
    ",pArr[2]);
    41 printf("array size:%d
    ",sizeof(pArr));
    42 //数组做函数函数参数,将退化为指针,在函数内部不再返回数组大小
    43 TestArray(arr);//4
    44 }

     C语言结构体所占用的字节数如何计算

    参考——https://zhidao.baidu.com/question/183477298.html

     1 //格式一
     2 struct tagPhone
     3 {
     4     char A;
     5     int B;
     6     short C;
     7 }Phone;
     8 //格式二
     9 struct tagPhone
    10 {
    11     char A;
    12     short C;
    13     int B;
    14 }Phone2;
    15 //格式三
    16 struct tagPhone3
    17 {
    18     char A;
    19     char B[2];
    20     char c[4j;
    21 }Phone3;

    扩展:

    我们都知道,char类型占用个字节,int型占用个字节,short类型占用个字节,long占用8个,double占用bai个;

      那么我们可能会犯一个错误就是直接1+4+2=7,该结构体占用7个字节。这是错的。
      以下我们简单分析下:
      计算结构体大小时需要考虑其内存布局,结构体在内存中存放是按单元存放的,每个单元多大取决于结构体中最大基本类型的大小。

      对格式一:


          以int型占用个来作为倍数,因为A占用一个字节后,B放不下,所以开辟新的单元,然后开辟新的单元放C,所以格式一占用的字节数为:3*4=12;

    同理对于格式二,


      A后面还有三个字节,足够C存放,所以C根着A后面存放,然后开辟新单元存放B数据。所以格式二占用的内存字节为2*4=8

      对于格式三:


      上面结构计算大小,sizeof(Phone3) = 1+2 +4 = 7, 其大小为结构体中个字段大小之和,这也是最节省空间的一种写法。
    总结:
      第一种写法,空间浪费严重,sizeof 计算大小与预期不一致,但是保持了每个字段的数据类型。这也是最常见的漫不经心的写法,一般人很容易这样写;

      第三种写法,最节省空间的写法,也是使用 sizeof 求大小与预期一样的写法,但是全部使用字节类型,丢失了字段本生的数据类型,不方便使用;

      第二种写法,介于第一种和第三种写法之间,其空间上比较紧凑,同时又保持了结构体中字段的数据类型。

      只要了解是这些写法的差异性,可以视情况选用。


     5、数据类型总结与扩展
    (1)数据类型本质是固定内存大小的别名;是个模具,c语言规定:通过数据类型定义变量。
    (2)数据类型大小计算(sizeof)
    (3)可以给已存在的数据类型起别名typedef
    (4)数据类型封装概念(void 万能类型)

    6、变量

    int i= 10; //把10放到i所代表的内存中

    变量就是一块内存(本质!)
    既能读又能写的内存对象,称为变量;若一旦初始化后不能修改的对象则称为常量。
    变量定义形式:类型标识符,标识符,…,标识符

    变量名的本质
    >变量名的本质:一段连续内存空间的别名
    >程序通过变量来申请和命名内存空间int a=0;
    >通过变量名访问内存空间;
    >不是向变量名读写数据,而是向变量所代表的内存空间中读写数据;

    修改变量的两种方式:

     1 void test(){
     2 int a=10 3 
     4     //1.直接修改
     5     a=20 6     printf("直接修改,a:%d
    ",a);
     7 
     8     //2.间接修改
     9     int*p=&a;
    10     *p=3011     printf("间接修改,a:%d
    ",a);
    12 }

    字节偏移

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<stdlib.h>
     5 
     6 struct Person
     7 {
     8     char a;
     9     int b;
    10     char c;
    11     int d;
    12 }
    13 
    14 void test02()
    15 {
    16     struct Person p = {'a', 100, 'b', 200};
    17     printf("p.d:%d
    ",p.d);
    18 
    19     p.d = 1000;//修改d
    20 
    21 } 
    22 int main()
    23 {
    24     test02();
    25     printf()
    26     system("pause");
    27     return EXIT_SUCCESS;
    28 }    

    用字节偏移修改d

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<stdlib.h>
     5 
     6 struct Person
     7 {
     8     char a;
     9     int b;
    10     char c;
    11     int d;
    12 }
    13 
    14 void test02()
    15 {
    16     struct Person p = {'a', 100, 'b', 200};
    17     printf("p.d:%d
    ",p.d);
    18 
    19     p.d = 1000;//修改d
    20     //(char *)&p +12结构体所占大小按最大的(int)计算,所以p.d的起始地址为:先计算起始地址&p,然后转成(char *)1个字节,然后加上12个字节
    21     //printf("(char *)&p +12:%d
    ",(char *)&p +12);//测试p.d的起始地址
    22     //printf("&p.d:%d
    ",&p.d);//测试p.d的起始地址
    23     printf("(int *)((char *)&p +12):%d
    ",(int *)((char *)&p +12));//找到d
    24 } 
    25 int main()
    26 {
    27     test02();
    28     printf()
    29     system("pause");
    30     return EXIT_SUCCESS;
    31 }    

    7、程序的内存分区模型

    流程说明

    1、操作系统把物理硬盘代码load到内存

    2、操作系统把c代码分成四个区

    3、操作系统找到main函数入口执行

    各区元素分析

    全局区和全局静态区的区别?
    int a;实际是extern int a;在当前项目所有文件中都可见。
    static int a;是全局静态变量,在当前文件中都可见。

    常量区:字符串常量如“hello world”和const修饰的全局变量

    常量区特点:数据一旦初始化,不能修改!只读的内存!


    (1)内存分区
    1)运行之前

    我们要想执行我们编写的c程序,那么第一步需要对这个程序进行编译。
    1)预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
    2)编译:检查语法,将预处理后文件编译生成汇编文件
    3)汇编:将汇编文件生成目标文件(二进制文件)
    4)链接:将目标文件链接为可执行程序

    当我们编译完成生成可执行文件之后,我们通过在linux下 size命令可以查看一个可执行二进制文件基本情况:

    通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3个部分(有些人直接把data和bss合起来叫做静态区或全局区)。


    ·代码区
    存放CPU执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。
    ·全局初始化数据区/静态数据区(data段)
    该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)
    ·未初始化数据区(又叫bss区)
    存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空(NULL)。
    总体来讲说,程序源代码被编译之后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据域段和.bss段属于程序数据。

    那为什么把程序的指令和程序数据分开呢?

    ■程序被load到内存中之后,可以将数据和代码分别映射到两个内存区域。由于数据区域对进程来说是可读可写的,而指令区域对程序来讲说是只读的,所以分区之后呢,可以将程序指令区域和数据区域分别设置成可读可写或只读。这样可以防止程序的指令有意或者无意被修改;

    ■当系统中运行着多个同样的程序的时候,这些程序执行的指令都是一样的,所以只需要内存中保存一份程序的指令就可以了,只是每一个程序运行中数据不一样而已,这样可以节省大量的内存。比如说之前的Windows Internet Explorer7.0运行起来之后,它需要占用112844KB的内存,它的私有部分数据有大概15944KB,也就是说有96900KB空间是共享的,如果程序中运行了几百个这样的进程,可以想象共享的方法可以节省大量的内存。

    2)运行之后
    程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,操作系统把物理硬盘程序 load(加载)到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区

    此处说的分配内存,只是分配地址,而不是真正分配内存。真正分配内存在程序运行时。

    ·代码区(text segment)
    加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。

    ·未初始化数据区(BSS)
    加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。

    ·全局初始化数据区/静态数据区(data segment)
    加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。


    ·栈区(stack)
    栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

    程序自动分配释放!栈的大小固定!


    ·堆区(heap)
    堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

    堆的大小不固定,主要看机器的有多大!

    (2)栈区

    由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<stdlib.h>
     5 
     6 //1.栈区的内存自动申请自动释放,不需要程序手动管理
     7 int* myFunc()
     8 {
     9     int a = 10;
    10     return &a;//不要返回局部变量的地址
    11 }
    12 
    13 void test01()
    14 {
    15     //我们并不关心a的值是多少,因为局部变量a的内存已经被回收
    16     int* p = myFunc();
    17     printf("*p = %d
    ", *p);
    18 }
    19 
    20 char *getString()
    21 {
    22     char str[] = "hello world";
    23     return str;
    24 }
    25 
    26 void test02()
    27 {
    28     char *s = NULL;
    29     s = getString();
    30     printf("s = %s
    ", s);//返回报错!!!
    31 }
    32 
    33 int main(){
    34 
    35     //test01();
    36     test02();
    37     
    38     system("pause");
    39     return EXIT_SUCCESS;
    40 }

    分析过程

     1 #char* func(){
     2 char p[]="hello world!"//在栈区存储乱码
     3 printf("%s
    ",p);
     4 return p;
     5 }
     6 void test(){
     7 char*p=NULL;
     8 p=func();
     9 printf("%s
    ",p);
    10 }

    (3)堆区
    由编程人员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间。使用malloc或者new进行堆的申请。

     1 #define _CRT_SECURE_NO_WARNINGS
     2 #include<stdio.h>
     3 #include<string.h>
     4 #include<stdlib.h>
     5 
     6 //1.堆的内存成员手动申请,手动释放
     7 int* getSpace()
     8 {
     9     int* p = malloc(sizeof(int)* 5);//p这个变量在栈上,4个字节大小,p这个变量投放的地址在堆上
    10     if(NULL == p) //NULL写前,这样写不容易出错
    11     {
    12         return NULL;
    13     }
    14     int j = 0;
    15     for(int i = 0; i < 5; ++i)
    16     {
    17         p[i] = 100 + i;
    18     }
    19     return p;
    20 }
    21 
    22 void test01()
    23 {
    24     int* ret = getSpace();
    25     for(int i = 0; i < 5; ++i)
    26     {
    27         printf("%d", ret[i]);
    28     }
    29     //用完堆内存,一定要释放
    30     free(ret);
    31     ret = NULL;
    32 }
    33 
    34 //2.
    35 //定义变量的时候一定要初始化,很多Bug产生都是由于没有初始化造成的。
    36 void allocateSpace(char* p)
    37 {
    38     p = malloc(100);
    39     memset(p,0,100); //初始化,用0填充
    40     strcpy(p, "hello world!");//把常量区的"hello world"拷贝至p所指向的堆空间
    41 }
    42 
    43 void test02()
    44 {
    45     char* p = NULL;
    46     allocateSpace(p);
    47     printf("p = %s
    ",p);
    48 }
    49 
    50 int main(){
    51 
    52     //test01();
    53     test02();
    54     
    55     system("pause");
    56     return EXIT_SUCCESS;
    57 }

    test01执行成功,test02执行不成功,分析原因?

    执行char* p = NULL;在栈区开辟内存,存放p,值为NULL;执行allocateSpace(p);调用void allocateSpace(char* p)子函数,在栈区分配内存,值为NULL(把test02中的p赋值给allocateSpace的p),p = malloc(100);在堆区分配100个字节,并把地址0x888传回,memset(p,0,100);初始化,用0填充;strcpy(p, "hello world!");把常量区的"hello world"拷贝至p所指向的堆空间,执行完allocateSpace(p);后allocateSpace的p释放栈空间,所以堆的地址找不到了。

    怎么办呢?解决如下:

     1 //定义变量的时候一定要初始化,很多Bug产生都是由于没有初始化造成的。
     2 void allocateSpace02(char** p) //用二级指针在于说明p指向的是char*类型的
     3 {
     4     char* temp = malloc(100);
     5     memset(temp,0,100); //初始化,用0填充
     6     strcpy(temp, "hello world!");//把常量区的"hello world"拷贝至p所指向的堆空间
     7     
     8     *p = temp;//使用p不用管p是几级指针,直接往里面放值就可以
     9 }
    10 
    11 void test03()
    12 {
    13     char* p = NULL;
    14     allocateSpace(&p);
    15     printf("p = %s
    ",p);
    16 }

    执行char* p = NULL;在栈区开辟内存,存放p,值为NULL(地址0x001);执行allocateSpace(&p);调用void allocateSpace(char* p)子函数,在栈区分配内存,值为0x001(把test03中的p取地址给allocateSpace的p),char* temp = malloc(100);在堆区分配100个字节,,并把地址0x003传回给栈区的tempchar* temp在栈区分配空间),memset(temp,0,100);初始化,用0填充;strcpy(temp, "hello world!");把常量区的"hello world"拷贝至temp所指向的堆空间,*p = temp;*p为allocateSpace(p)中p的值为0x001,赋值为temp0x003。执行完allocateSpace(p);后allocateSpace的p和temp释放栈空间,所以堆的地址0x003保存下来了(在test03的p的值为0x003)

    堆分配内存API:——未讲,第三天讲

     1 #include<stdlib.h>
     2 void *calloc(size_t nmemb,size_t size);
     3 功能:
     4     在内存动态存储区中分配nmemb块长度为size字节的连续区域。calloc自动将分配的内存置0。
     5 参数:
     6     nmemb:所需内存单元数量
     7     size:每个内存单元的大小(单位:字节)
     8 返回值:
     9     成功:分配空间的起始地址
    10     失败:NULL
     1 #include<stdlib.h>
     2 void *realloc(void *ptr,size_t size);
     3 功能:
     4     重新分配用malloc或者calloc函数在堆中分配内存空间的大小。realloc不会自动清理增加的内存,需要手动清理,如果指定的地址后面有连续的空间,那么就会在已有地址基础上增加内存,如果指定的地址后面没有空间,那么realloc会重新分配新的连续内存,把旧内存的值拷贝到新内存,同时释放旧内存。
     5 参数:
     6    ptr:为之前用malloc或者calloc分配的内存地址,如果此参数等于NULL,那么和realloc与malloc功能一致
     7    size:为重新分配内存的大小,单位:字节
     8 返回值:
     9     成功:新分配的堆内存地址
    10     失败:NULL

    示例代码

     1 void test01(){
     2   int* pl=calloc(10,sizeof(int));//开辟了40个字节,并赋值为0
     3   if(pl==NULL){
     4     return 5   }
     6   for(int i=0;i<10;i++){
     7     pl[i]=i+1 8   }
     9   for(inti=0;i<10;i++){
    10     printf("%d",pl[i]);
    11   }
    12   printf("
    ");
    13   free(pl);
    14 }
    15 
    16 void test02(){
    17   int* pl=calloc(10,sizeof(int));
    18   if(pl==NULL){
    19     return20   }
    21   for(int i=0;i<10;i++){
    22     pl[i]=i+123   }
    24 
    25   int* p2=realloc(pl,15* sizeof(int));
    26   if(p2==NULL){
    27     return28   }
    29   printf("%d
    ",pl);
    30   printf("%d
    ",p2);
    31 
    32   //打印
    33   for(int i=0;i<15;i++){
    34     printf("%d",p2[i]);
    35   }
    36   printf("
    ");
    37 
    38   //重新赋值
    39   for(int i=0;i<15;i++){
    40     p2[i]=i+1;
    41   }
    42 
    43   //再次打印
    44   for(int i=0;i<15;i++){
    45     printf("%d",p2[i]);
    46   printf("
    ");
    47   free(p2);
    48 }

    (4)全局/静态区
    全局静态区内的变量在编译阶段已经分配好内存空间并初始化。这块内存在程序运行期间一直存在,它主要存储全局变量、静态变量和常量。

    注意:
    (1)这里不区分初始化和未初始化的数据区,是因为静态存储区内的变量若不显示初始化,则编译器会自动以默认的方式进行初始化,即静态存储区内不存在未初始化的变量。
    (2)全局静态存储区内的常量分为常变量和字符串常量,一经初始化,不可修改。静态存储内的常变量是全局变量,与局部常变量不同,区别在于局部常变量存放于栈,实际可间接通过指针或者引用进行修改,而全局常变量存放于静态常量区则不可以间接修改。
    (3)字符串常量存储在全局/静态存储区的常量区。

     示例代码

    1 int v1=10//全局/静态区,默认外部链接
    2 const int v2=20//常量,一旦初始化,不可修改
    3 static int v3=20;//全局/静态区,内部链接
    4 char*p1;//全局/静态区,编译器默认初始化为NULL
    5 //那么全局static int和全局int变量有什么区别?
    6 void test(){
    7 static int v4=20//全局/静态区
    8 }

    1)全局静态变量和局部静态变量都存储在静态区,在程序执行期间都是合法有效的。

    2)局部静态变量符号的可见范围仅限于当前函数内部,全局静态变量可见范围从定义到文件结尾。

    内部链接和外部链接的区别?(那么全局static int和全局int变量有什么区别?)

    1)如果变量是内部链接的话,那么此变量只能在当前文件内访问;

    2)如果变量是外部链接的话,那么此变量可以被其他文件使用。

    编译时只编译.c和.cpp文件!头文件不编译,每个.c文件,我们叫做一个编译单元。

    如test.c中有int g_a = 100,main.c中要用g_a;怎么办?

    如果未定义g_a或在test.c中为static int g_a = 100(static只能在当前文件内访问),即链接器找不到,会报错

    头文件不要放定义(定义需要大括号),只能放声明(没有大括号)!

    (5)常量区 字符串常量 全局const变量

    1)const全局和局部变量区别?

    const全局变量在常量区,一旦初始化,(直接或间接都)不能修改

     1 1)const全局和局部变量区别?
     2 const全局变量在常量区,(直接或间接都)不能修改
     3 const int g_c = 100;
     4 
     5 void test()
     6 {
     7     //直接修改不行
     8     //g_c = 200;
     9     
    10     //间接修改也不行
    11     int* p = (int*) &g_c;
    12     *p = 200;
    13 }

    const局部变量在常量区,直接不能修改,间接可以修改

     1 1)const局部变量
     2 const局部变量在栈区,直接不能修改,间接可以修改
     3 void test()
     4 {
     5     const int g_d = 100; //放在栈上!
     6     //直接修改不行
     7     //g_d = 200;
     8     
     9     //间接修改可以
    10     int* p = (int*) &g_d;
    11     *p = 200;
    12     printf("g_d = %d
    ", g_d);
    13 }

    2)字符串常量

    加深理解

     1 char* func(){
     2 static char arr[]="hello world!";//在静态区存储可读可写
     3 arr[2]='c' 4 char* p="hello world!"//全局/静态区-字符串常量区
     5 //p[2]='c';//只读,不可修改
     6 printf("%d
    ",arr);
     7 printf("%d
    ",p);
     8 printf("%s
    ",arr);
     9 return arr;
    10 }
    11 
    12 void test(){
    13 char* p=func();
    14 printf("ss
    ",p);
    15 }

     问题: C中常量字符串和字符数组的区别?

    参看:https://blog.csdn.net/zhongyili_sohu/article/details/8084188

    1) 常量字符串
    在代码里直接出现的”abcdef”这种字符串,在程序执行的时候,系统会将它们放在常量区,所谓常量区就是一直存在的,只读的,不可更改的数据区域,并且一个字符串只会有一份。假设你在程序里有两行代码
    char* p1 = “agcd”;
    char* p2 = “agcd”;

    无论你这两个行代码隔了多远,如果你想知道p1和p2所指向的字符串在内存中是不是同一个,那答案是肯定的,p1和p2的值完全一样。”agcd”这是一个存在于内存中的常量字符串,它从程序一开始就在那里,一直到程序结束都不会改变。在内存中,”agcd”是以如下方式存储的


    它的最后肯定有一个字符串结束标志’’。这种字符串的名字叫“以空字符为结束标志的字符串”。
    char* p1 = “agcd”;

    如果你这时候想改变第一个字符的值,用p[0] =’b’,系统会报一个错,常量字符不能更改。(这里为什么指针可以当数组用,下面再解释)。

    2)字符数组

    如果你定义一个char a[10],那么系统会“只分配”10个char这么长的内存区域,一个char是一个字节,那么系统会分配十个字节的内存空间,并且将这一片连续的内存空间的首地址赋值给a。也就是说“数组名的值是数组所在内存区域的首地址”换句话说“数组名是一个指针,指向数组第一个值的地址”。

    如果你定义一个char a[] = “abcdefg”;这句代码就复杂点了。定义一个数组,数组长度未知,那么系统会根据等号后面的值来“初始化”这个数组,等号后面是什么?前面说过,它是一个常量字符串。在内存中占8个字节,7个字符加上一个结束标志。这时候在内存中就有两个”abcdefg”的字符串了,一个是常量区域的,另一个是根据前者复制了一份的。这句代码的意思就是复制一个常量区域的字符串,将复制后的字符串的首字母的地址赋值给a。

    也就是说,最后a所指向的内存区域,已经不是常量里的”abcdefg”了,这里为什么要复制一份呢?原因是因为常量是不允许更改的,而数组一般都意味着需要修改,所以就复制了一份数据,放在非常量区域,就可以更改了。
    下面的测试程序可证明上述结论:

     1 #include<stdio.h>
     2 int main()
     3 {
     4     char* p1 = "hello world!";
     5     char* p2 = "hello world!";
     6     char p[] = "hello world!";
     7 
     8     printf("%x
    ", p1);
     9     printf("%x
    ", p2);
    10     printf("%x
    ", p);
    11 }

     1 #include<stdio.h> 
    2
    int main() 3 { 4 //"hello world!" char* 类型字符串 5 char* p = "hello world!"; 6 printf("p = %s ", p); 7 //printf("%d ", &"hello world!"); 8 //printf("%d ", p); 9 p[0] = 'A'; 10   printf("p = %s ", p);
        return 0;
    11 }

    同一个程序,在bcc编译器可以更改,在mingw不可更改?

    字符串常量是否可修改?字符串常量优化:
    ANSI C中规定:修改字符串常量,结果是未定义的。
    ANSI C并没有规定编译器的实现者对字符串的处理,例如:
    1.有些编译器可修改字符串常量,有些编译器则不可修改字符串常量。
    2.有些编译器把多个相同的字符串常量看成一个(这种优化可能出现在字符串常量中,节省空间),有些则不进行此优化。如果进行优化,则可能导致修改一个字符串常量导致另外的字符串常量也发生变化,结果不可知。
    所以尽量不要去修改字符串常量!

    C99标准:
    char *p="abc";defines p with type"pointer to char"and initializes it to point to an object with type “array of char”with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to modify the contents of the array,the behavior is undefined.

    字符串常量地址是否相同?
    tc2.0,同文件字符串常量地址不同。
    Vs2013,字符串常量地址同文件和不同文件都相同。
    Devc++、QT同文件相同,不同文件不同。

    8、总结
    在理解C/C++内存分区时,常会碰到如下术语:数据区,堆,栈,静态区,常量区,全局区,字符串常量区,文字常量区,代码区等等,初学者被搞得云里雾里。在这里,尝试捋清楚以上分区的关系。

    数据区包括:堆,栈,全局/静态存储区。
    全局/静态存储区包括:常量区,全局区、静态区。
    常量区包括:字符串常量区、常变量区(即全局const变量)。
    代码区:存放程序编译后的二进制代码,不可寻址区。
    可以说,C/C++内存分区其实只有两个,即代码区和数据区。

    参考:

    1)讲义:豆丁网:https://www.docin.com/p-2159552288.html
    道客巴巴:https://www.doc88.com/p-6951788232280.html

    2) C语言结构体所占用的字节数如何计算?——https://zhidao.baidu.com/question/183477298.html

    3)C中常量字符串和字符数组的区别?——https://blog.csdn.net/zhongyili_sohu/article/details/8084188

    在学习c语言提高总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。

  • 相关阅读:
    PHP atanh() 函数
    PHP atan2() 函数
    PHP atan() 函数
    #检查磁盘使用率超过90%,并且后台进程没有rman在跑,就运行 /data/script/del_dg_arch.sh 脚本清理归档
    [学习笔记]自适应辛普森积分
    C# 如何写 DEBUG 输出
    C# 如何写出一个不能被其他程序集继承的抽象类
    C# 如何写出一个不能被其他程序集继承的抽象类
    C# 如何引用 WshShell 类
    C# 如何引用 WshShell 类
  • 原文地址:https://www.cnblogs.com/Alliswell-WP/p/C_ImprovedLearning_01.html
Copyright © 2011-2022 走看看