C语言2——指针

1、内存的最小单位是byte,每个byte的内存都有一个唯一编号,这个编号就是内存地址,编号在32位系统下是一个32位的整数,在64位系统下是一个64位的整数

2、如果写成int p=&a;语法上没有错误,但是地址是一个特殊的整数,是不能通过整数来操作的,所以要定义成int *p=&a;

3、int *p1;//定义一个变量,名字叫p1,它可以指向一个int的地址

4、无类型指针:void *p; 意思是这只是一个指针变量,不指向任何具体的数据类型

5、printf("%d",sizeof(p2)); //4 32位操作系统下永远都是4(64位下是8),和p2这个指针指向什么类型没有关系

  注意:数组名虽然可以看成一个指针常量,但是它的sizeof的值比较特殊:例如int arr[5]={1,2,3,4,5}; 在32位操作系统下sizeof(arr)的值是20,说明数组名和普通的指针常量还是有一定区别的

6、void *p3;//目前p3没有任何变量的地址所指向,叫野指针

7、p3=NULL;//将指针赋值NULL,值为NULL的指针,我们俗称空指针

8、NULL其实就是0,下面的代码输出为false

if(p3){
    printf("true");
}else{
    printf("false");
}

9、int *p1;//初始化的时候没有赋值,p1就是一个野指针 程序中要避免野指针的存在 野指针是导致程序崩溃的主要原因

10、int *p1; *p1=3;//对未知的空间读写,计算机会被认为是恶意行为,虽然编译时没有问题,但运行时会阻止

11、指针兼容性

