C编程基础day10

char * p[]和char **p只有当做函数参数时是等价的,其他情况不等价。

一、不等价的情况:

char *p2[]={"abc","edf", "ghi"}; //这里p2是数组

char * *p2={"abc","edf", "ghi"}; //err,因为指针p2是char * *型, p2指向的是char *型。这里p2是指针。

char *temp;

char **p = &temp; //OK

char *str[]={“hello”,"jack"};//str的首元素是char *型 ,首元素地址是str或者&str[0].

char **p =str; //char **指向 char*

char **p =&str[0];

void fun(str)等价void fun(char**p)

二、等价的情况:

如果char * str[]作为函数参数则可以改为 char * *str类型,因为作为函数参数无论写成什么样,编译器都把他作为指针来用。

下面三者完全等价,编译器都是当做char **p来处理

void fun (char **p);

void fun (char *p[]);

void fun (char *p[100]);

相近的例子,可以帮助立即,以下三者完全等价, 我们把int换成char *类型

void fun (int *p);

void fun (int p[]);

void fun (int p[100]); // 因为形参中的数组压根不是数组,而是指针。

作用域:作用的范围。

1、代码作用域,代码在那个{}内。

2、函数作用域

3、文件作用域

一、普通局部变量:

1、在{}内定义的变量就是局部变量,

2、只有当执行到这个语句时系统才给这个普通局部变量变量分配空间, 

3、当离开{}这个非static局部变量自动释放。 

4、局部变量的作用域为当前{},离开{}该局部变量无法使用。

5、不同的{}中,变量的名字可以一样。

6、for(int i=0; i<10; i++) //例子中i的作用域为for循环,离开for循环后,i值就不能用了。

7、{}内的局部变量加不加auto关键字是等价的。普通局部变量也叫自动变量,所谓自动是指自动分配空间自动释放。auto int a=100;  (C++中的auto和C语言的auto关键字意义不太一样)

8、就近原则

int a=10;

if(1)

{

   int a=11; //就近原则

   printf("a=%d\n",a); //因为就近原则,打印为11

}

printf("a=%d\n",a);//打印为10, if中定义的a只能在if的{}中使用,离开后if的{}里边的a就释放了。

9,普通局部变量不初始化,他的值为随机值。

二、static局部变量: static int a=10;

1、在{}内定义的static变量就是static局部变量,

2、static局部变量在编译阶段就已经分配空间,函数没调用前,它已经存在。

3、当离开{}这个static局部变量不释放。 只有程序结束,static变量才释放。 

4、static局部变量的作用域为当前{},离开{}该static局部变量无法使用。

5、不同的{}中,static局部变量的名字可以一样。

6、如果static局部变量不初始化,那么他的值为0。

7、static局部变量初始化语句只会执行一次,但是可以被赋值多次。

8、static局部变量只能使用常量初始化。

int a=10;

static int b=a ;//err,错误因为i是变量。 static局部变量b在编译之前就存在了,当时变量a还不存在,所以无法使用变量对其初始化。

普通局部变量于static局部变量区别主要是 内存分配时间不同、内存释放时间不同、 初始化不同(static局部变量初始化语句只执行1次,且使用常量初始化,不初始化时其值为0)

三、普通全局变量(外部链接)

1、在{}外面(函数外面)定义的变量就是全局变量。

2、只要定义了全局变量,任何地方都能使用这个全局变量。

3、如果使用全局变量时,在前面找不到此全局变量的定义,需要声明才能使用。加extern表示是声明。声明时extern int a;后边的变量不能赋值,否则会出错。 因为只声明,不定义时候无法给变量赋值。

4、全局变量不初始化时,默认赋初值为0.

5、声明只是针对全部变量,不是针对局部变量。

6、全局变量只能定义一次,可以声明一次。

7、全局变量在编译阶段已经分配空间,函数没执行前就有空间,只有在整个程序结束才自动释放。

C全局变量缺陷:

1、C语言全局变量缺陷: 以下几个语句在函数{}外部作为全局变量编译通过, 作为{}函数内部的局部变量话编译不通过。

