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>