int a=0x1310;
char *p=&a;//会报类型不兼容的警告
printf("%x
",*p);//10 即只读到了低8位
char b=2;
int *p=&b;
printf("%x
",*p);//cccccc02 因为一定要输出4个字节才可以构成一个int值,因此前3个字节就变成了随机值
char buf[10]={0x12,0x34,0x56,0x78,0x90};
int *p=buf;
printf("%x
",*p);//78563412 只输出前4个字节
float f=3.14;//float占4个字节
char *p=&f;
printf("%x,%x,%x,%x",*p,*(p+1),*(p+2),*(p+2));//ffffffc3,fffffff5,48,40
int main(){
    int a=0x12345678;//整形占4个字节
    char *p=&a;
    p++;
    printf("%x
",*p);//56 输出是56,证明整数在内存中存储的方式是大端对齐模式
    //大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中
    //小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中
}
int main(){
    int a=0x12345678;//整形占4个字节
    char *p=&a;
    p++;
    p++;
    printf("%x
",*p);//34
}
int main(){
    int *p1=&arr[2];
    int *p2=&arr[5];
    printf("%d
",p2-p1);//3
}

原则上一定是相同类型的指针指向相同类型的变量地址

12、指针常量与指向常量的指针

int main(){//定义一个指向常量的指针
    int a=10;
    const int *p=&a;//p这个指针只能指向一个常量
    *p=20;//会报错 不能通过*p的方法修改一个const指针
    //但是可以改a
    printf("a=%d
",a);
}
//但在C语言中,实际上通过间接的方式仍然可以修改,后来C++修正了这个问题
int main(){
    int a=100;
    const int *p=&a;
    int *p1=p;
    *p1=10;
    printf("a=%d
",a);//10 C语言里面可以这样改,但是C++里面是不可以这样改的
}
int main(){//常量指针
    int a=10;
    int b=30;
    int *const p=&a;//定义一个常量指针,可以通过常量指针修改或者读取一个变量的值
    *p=20;//可以赋值
    //但是p=&b;会报错 常量指针一旦定义了就不能修改其指向的变量
    printf("a=%d",*p);//可读
}

13、指针与数组


#include <stdio.h>
int main(){
char buf[10]={0,1,2,3,4};
char *p=buf;
   char *p=&buf;//buf &buf &buf[0]都是数组第一个元素的地址
char *p1=&buf[0];
}
#include <stdio.h>
int main(){
    char buf[10]={0,1,2,3,4};
    char *p=buf;
    int i;
    for(i=0;i<10;i++){
        *p=i;
        p++;
        //注意不可以写成buf++ buf是数组名,数组名是一个指针常量,只有变量才有++操作
    }
    //此时p已经指向数组的最后一个成员,此时再p++就会越界,所以需要重新改变p的值
    //p=buf; 这是将p的值重新指向了数组的首地址
    //p-=10; 这是将p的值重新指向了数组的首地址
}

14、指针运算注意事项

int[10] p={1,2,3,4,5,6,7,8,9,0};
p+=7;//现在p指向了第7个元素
p[2]=100;//这里将不再是给第2个元素赋值了,因为p[2]相当于*(p+2),所以这里的p其实指向了第9个元素

15、IP地址的保存

int main(){
    int ip=0;
    unsigned char *p1=&ip;
    *p1=192;
    p1++;
    *p1=168;
    p1++;
    *p1=6;
    p1++;
    *p1=252;
    printf("%d
",ip);
    //将整数转化为ip
    ip2s(ip);
    //将ip字符串转换为整数
    char s[100]="192.168.1.251";
    int my_ip=s2ip(s);
}
void ip2s(int n){
    char *p=&n;
    unsigned char *p=&n;
    printf("%d.%d.%d.%d
",*p,*(p+1),*(p+2),*(p+3));
}
void s2ip(char s[]){
    int a=0;
    int b=0;
    int c=0;
    int d=0;
    sscanf(s,"%d.%d.%d.%d",&a,&b,&c,&d);
    //printf("a=%d,b=%d,c=%d,d=%d",a,b,c,d);
    int ip=0;
    char *p=&ip;
    *p=a;
    p++;
    *p=b;
    p++;
    *p=c;
    p++;
    *p=d;
    p++;
    return ip;
}

16、用指针求一个字符串的长度

char s1[100]="hello";
char s2[100]="world";
char *p1=s1;
int len=0;
while(*p1){//*p1的内容为0的时候表示字符串结束了 *p1==''
    p1++;
    len++;
}

char *p2=s2;
while(*p2){
    *p1=*p2;//当前p1已经指向s1的最末尾,从s1的最后开始,从s2的首元素开始
    p2++;
    p1++;
}
//while循环里面的代码可以简化为*p1++=*p2++;
printf("s1=%s
",s1);//helloworld

17、冒泡排序

void bubble(int *s){
    int i;
    int j;
    for(i=0;i<10;i++){
        for(j=1;j<10;j++){
            if(*(s+j)>*(s+i)){
                int tmp=*(s+j);
                *(s+j)=*(s+i);
                *(s+i)=tmp;
            }
        }
    }
}

18、数组逆置

int *start=&buf[0];
int *end=&end[9];
while(start<end){
    int tmp=*start;
    *start=*end;
    *end=tmp;
    start++;
    end--;
}

19、不排序求数组第二大元素

int smax(int *s){
    int max=*s;
    int s_max=*(s+1);
    if(s_max>max){
        max=*(s+1);
        s_max=*s;
    }
    for(int i=2;i<10;i++){
        if(max<*(s+i)){
            s_max=max;
            max=*(s+i);
        }else if(*(s+i)<max && *(s+i)>s_max){
            s_max=*(s+i);
        }else{
            //比s_max还要小,什么都不用做
        }
    }
    return s_max;
}

20、通过指针逆置单词

char str[100]="you good me too";
char *str_start=&str[0];
char *str_end=&str[strlen(str)-1];
while(str_start<str_end){
    char *tmp=*str_start;
    *str_start=*str_end;
    *str_end=tmp;
    str_start++;
    str_end--;
}
printf("%s
",str);

21、通过指针逆置汉字字符串,一个汉字是两个字节

char str[100]="你好我也好";
short *str_start=&str[0];//short类型是2个字节
short *str_end=&str[strlen(str)-2];
while(str_start<str_end){
    short *tmp=*str_start;
    *str_start=*str_end;
    *str_end=tmp;
    str_start++;
    str_end--;
}
printf("%s
",str);

22、指针数组

int *a[10];//定义了一个指针数组,一共10个成员,其中每个成员都是int *类型
printf("%d,%d
",sizeof(a),sizeof(a[0]));//40 4
sizeof(a)是a里面所有值的长度的和,一个int *占4个字节,10个自然就是40个字节
sizeof(a[0])是第0个元素的长度,第0个元素是一个int *类型,占4个字节

short *b[10];
printf("%d,%d
",sizeof(b),sizeof(b[0]));//40 4

23、指向二维数组的指针

int main(){
    int buf[2][3]={{1,2,3},{4,5,6}};
    //int *buf[3];指针数组
    int (*p)[3];//定义了一个指针,指向int [3]这种数据类型,这就是指向二维数组的指针
    printf("%d
",sizeof(p));//4
    p=buf;//p指向了二维数组中的第一行
    p++;//p指向第二行
    printf("%d,%d
",p,p+1);//3276484 3276496 相差12 位移了1*sizeof(int [3])
    printf("%d,%d
",p,p+2);//3276496 3276520 相差24 位移了2*sizeof(int [3])
    int i;
    int j;
    for(i=0;i<2;i++){
        for(j=0;j<3;j++){
            printf("%d
",p[i][j]);
        }
    }
    for(i=0;i<2;i++){
        for(j=0;j<3;j++){
            printf("%d
",*(*(p+i)));//*(p+i)是第i行首元素的地址 *(*(p+i))是第i行首元素地址的值
            printf("%d
",*(*(p+i)+j));//*(p+i)+j是第i行第j个元素的地址,*(*(p+i)+j)是第i行第j个元素的值,等价于p[i][j]
        }
    }
}

24、指针总结

int buf[3][5];
int (*a)[5];            定义一个指向int[5]类型的指针
a                第0行的首地址
a[0],*(a+0),*a;            第0行第0个元素的地址
a+1;                第1行的首地址
a[1],*(a+1);            第1行第0个元素的地址
a[1]+2,*(a+1)+2,&a[1][2]    第1行第2个元素的地址
*(a[1]+2),*(*(a+1)+2),a[1][2]    第1行第2个元素的值

25、通过指针求每行每列的平均值

int buf1[3][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};//可以先考虑不用指针的情况
for(int i=0;i<3;i++){
    int sum=0;
    for(int j=0;j<5;j++){
        //sum+=buf1[i][j];
        sum+=*(*(buf1+i)+j);
    }
    printf("%d
",sum/5);//每行的均值
}
for(int i=0;i<5;i++){
    int sum=0;
    for(int j=0;j<3;j++){
        //sum+=buf1[j][i];
        sum+=*(*(buf1+j)+i);
    }
    printf("%d
",sum/3);//每列的均值
}

26、数组名作为参数,这种情况通常会在后面跟一个参数来表示数组的长度

void set_array(int *buf,int n){//这种写法同void set_array(int buf[]){...} n主要是用来告诉函数内部buf这个数组的维度
    printf("%d
",sizeof(buf));//4
    buf[0]=100;//等价于*buf=100
}
int main(){
    int buf[5]={1,2,3,4,5};
    printf("%d
",sizeof(buf));//20
    set_array(buf,sizeof(buf)/sizeof(int));
}

27、将二维数组作为函数参数传递的时候定义的指针类型(平时开发用的很少)

void print_array(int (*p)[3],int a,int b){
    int i;
    int j;
    for(i=0;i<a;i++){
        for(j=0;j<b;j++){
            printf("p[%d][%d]=%d
",i,j,p[i][j]);
        }
    }
}
int main(){
    int buf[2][3]={{1,2,3},{4,5,6}};
    print_array(buf,sizeof(buf)/sizeof(buf[0]),sizeof(buf[0])/sizeof(int));
}

28、自己实现strcpy

void mystrcat(char *s1,const char *s2){//第一个参数是要被修改的,第二个参数不被修改
    int len=0;
    while(s2[len]){
        len++;
    }
    while(*s1){
        s1++;
    }
    int i;
    for(i=0;i<len;i++){
        *s1=*s2;
        s1++;
        s2++;
    }
}
int main(){
    char s1[10]="abc";
    char s2[10]="def";
    mystrcat(s1,s2);
    printf("s1=%s
",s1);
    return 0;
}

strcpy在定义的时候是这样的:strcpy(char *s1,const char *s2);因为s2不需要改变,所以需要用const保护起来

29、返回指针类型的函数

char *mystrchr(char *s,char c){
    while(*s){
        if(*s==c){
            return s;
        }
        s++;
    }
    return NULL;
}
int main(){
    char str[100]="hello world";
    char *s=mystrchr(str,'o');
    printf("s=%s
",s);//o world
}

30、函数指针 Java中的多态在底层就是通过函数指针来实现的

int add(int a,int b){
    return a+b;
}
int max(int a,int b){
    return a>b?a:b;
}
int main(){
    int(*p)(int,int);//定义了一个指向函数的指针,但是函数必须要有两个参数,而且返回至必须也是int,在C++中是编译不通过的,不过在C语言中只有警告
    p=add;
    int i=0;
    i=p(5,7);
    printf("%d",i);//12
    p=max;
    int j=0;
    j=p(5,7);
    printf("%d",i);//7
    return 0;
}

31、定义一个参数为int * 返回值为int *的指向函数的指针

int *(*p)(int *);

32、把指向函数的指针作为函数的参数

void function(int(*p)(int,int),int a,int b){//第一个参数是指向函数的指针
    return p(a,b);
}
int main(){
    int i=func(add,6,9);//add就是回调函数
    printf("i=%d
",i);
    return 0;
}

33、内存操作函数(需要引入string.h头文件)

memset 

int buf[10]={0};//int buf[10]={0}这种数组赋值方式只能用于定义数组的时候同时初始化内容,不能在中途调用
buf[0]=8;
buf[1]=9;
buf[2]=8;
//希望将这个buf再一次初始化为0
//在这里如果企图通过buf[10]={0}来重新把所有的值都重置为0是不可以的,这种方式只能在初始化的时候用
memset(buf,0,sizeof(buf));//三个参数分别是要设置的内存地址 要设置的值 内存大小

memcpy

int main(){
    int buf1[5]={1,2,3,4,5};
    int buf2[5];
    memcpy(buf2,buf1,sizeof(buf1));//将buf1的内存内容全部拷贝到buf2,拷贝大小由第三个参数确定,单位是字节
}

memmove

int main(){
    int buf1[5]={1,2,3,4,5};
    int buf2[5];
    memcpy(buf2,buf1,sizeof(buf1));
    for(int i=0;i<5;i++){
        printf("%d ",buf2[2]);//1 2 3 4 5
    }
    for(int i=0;i<5;i++){
        printf("%d ",buf[2]);//1 2 3 4 5
    }
}

memmove并没有改变原来内存的值,和memcpy功能一样

使用memcpy的时候要确保内存没有重叠区域

int main(){
    int buf[10]={1,2,3,4,5,6,7,8,9,0};
    int *start=buf+3;
    int *end=buf+5;
    memcpy(start,end,16);//将从start首地址开始的16个字节的内容拷贝到从end首地址开始的16个字节内,这样的话在拷贝的过程中就会产生重叠区
}

34、字符指针和字符串

int main(){
    char buf[100]="hello world";
    char *p=buf;
    *(p+5)='a';//*(p+5)和p[5]效果是一样的
    printf("%s",buf);
    return 0;
}

35、字符指针类型作为参数传入

void print_str(char *s){//如果是int类型的数组的话(即int*),那就必须传入第二个参数作为数组的长度,如果是char *就不用,因为字符串是明确的以''结尾的,所以在函数内部是有条件来作为循环终止依据的,但是这同样带来一个问题,那就是如果我们写了s[3]=0这样一句代码,那么用这个print_str函数打印字符串的时候只能打印到第二个字符,第三个字符之后就出不来了,因为循环已经结束了,即使我们不用自定义的函数而用系统提供的printf效果也是一样的,但是字符数组第3个字符后面的字符还真实存在
    int i;
    while(s[i]){
        printf("%c",s[i++]);
    }
}
int main(){
    char s[100]="hello";
    print_str(s);
}

36、main函数中有参数的情况

C语言中的main函数是操作系统会自己的

int main(int argc,char *args[]){
    printf("%d",argc);//1 形参argc代表程序执行的时候有几个参数,程序名称本身就是一个参数,所以argc最小是1
    //第二个参数是一个指针数组,其中每个成员的类型是char*
    //argc就是args的长度
    printf("args[0]=%s
",args[0]);//可执行程序的绝对路径
    int i;
    for(i=0;i<argc;i++){
        printf("args[%d]=%s
",i,args[i]);
    }
}

37、cmd下运行下面这个程序,效果和dir指令是一样的,但是dir指令有参数,例如dir c: 但是这里的参数不起作用,我们可以将这个程序改造一下

int main(int argc,char *args[]){
    system("dir");
    return 0;
}
//================================
int main(int argc,char *args[]){
    char buf[100]="dir";
    int i;
    for(i=1;i<argc;i++){
        strcat(buf," ");
        strcat(buf,args[i]);
    }
    system("dir");
    return 0;
}

38、int *p[10]和int (*p)[10]的区别

int main(){
    int *s[10]={0};
    int (*p)[10]=0;//定义了一个指针变量p,指向int [10]这么大的一种数据类型
    printf("%d",sizeof(s));//40 s是一个数组名,数组的类型是int *,每个int *是4个字节,所以sizeof(s)是40个字节
    printf("%d",sizeof(p));//4
}

39、二维数组名赋值给指针变量的问题

int main(){
    int array[3][4]={0};
    //int *p=array;这样写是错误的,int *这种类型只能指向一维数组,这里需要int [4]类型,即通过int (*p)[4]来定义
    
}

40、变量作用域

C语言变量的作用域可以是块级作用域 函数作用域 文件作用域

#include<stdio.h>
int a=10;//全局作用域
extern int age;//这句话意思是名为age的int类型的变量在其他外部文件中已经定义过了,在此声明一下,在该文件中就可以使用这个变量了
int main(){
    printf("%d",a);//10
}

假如在一个工程下有两个C源文件:a.c main.c

情景1

//a.c--------------------
int mymax=5;
int max(int a,int b){
    return a>b?a:b;
}
//main.c----------------------------
#include<stdio.h>
int max(int a,int b);//在main.c中引入的时候可以加上extern,也可以不加,只要在这里声明了max函数就把外面a.c文件里面的max函数引过来了
int mymax;//在这里可以用extern修饰,也可以不用extern修饰,但是如果不用extern修饰的话容易让人认为是定义了一个全局变量,但是实际上表示的却是一个外部变量,这样就会有二义性
int main(){
    return 0;
}

情景2

//a.c--------------------
static int mymax=5;
int max(int a,int b){
    return a>b?a:b;
}
//注:C语言中很少用static修饰max函数
//main.c----------------------------
#include<stdio.h>
int max(int a,int b);
int mymax;//a.c中已经通过static修饰了,main.c里面就无法访问到了,所以这里会报错
int main(){
    return 0;
}

41、寄存器变量

register int i=0;//建议如果有寄存器空闲,那么这个变量就放到寄存器里面使用,可以使效率增加
int *p=&i;//这样会报错,既然变量都到寄存器里面了,不存在内存地址这一说,所以寄存器变量不能取地址

42、静态变量

void mystatic(){
    static int a=0;//静态变量只初始化一次,而且程序运行期间内,静态变量一直存在,在这里,这个a变量是在mystatic函数里面定义的,所以这个a的作用域仍然是这个块语句
    printf("a=%d
",a);//依次输出0 1 2 3 4
    a++;
}
int main(){
    for(int i=0;i<5;i++){
        mystatic();
    }
}

全局静态变量只能在本文件内使用,在其他文件中即使通过extern引入也不能使用

C语言里面的函数都是全局的
函数也可以用static修饰,加了static的函数只能在当前.c文件中使用

43、内存管理概述

每个应用程序都会占一块独立的内存,各个应用程序之间的内存不能相互访问,对于每个应用程序占的内存,分为堆 栈 代码区 静态区

代码区:程序被操作系统加载到内存的时候,所有可执行代码都被加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的,注意:变量的定义不是可执行代码,变量的赋值是可执行代码

静态区:所有的全局变量以及程序中的静态变量都存储到静态区

栈区:所有的自动变量,函数的形参都是由编译器自动放入栈中,当一个自动变量超出其作用域时,自动从栈中弹出,大小并不固定,各个操作系统下不一样,但是不会特别大,一般以K为单位,所谓栈溢出,就是当栈空间已满,但还往栈内存中压变量,就是栈溢出

int main(){
    char array[1024 * 1024 *100]={0};//定义了一个100M的数组,会发生栈溢出,定义一个很大的数组一定会溢出
    return 0;
}

堆区和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序
堆是一个大容器,它的容量要远远大于栈,但是在C语言中堆内存空间需要手动通过代码申请和释放

#include<stdio.h>
int c=0;//在静态区
void test(int a,int b){
    printf("%d %d
",&a,&b);
}int main(){//函数都在代码区static int d=0;
    int a=0;//在栈区
    int b=0;
    printf("%d %d %d %d %d
",&a,&b,&c,&d,main);//从得到的结果可以看出,c与a b的位置相差很远,main函数的地址也没有和a b c挨着,而a和b是挨着的,c和d是挨着的,从a和b的值上可以看到一个细节:a的地址比b大
    test(a,b);//函数里面形参的地址是a的地址比b大
    return 0;
}

以上程序执行的过程中,先定义a,所以a先入栈,再定义b,b再入栈,a就在b的后面,所以a的地址比b大
main函数执行完的时候b先出栈,a后出栈
自动变量的出入栈是编译器控制的,程序无需控制

对于32位操作系统,最大管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序的,一个用户程序理论上可以使用3G的内存空间

44、malloc和free——申请/释放堆内存

malloc返回一个无类型的指针(void *),可以强转为对应类型,也可以依靠程序自动去转换,malloc在堆中间申请内存

int *p=(int *)malloc(sizeof(int)*10);
char *p1=(char *)malloc(sizeof(char)*10);//在堆中申请了10个char类型大小的空间
free(p);
free(p1);//释放malloc分配的内存

malloc和free一定要成对使用
返回的p p1可以当指针和数组用

通过memset清空p的内存
memset(p,0,sizeof(int)*10);

45、堆数组和栈数组

int *p=(int *)malloc(sizeof(int)*10);//堆数组
int array[10]={0};//栈数组

46、函数的形参是值类型时,在函数体内不可以作为左值使用,函数的形参是指针类型时才可以,这个规律同样适用于js

#include<stdio.h>
int *geta(){ int a=100; return &a; } int main(){//函数都在代码区 int *p=geta();//这里得到了一个临时栈变量的地址,这个地址在函数geta调用完成之后已经无效了,所以这样写可能是有问题的,不符合栈的要求 *p=100; printf("%d ",*p); return 0; }

上面的geta函数我们可以这样修改使其正确

int *geta1(){
    int *p=malloc(sizeof(int));
    return p;
}
int *geta2(){//这样也可以
    static int a=0;
    return &a;
}
int main(){
    int *getp=geta1();
    *getp=100;
    free(getp);//一定要记得free

    int *getp2=geta2();
    //静态区的变量不能free
}

47、堆内存空间的丢失(报废)

void getheap(int *p){
    p=malloc(sizeof(int)*10);
}//getheap执行完以后,p变量就消失了,导致它指向的具体堆空间的地址编号也随之消失了,这里面malloc申请的内存将永远无法释放(报废了),main函数里面的free只是释放一个空指针p
int main(){
    int *p=NULL;
    getheap(p);
    p[0]=1;//程序会崩溃
    p[1]=2;
    printf("%d %d",p[0],p[1]);
    free(p);
    return 0;
}

通过二级指针修改上面的代码

void getheap1(int **p){
    *p=malloc(sizeof(int)*10);
}
int main(){
    int *p=NULL;
    getheap(&p);//将p在栈中的地址传了进去
    p[0]=1;
    p[1]=2;
    printf("%d %d",p[0],p[1]);
    free(p);
    return 0;
}
char *getstring(){//错误的模型
    char array[10]="hello";//因为array是在栈中的地址,但是getstring执行完了之后array所占的空间就会被释放,所以main函数中s变量接受到的是个垃圾值
    return array;
}
int main(){
    char *s=getstring();
    printf("%s",s);
    return 0;
}
int *getheap2(){//正确的模型
    return malloc(100);//返回了一块堆空间的地址,这块地址还没有被释放,所以在main函数中可以拿到
}
int main(){
    int *p=NULL;
    p=getheap2();
    free(p);
    return 0;
}
char getstring1(){//正确的模型
    char c='a';
    return c;
}
int main(){
    char c=getstring1();
    printf("%c",c);
}
const char *getstring3(){//正确的模型
    return "hello";//常量 全局变量 静态变量都在静态区,直接返回常量"hello"的地址是可以的,而且这个常量的地址在程序运行过程中是不变的
}
int main(){
    const char *ss=getstring3();
    printf("%s",ss);
}
char *getstring3(){//正确的模型
    static char array[10]="hello";//在静态区
    return array;
}
int main(){
    const char *ss=getstring3();
    printf("%s",ss);
}

48、static定义的全局变量

//------------------a.c
//全局变量加了static的话该变量只能在a.c本文件里面使用,而别的文件还可以定义mymax这个变量,但是别的文件里面定义mymax这个变量的时候最好也用static修饰,这样各个文件之间就互不影响了,而在C++中则采用namespace来避免命名冲突
static int mymax=100;//虽然在别的文件里面访问不到mymax变量,但是我们可以通过定义下面的get_mymax来拿到其地址
int *get_mymax(){
    return &mymax;
}
void print_mymax(){
    printf("mymax=%d
",mymax);
}
//------------------main.c
#include<stdio.h>
extern int *get_mymax();
extern void print_mymax();
int main(){
    int *p=get_mymax();
    &p=300;
    print_mymax();//300
    return 0;
}

49、动态数组

想要定义动态数组通过下面的方法在栈上定义是不可以的,这样写编译都不会通过,数组在定义的时候长度必须是常量
int i=0;
scanf("%d",&i);
int array[i];
==========================
定义动态数组只能在堆上定义
int i=0;
scanf("%d",&i);
int *arr=malloc(sizeof(int)*i);//使用完了之后可以free掉

50、C语言中函数的入栈是从右往左的,例如:UpdateCounter(char *b,int c,int a1)这个函数的参数入栈的顺序其实是先a1入栈,接下来c入栈,最后是b入栈,有的高级语言是从左往右入栈的

51、内存页

操作系统在管理内存的时候,最小单位不是字节,而是内存页
Linux下内存页是4K
当通过malloc申请1K内存时,堆空间大小是4K,再申请1K内存时,堆空间大小还是4K,直到malloc申请到的内存超过4K时会再分配一个4K的内存,这个4K的内存就是内存页,Linux将内存页定为4K一定有它的道理,如果内存页分配太大,那一定回造成空间的浪费,但是操作系统分配内存的次数少了,效率提升,相反内存页分配太小的话,操作系统就需要频繁调度寻找是否有空闲内存,造成效率上的降低,但是节省了空间,所以这是一个折中的问题
在Windows中,我们把一个含有用malloc申请堆空间的C语言程序跑起来以后可以看一下任务管理器进程里面这个程序所占的内存空间,然后改变源代码中malloc申请空间的大小,再看进程所占的空间,就可以计算出Windows下内存页的大小了,代码如下
while(1){
  int *p=malloc(1024);//通过测试发现32位Windows操作系统的内存页也是4K
  getchar();
}
在嵌入式开发的场合一定要节约内存

52、calloc

malloc申请到的内存空间里面旧的值有可能还保留着,所以我们需要手动调用memset清除,这一步工作可以交给calloc函数,calloc函数保证了申请到的内存空间是干净的,同样也需要free释放内存
char *p1=calloc(10,sizeof(char));//分配了10个大小为sizeof(char)的堆内存,即10个字节

int *p1=calloc(sizeof(int),100);//申请100字节内存
int *p2=realloc(p1,sizeof(int)*200);//再在p1后面申请100字节内存,达到200字节
//新申请的100字节不是空的,可以通过memset(p2+100,0,sizeof(int)*100);来清零

char *p2=realloc(p1,20);//希望申请20个字节,而且希望紧接在p1后,如果p1后面没有连续空间扩展,会新分配一个空间,将原有内存拷贝到新空间,然后释放原有的内存,realloc分配到的内存不是干净的

char *p2=realloc(NULL,5);//分配形式相当于malloc

所有的变量都是在栈里面的,可以通过在栈中存放地址来访问堆内存
calloc的两个参数写反了也无所谓,因为calloc内部其实是把两个参数一乘,再返回结果

不论malloc calloc realloc,都会有一种情况,就是没有堆内存可分配了,申请不到内存空间了,这种情况是比较严重的,可以直接exit(0);
if(p1==NULL){
  exit(0);
}

原文地址:https://www.cnblogs.com/zhaohuiziwo901/p/5031332.html