int a;

int a;

int a=10;//这句是定义,其他事声明

int a;

int a;

以下4句有3句式是声明,有1句是定义。(不知道具体哪句是定义)

int b;

int b;

int b;

int b;

只声明,不定义时候无法给变量赋值。以下几句编译出错。

extern int c;

extern int c;

extern int c;

extern int c;

int main()

{

    c=10; //err,因为只声明,不定义时候无法给变量赋值。

}

2、如果定义一个全局变量,没有赋值(初始化),无法确定是定义还是声明。

3、如果定义一个全局变量,同时赋值(初始化),这个肯定是定义。

全局变量建议写法。

1、定义全局变量时候建议写法:定义同时初始化。

int a=10;

2、声明全局变量时候建议写法:声明前边加extern关键字。一看extern就知道是声明。

extern int a;

全局变量的分文件:

1、调用其他文件中的函数和全局,要声明。 其他文件函数声明时候加不加extern都行。 其他文件全局变量声明时候要加extern.

extern void fun();

extern int a ;

extern int b;

编译指令如下:

gcc main.c text.c

普通全局变量和经常被调用的函数最好写在头文件中, 因为这样只用在头文件中声明一次就行了。 否则的话多个文件调用话就需要在多个文件中分别声明。若是在.h中定义的话,多个文件调用.h文件会造成全局变量多次定义。 .h头文件防止多次包含只能防止一个文件多次包含.h文件,不能防止多个文件来包含这个.h文件 。综上.h文件中是声明,.c文件中是定义的。

所有文件中,全局变量只能定义一次,可以声明多次。

四、static全局变量(内部链接)

1、static全局变量和普通全局变量的区别就是作用域不一样(文件作用域)。

2、extern关键字只适用于普通全局变量。

3、普通全局变量在多个文件都可以使用, 前提是需要提前声明。

4、static全局变量只能在本文件中使用,其他文件不能使用。

5、不同文件中只能定义一个全局变量。

6、一个文件中可以出现1个static 全局变量,多个不同的文件间可以出现多个static全局变量,不同文件间就算多个static全局变量就算名字相同,他们也没有关系。

7、static全局变量,不做初始化时默认都是数字0.

五、普通函数和static函数区别

1、所有文件只能有一次普通函数的定义。

2、一个文件可以有一个static函数的定义。

3、普通函数在所有文件中都能被调用,前提是先声明。

4、static函数只能在定义的文件中使用。

size a.out 可以看到a.out分布在哪些区:代码区,data区、bss区.....

在程序没有执行前,a.out有几个内存分区就已经确定。虽然分区已经确定但是没有加载内存,程序只有在运行时才加载内存。

text区(代码区):只读,函数

data区:初始化的数据,全局变量,static变量,文字常量区(只读)

bss区:没有初始化的数据,全局变量,static变量

运行程序开始加载内存,首先前边已经确定的分区(text、data、bss)先加载,然后额外加载两个区:stack栈区、heap堆区。

stack栈区:普通变量,自动管理内存。有先进后去的特点。

heap堆区:手动申请空间,手动释放。整个程序结束时系统也会回收。如果没有手动释放,程序也没有结束,那么这个堆区空间不会释放一直都在。

栈的空间不大,容易越界。 ulimit -a 指令可以查看栈的大小等信息。 这里检查为8M

int a[10000000000]=1; //err  编译没有问题,但是执行时候会栈越界。远远大于8M.

int *p =(int *) malloc(10000000000 * sizeof(int)); //err  编译没有问题,但是执行时候会,可见堆区空间虽然比栈区大,但是太大也是也会使内存出问题。

if(p==NULL)

{

   printf("分配失败\n");

}

memset函数不仅仅针对字符串,对所有类型都能用。

void msemset(void *s, int c, size_t n);  填充字符 c虽然为int 类型,但实际是按照char类型来操作,所以值为c的值应该为0~255。常用0来清零使用,使用其他的数值由于大小端问题容易出错。所以要么用来清0,要么用来处理char类型。

int a;

memset(&a ,0, sizeof(int));

printf("a=%d\n"a,);//得到a=0

memset(&a ,10, sizeof(int));

printf("a=%d\n"a,); //得到a=168430090,因为10作为char类型每个字节都为10(0x0a0a0a0a),所以4字节的int类型就出错了。

memcpy函数相对于strncpy的区别为,memcpy函数不会因为字符串中间有结束符号'\0'而结束。 另外使用memcpy最好别出现内存重叠,如果有内存重叠使用memmove函数不要用memcpy。

char p[]="hello\0mike";

char buf[100];

strncpy(buf,p,sizeof(p));

prinf("buf1=%s\n", buf); //打印hello

prinf("buf2=%s\n", buf+strlen("hello")+1);//打印为空,因为‘\0’后边内容没有拷贝过来

memcpy(buf,p,sizeof(p));

prinf("buf3=%s\n", buf);//打印hello,因为%s遇到'\0'也会停止。

prinf("buf4=%s\n", buf+strlen("hello")+1);//打印mike,说明‘\0’后边内容已经拷贝过来

memcmp函数和strcmp函数用法几乎一样。但是memcmp函数能制定比前几个元素。

指向堆区的指针:通过malloc函数在堆区申请一个空间。

malloc函数参数为申请多大的空间, 返回值为空表示失败,成功时候返回堆区空间首元素地址。

动态分配空间使用完不会释放,自动释放。

一般使用完需要人为释放,free(p)不是释放p变量,而是释放p指向的内存。同一块内存只能释放一次。

所谓的释放不是指内存消失,而是指这块内存用户不能再使用,系统回收了,释放后p的值不变。 如果用户再用就变成了操作非法内存(野指针),有些时候编译器虽然检测不到但很危险。 想要使用需要再申请。

int *p;

p =(int *) malloc(sizeof(int));

if(p==NULL)

{

   printf("分配失败\n");

  return -1;

}

else

{

  *p=100;

}

if(p!=NULL)

{

    free(p);   //释放后p的值不变,最好加一句人为清空。

   p=NULL;   //这样写更安全。解决释放多次问题。

}

内存泄漏:动态分配了内存,不释放。

内存污染:非法操作内存,(野指针),堆区越界等造成的。

堆区越界问题,  以下代 码编译器检测不出来,但是可能在未来不一定什么时候崩,让你摸不着头脑,以为是新写代码错误,从而找不出错误。

char *p;

p =(char *) malloc(0);

if(p==NULL)

{

   printf("分配失败\n");

  return -1;

}

else

{

  strcpy(p,"mikejack");

  printf("%s\n",p);

}

if(p!=NULL)

{

    free(p);   //释放后p的值不变,最好加一句人为清空。

   p=NULL;   //这样写更安全。解决释放多次问题。

}

堆区数组

int *p;

p = (int *) malloc(10 * sizeof(int));  //分配的内存是连续的10个int,返回的p指向的首元素地址。

p[0]=1;

*(p+0)=1; 

if(p!=NULL)

{

    free(p);   //释放后p的值不变,最好加一句人为清空。

   p=NULL;   //这样写更安全。解决释放多次问题。

}

假如指针p、q、m三个指针同时指向一块内存,free(p)后一定要把p、q、m都赋为NULL,否则易犯错。

值传递1:形参修改,不会改变实参的值。 例子如下:刚开始p的值传给temp值,temp值为NULL,后来temp改为指向堆区的某个值,但p的值仍为空。

值传递2: 下边的值传递没有问题,因为在传递前给指针在堆区分配了空间,p的值传给temp后,temp的指向和p一样,没有改变。函数调用完毕temp释放了,但堆区空间没有释放。

返回堆区地址:以下例子没有问题,因为相对于值传递1的方式,这里p是取调用函数的返回值。

windows平台通过_getch()函数可以实现字符输入不用按回车录入。 头文件位#include<conio.h>

猜你喜欢

转载自blog.csdn.net/Shayne_Lee/article/details/81159652
今日推荐