2023年的C基础笔记

头介绍

#include <stdio.h>
#include <time.h>
#include <string.h>
// <> 寻找系统的资源
// “” 寻找我们自己写的资源
// .h .hpp(声明文件 头文件)
// .c  .cpp (实现文件)

字符串

字符串是一种非常重要的数据类型,但是C语言不存在显式的字符串类型,C语言中的字符串都以字符串常量的形式出现或存储在字符数组中。

C 语言虽然没有字符串类型。

字符串常量

C 语言虽然没有字符串类型,但是 C语言是存在字符串这个概念的,也就是字符串常量。

字符串常量:以 NUL 字节结尾的 0 个或多个字符组成的序列字符串常量是不可被修改的

一般用一对双引号(" ")括起的一串字符来表示字符串常量,如:"Hello!"、"\aWarning!\a"、"123abc\n"、""

字符串常量可以为空,如""就是一个空的字符串常量,但是即使为空,还是存在一个终止符 NUL 的。(在 C 语言中,常用转义字符 \0 来表示 NUL)

   声明了一个指向 char 类型的指针  这是一个野指针 没有初始化
     char * char_p;
    使用字符串常量"Hello World!" 中第 1 个字符H的指针地址 对该指针char_p进行初始化。
    char_p = "Hello World!";
    
     等价于
     char *char_p = "Hello World!";

  // 声明了一个指向 char 类型的指针  这是一个野指针 没有初始化
     char * char_p;
     printf("输出指针char_p=%p\n",char_p);
    //使用字符串常量"Hello World!" 中第 1 个字符H的指针地址 对该指针char_p进行初始化。
    char_p = "Hello World!";
    printf("输出指针char_p=%p,通过指针char_p获取字符串常量Hello World!的第 1 个字符*char_p=%c\n",char_p,*char_p); // 输出指针char_p=00408AC7,通过指针char_p获取字符串常量Hello World!的第 1 个字符*char_p=H

    //通过挪动指针char_p  来获取字符串常量Hello World!的每一字符*(char_p++)
    while(*char_p !='\0'){
      printf("通过挪动指针地址char_p=%p ,获取字符串常量的每一个字符=%c\n",char_p,*(char_p++));
    }
    /**
通过挪动指针地址char_p=00408AC8 ,获取字符串常量的每一个字符=H
通过挪动指针地址char_p=00408AC9 ,获取字符串常量的每一个字符=e
通过挪动指针地址char_p=00408ACA ,获取字符串常量的每一个字符=l
通过挪动指针地址char_p=00408ACB ,获取字符串常量的每一个字符=l
通过挪动指针地址char_p=00408ACC ,获取字符串常量的每一个字符=o
通过挪动指针地址char_p=00408ACD ,获取字符串常量的每一个字符=
通过挪动指针地址char_p=00408ACE ,获取字符串常量的每一个字符=W
通过挪动指针地址char_p=00408ACF ,获取字符串常量的每一个字符=o
通过挪动指针地址char_p=00408AD0 ,获取字符串常量的每一个字符=r
通过挪动指针地址char_p=00408AD1 ,获取字符串常量的每一个字符=l
通过挪动指针地址char_p=00408AD2 ,获取字符串常量的每一个字符=d
通过挪动指针地址char_p=00408AD3 ,获取字符串常量的每一个字符=!
  */

字符串常量与指针

字符串常量与指针关系密切,因为字符串常量的值,实际上表示的是存储这些字符的内存空间的地址,更准确地说是字符串常量中第 1 个字符的地址,而不是这些字符本身。因此,在 C 语言中是不能直接进行字符串赋值的(因为没有字符串类型嘛)。在 C 语言中,常通过声明一个指向 char 类型的指针并将其初始化为一个字符串常量的方式来访问一个字符串:char *message = "Hello World!";

字符数组

用于存放字符的数组称为字符数组。在 C 语言中,除了字符串常量外,其他所有字符串都必须存储于字符数组或动态分配的内存中

 定义一个字符数组和定义一个普通数组一样,不同的是字符数组中存放的是字符数据而已:

    声明并初始化一个字符数组charArray  
    char charArray1[] = {'H','e','l','l','o'};
     声明并初始化一个字符数组charArray11
    char charArray11[] = "Hello";    
    二者等价的

 // 声明并初始化一个字符数组charArray  这个数组的长度实际上为 6 ,因为会自动添加一个字符串结束符 '\0'。
    char charArray1[] = {'H','e','l','l','o'};
    int len1 = strlen(charArray1);
    printf("字符数组charArray1的长度strlen=%d\n",len1); // 字符数组charArray1的长度strlen=5
    //或者
    // 声明并初始化一个字符数组charArray11
    char charArray11[] = "Hello";
    int len11 = strlen(charArray11);
    printf("字符数组charArray11的长度strlen=%d\n",len11); //字符数组charArray11的长度strlen=5



    char str[] = "hello world!";
    int len = strlen(str);
    int i;
    for(i = 0; i < len; i++){
        if( str[i] <= 'z' && str[i] >= 'a'){
            str[i] = str[i] - 32;
        }
    }
  printf("输出修改后的数组str=%s\n",str); //输出修改后的数组str=HELLO WORLD!

字符串常量与字符数组的区别

1)字符串常量是一个字符数组,但是内容和长度在初始化时就已经固定了,不可更改;可以通过一个指向字符串常量第一个元素的指针来访问该字符串常量; char *char_p = "Hello World!";

2)字符数组是一个用于存放字符的数组,字符数组的值是可以更改的。

char charArray1[] = {'H','e','l','l','o','W','o','r','l','d',','!'}; 或者 char charArray11[] = "Hello World!";  

获取字符串长度

字符串的长度就是这个字符串所包含字符的个数,但是这个长度是不包含 NUL 字符的。C 语言中使用库函数 strlen 来计算字符串长度:

size_t strlen(char const *string);

字符串转换

int atoi (const char *)    把字符串char *转为int类型

double atof (const char *)  把字符串char *转为double类型

 // todo 定义一个char类型的指针num  初始化 指向字符串常量"1"的第一个字符指针地址
    char*  num = "1";
   // int atoi (const char *) 把指针num转换为int类型   返回int类型数值结果   非0表示转换成功   0表示转换失败
    int result =  atoi(num);
    if (result!=0) { // 非0转换成功
        printf("恭喜你转换成功:%d\n", result);   //恭喜你转换成功:1
    } else { // 0就是转换失败了
        printf("转换失败!\n");
    }

    //  double atof (const char *);  把指针num转换为double类型  返回double类型数值结果    非0表示转换成功   0表示转换失败
    double resultD =  atof(num);
    printf("恭喜你转换成功:%lf\n", resultD); //恭喜你转换成功:1.000000

复制字符串

  char *strcpy(char *dst , char const *src);

有问题 复制字符串会存在覆盖的问题

函数 strcpy 接收两个字符串参数,并将参数 src 字符串复制到 dst 参数。使用 strcpy 函数时需要注意的是,必须保证目标字符数组 dst 的长度足够存放源字符数组 src 的内容。如果 src 比 dst 长,则 src 剩余部分的字符仍会被复制,而且它们会覆盖 dst 后面的内存空间的值,如果这除内存空间原本就存放有值,则会导致原本的数据丢失,这样会造成很严重的后果。为了解决这个问题, C 语言中提供了一种更为安全的方式来进行字符串复制操作——strncpy 函数:

// 指针是可以:++ --  +=  -=
     //定义一个数组destination  内存空间大小 1*25 =25字节
    char destination[25]; // 容器 25的大小 已经写死了
     // 定义字符串
    char*  blank = "--到--", * CPP = "C++", *Java = "Java";
    printf("输出字符串blank=%s , CPP=%s , Java=%s\n",blank,CPP,Java); // 输出字符串blank=--到-- , CPP=C++ , Java=Java
   printf("输出数组destination=%s\n",destination); //输出数值destination=

   // char *strcpy (char *, const char *); 将字符串CPP  复制到数组destination里去
    strcpy(destination, CPP);
    printf("再次输出数组destination=%s\n",destination);   //再次输出数组destination=C++
char *strncpy(char *dst , char const *src , size_t len);
strncpy 函数有三个参数,与 strcpy 函数一样,它也是将 src 字符串中的字符复制到目标数组 dst 中去,但是 strncpy 函数提供的第三个参数 len 规定了可以向 dst 中写入的字符的个数:

1)如果 strlen(src) > len,则只有 len 个字符被复制到 dst 中去,此时 dst 将不会以 NUL 字节结尾(也就是说,strncpy 调用的结果可能不是一个字符串);

2)如果 strlen(src) < len,则 src 中的字符全被复制到 dst 中去,dst 中剩余的部分用 NUL 填充。


// 定义一个数组
char stringArr[10];
 // 定义字符串 str1
 char* str1  = "abcdefghi";

 printf("%s\n", stringArr); // 打印数组 空的
 // 就是把 字符串str1 复制到 数组stringArr里去
 strcpy(stringArr,str1);
 printf("%s\n", stringArr); // 打印数组 abcdefghi
printf("数组stringArr的长度=%d\n", sizeof stringArr / sizeof(char));// 数组arr的长度=10
//参数  pResult是个指针   str 是个指针
void substrAction4(char *result, char *str, int start, int end) {
    // 参数1:我最终是copy到result容器里面
    // 参数2:直接从r开始,因为我一级做了,指针挪动了
    // 参数3:你从r开始,挪动多少
    printf("输出result数值result=%s,str数值=%s\n",result,str); //输出result数值result=,str数值=Derry is
    //strcpy(result,str); //将字符串str=Derry is  copy 给字符串result   result=Derry is
    //strncpy(result,str,end-start); //将字符串str=Derry is 从0-2位 copy 给字符串result   result=Der
    strncpy(result,str+start,end-start); //将字符串str=Derry is 从2-4位 copy 给字符串result   result=rry
}


int mainTask2(){

    //定义一个字符串
    char* str = "Derry is";
    // 定义一个字符串
    char*  result;

    //传递参数:字符串result  字符串  str
    //substrAction1(result,str,2,5);
   // substrAction11(result,str,2,5);
    substrAction2(&result,str,2,5);
  //  substrAction4(result, str, 2, 5);
    printf("最终输出result=%s\n",result);  //最终输出result=rry

  //动态开辟的内存空间 result 在这里回收
    if (result) {
        free(result);
        result = NULL;
    }



}

连接/拼接字符串

char *strcat(char *dst,char const *src);
函数 strcat 将参数 src 字符串连接到参数 dst 字符串的后面。与 strcpy 函数一个同样的问题是,必须保证 dst 的剩余空间足够存放下 src 整个字符串。C 语言中提供了 strncat 函数来解决这个问题: 
char *strncat(char *dst , char const *src , size_t len);
strncat 函数从 src 中最多复制 len 个字符到目标数组 dst 后面,并且,strncat 总是在结果字符串后面添加一个 NUL 字节,而且不会像 strncpy 函数那样对 dst 剩余的空间用 NUL 进行填充。
  // 指针是可以:++ --  +=  -=
     //定义一个数组destination  内存空间大小 1*25 =25字节
    char destination[25]; // 容器 25的大小 已经写死了
     // 定义字符串
    char*  blank = "--到--", * CPP = "C++", *Java = "Java";
    printf("输出字符串blank=%s , CPP=%s , Java=%s\n",blank,CPP,Java); // 输出字符串blank=--到-- , CPP=C++ , Java=Java
   printf("输出数组destination=%s\n",destination); //输出数值destination=

   // char *strcpy (char *, const char *); 将字符串CPP  复制到数组destination里去
    strcpy(destination, CPP);
    printf("再次输出数组destination=%s\n",destination);   //再次输出数组destination=C++

     //char *strcat (char *, const char *); 将字符串  blank 拼接到数组destination里去
    strcat(destination, blank); // 然后再拼接
    printf("再再次输出数组destination=%s\n",destination);  // 再再次输出数组destination=C++--到--
    strcat(destination, Java); // 然后再拼接
    printf("再再再次输出数组destination=%s\n",destination);  // 再再再次输出数组destination=C++--到--Java

字符串比较

比较2个字符串是否相等(区分大小写)
int strcmp(char const *s1 , char sonst *s2);
strcmp 函数会对被比较的两个字符串进行逐字符地比较,直到发现不匹配为止:最先不匹配的字符中较小的那个字符所对应的字符串即被认为小于另一个字符串;如果两者所有字符都匹配,则认为这两个字符串相等;
该函数的返回值如下:
1)s1 小于 s2,返回一个负值;
2)s1 等于 s2,返回 0;
3)s1 大于 s2,返回一个正值。

 比较2个字符串是否相等(不区分大小写)
 int strcmpi (const char *, const char *) 

char *strncmp(char const *s1 , char const *s2 , size_t len);
可以使用 strncmp 函数限定比较的字符的个数,返回值与 strcmp 一样,但是只针对前 len 个字符进行比较。
//todo 定义一个char类型的指针str1  初始化 指向字符串常量"Derry"的第一个字符'D'的指针地址
    char* str1 = "Derry";
    //todo 定义一个char类型的指针str2  初始化 指向字符串常量"derry"的第一个字符'd'的指针地址
    char* str2 = "derry";

     //  todo int strcmp (const char *, const char *)  比较2个字符串是否相等(区分大小写)    返回0代表是相等的, 非0代表是不相等的
    int resultC = strcmp(str1, str2);
    printf("resultC=%d\n",resultC);  // resultC=-1  非0代表是不相等的

    //  todo int strcmpi (const char *, const char *)  比较2个字符串是否相等(不区分大小写)    返回0代表是相等的, 非0代表是不相等的
    int resultC2 = strcmpi(str1, str2);
    printf("resultC2=%d\n",resultC2); // resultC2=0  0代表是相等的
     if(resultC2==0){
         printf("字符串str1与字符串str2  相等\n"); //  字符串str1与字符串str2  相等
     }else{
         printf("字符串str1与字符串str2 不相等\n");
     }

字符串的查找

查找一个字符

使用 strchr 函数或 strrchr 函数来在一个字符串中查找一个特定的字符

char *strchr(char const *str,int ch); // int ch 是字符的ASCII码值
char *strrchr(char const *str,int ch);

函数 strchr 在字符串 str 中查找字符 ch 第一次出现的位置,并返回一个指向该位置的指针;如果没有找到相应的字符,则返回一个 NULL 指针。

函数 strrchr 在字符串中查找字符 ch 最后一次出现的位置,并返回指向该位置的指针


 

查找任意几个字符

strpbrk 函数来查找任何一组字符第一次在字符串中出现的位置:

char *strpbrk(char const *str , char const *group);

这个函数返回一个指向字符串 str 中第一个匹配 group 中任何一个字符的字符位置,如果没有匹配到,则返回一个 NULL 指针。 

 

查找一个子串

使用 strstr 函数来在一个字符串中查找一个子串

char *strstr(char const *str1 , char const *str2);

在 str1 中查找整个字符串 str2 第一次出现的起始位置,并返回一个指向该位置的指针;如果 str2 并没有完整的出现在 str1 中,则函数将返回一个 NULL 指针;如果 str2 是一个空字符串,则返回str1.

 //定义字符串
    char * text = "name is Derry";
    char * subtext = "D";

    // char *strstr (const char *, const char *)  查找一个字符串是否包含另一个字符串  返回一个指针结果    非NULL  就查找到了包含   NULL 就是没查找到不包含
    char* pop = strstr(text, subtext);
     if(pop!=NULL){
         printf("查找到了,pop的值=%s\n", pop);   // 非NULL 走if  查找到了,pop的值=Derry
     }else{
         printf("没有查找到,subtext的值是:%s\n", subtext);
     }

    // 包含了D吗
    if (pop) {
        printf("包含D\n");   // 包含D
    } else {
        printf("不包含D\n");
    }

数据类型

基本数据类型

它们是算术类型,包括整型(int)、字符型(char)、浮点型(float)和双精度浮点型(double)。

char   1 字节

 int       4 字节

long  4 字节   

float  4 字节  单精度浮点值。

double  8 字节 双精度浮点值。

long double 16 字节

      变量初始化

         变量在定义时就初始化:

        type 表示变量的数据类型,variable_name 是变量的名称,value 是变量的初始值。
         type variable_name = value;

int x = 10;         // 整型变量 x 初始化为 10
float pi = 3.14;    // 浮点型变量 pi 初始化为 3.14
char ch = 'A';      // 字符型变量 ch 初始化为字符 'A'
extern int d = 3, f = 5;    // d 和 f 的声明与初始化
int d = 3, f = 5;           // 定义并初始化 d 和 f
byte z = 22;                // 定义并初始化 z

       后续初始化变量:

        在变量定义后的代码中,可以使用赋值运算符 = 为变量赋予一个新的值。
        type variable_name;    // 变量定义
       variable_name = new_value;    // 变量初始化

     变量在使用之前应该被初始化。未初始化的变量的值是未定义的,可能包含任意的垃圾值。
     因此,为了避免不确定的行为和错误,建议在使用变量之前进行初始化。

int x;          // 整型变量x定义
x = 20;         // 变量x初始化为20
float pi;       // 浮点型变量pi定义
pi = 3.14159;   // 变量pi初始化为3.14159
char ch;        // 字符型变量ch定义
ch = 'B';       // 变量ch初始化为字符'B'

变量不初始化

C语言 如果变量没有显式初始化,变量的默认值取决于其类型和作用域。全局变量和静态变量的默认值为 0,字符型变量的默认值为 \0,指针变量的默认值为 NULL,而局部变量没有默认值,其初始值是未定义的。

对于全局变量和静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。

整型变量(int、short、long等):默认值为0。

浮点型变量(float、double等):默认值为0.0。

字符型变量(char):默认值为'\0',即空字符。

指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。

数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。

局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。

变量声明

变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要知道实际的变量声明。

变量的声明有两种情况:

1、一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。

2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。

除非有extern关键字,否则都是变量的定义。

extern int i; //声明,不是定义
int i; //声明,也是定义

// 函数外定义变量 x 和 y 没有初始化
int x;
int y;
int addtwonum() {
    // 函数内声明变量 x 和 y 为外部变量
    extern int x;
    extern int y;
    printf("输出变量x=%d,y=%d\n",x,y); //输出变量x=0,y=0
    // 给外部变量(全局变量)x 和 y 赋值
    x = 1;
    y = 2;
    printf("输出变量x=%d,y=%d\n",x,y); // 输出变量x=1,y=2
    return x+y;
}


int  main(){

//声明 定义变量result 没有初始化
    int result;
    printf("输出变量result=%d\n",result); //输出变量result=6422352
    // 调用函数 addtwonum
    result = addtwonum();
    printf("输出变量result=%d",result); // 输出变量result=3

    return 0;

}

在一个源文件中引用另外一个源文件中定义的变量,我们只需在引用的文件中将变量加上 extern 关键字的声明即可。

addtwonum.c 文件代码:

#include <stdio.h>
/*外部变量声明*/
extern int x1 ;
extern int y1 ;
int addtwonum1()
{
    return x1+y1;
}

test.c 文件代码:

#include <stdio.h>

/*定义两个全局变量*/
int x1=1;
int y1=2;
int addtwonum1();
int main(){
    int result;
    result = addtwonum1();
    printf("输出变量result=%d",result); // 输出变量result=3
    return 0;
}

枚举类型:

 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。

void 类型:

类型说明符 void 表示没有值的数据类型,通常用于函数返回值。表示类型的缺失。

派生类型:

 包括数组类型、指针类型和结构体类型。

  常量

常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量

常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,枚举常量。

1.几种常见的常量定义

1.整数常量

 整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),
L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。

212         /* 合法的 */
215u        /* 合法的 */
0xFeeL      /* 合法的 */
078         /* 非法的:8 不是八进制的数字 */
032UU       /* 非法的:不能重复后缀 */
85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

 整数常量可以带有一个后缀表示数据类型

int myInt = 10;
long myLong = 100000L;
unsigned int myUnsignedInt = 10U;

2.浮点常量

浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。

当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
510E          /* 非法的:不完整的指数 */
210f          /* 非法的:没有小数或指数 */
.e55          /* 非法的:缺少整数或分数 */


浮点数常量可以带有一个后缀表示数据类型
float myFloat = 3.14f;
double myDouble = 3.14159;

3.字符常量

字符常量是括在单引号中,例如,'x' 可以存储在 char 类型的简单变量中。

字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。

字符常量的 ASCII 值可以通过强制类型转换转换为整数值。

char myChar = 'a';
int myAsciiValue = (int) myChar; // 将 myChar 转换为 ASCII 值 97

4.字符串常量

字符串字面值或常量是括在双引号 " " 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。

字符串常量在内存中以 null 终止符 \0 结尾。

char myString[] = "Hello, world!"; //系统对字符串常量自动加一个 '\0'

2. 定义常量

 1.使用 #define 预处理器定义常量

  #define 可以在程序中定义一个常量,它在编译时会被替换为其对应的值。

   #define 常量名 常量值
 
  #define  PI      3.14159  //在程序中使用该常量时,编译器会将所有的 PI 替换为 3.14159。

#include <stdio.h>
 
#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'
 
int main()
{
 
   int area;  
  //在程序中使用LENGTH  WIDTH常量时,编译器会将所有的LENGTH 替换为 10   WIDTH  替换为 5 。 
   area = LENGTH * WIDTH;
   printf("value of area : %d", area); //value of area : 50
    printf("%c", NEWLINE);
 
   return 0;
}

2.使用 const 声明指定类型的常量

const 关键字用于声明一个只读变量,即该变量的值不能在程序运行时修改。   

  const 数据类型 常量名 = 常量值;
   const int MAX_VALUE = 100;  在程序中使用该常量时,其值将始终为100,并且不能被修改。 const 声明常量要在一个语句内完成。

 

#include <stdio.h>
 
int main()
{
   const int  LENGTH = 10;//定义常量LENGTH 
   const int  WIDTH  = 5; //定义常量WIDTH  
   const char NEWLINE = '\n'; //定义常量NEWLINE 
   int area;  
   
   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);
 
   return 0;
}

3.#define 与 const 区别

 通常情况下,建议使用 const 关键字来定义常量,因为它具有类型检查和作用域的优势,而 #define 仅进行简单的文本替换,可能会导致一些意外的问题。

#define 预处理指令和 const 关键字在定义常量时有一些区别:

(1)替换机制:#define 是进行简单的文本替换,而 const 是声明一个具有类型的常量。#define 定义的常量在编译时会被直接替换为其对应的值,而 const 定义的常量在程序运行时会分配内存,并且具有类型信息。

(2)类型检查:#define 不进行类型检查,因为它只是进行简单的文本替换。而 const 定义的常量具有类型信息,编译器可以对其进行类型检查。这可以帮助捕获一些潜在的类型错误。

(3)作用域:#define 定义的常量没有作用域限制,它在定义之后的整个代码中都有效。而 const 定义的常量具有块级作用域,只在其定义所在的作用域内有效。

(4)调试和符号表:使用 #define 定义的常量在符号表中不会有相应的条目,因为它只是进行文本替换。而使用 const 定义的常量会在符号表中有相应的条目,有助于调试和可读性。

存储类

auto 存储类

auto 存储类是所有局部变量默认的存储类。
auto 只能用在函数内,即 auto 只能修饰局部变量。
定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。

{
   int mount;
   auto int month;
}

register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),
 且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
register 存储类定义存储在寄存器,所以变量的访问速度更快,但是它不能直接取地址,因为它不是存储在 RAM 中的。
在需要频繁访问的变量上使用 register 存储类可以提高程序的运行速度。
 寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

{
   register int  miles;
}

 static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。
因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
 全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。
静态变量在程序中只被初始化一次,即使函数被调用多次,该变量的值也不会重置。

#include <stdio.h>
 
/* 函数声明 */
void func1(void);
 
static int count=10;        /* 全局变量 - static 是默认的 */
 
int main()
{
  while (count--) {
      func1();
  }
  return 0;
}
 
void func1(void)
{
/* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
 * 每次调用函数 'func1' 'thingy' 值不会被重置。
 */                
  static int thingy=5;
  thingy++;
  printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}

extern 存储类

extern 存储类用于定义在其他文件中声明的全局变量或函数。当使用 extern 关键字时,不会为变量分配任何存储空间,
而只是指示编译器该变量在其他文件中定义。

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。
可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,

第一个文件:main.c

#include <stdio.h>
 
int count ;
extern void write_extern();
 
int main()
{
   count = 5;
   write_extern();
}
第二个文件:support.c

#include <stdio.h>

//第二个文件中的 extern 关键字用于声明已经在第一个文件 main.c 中定义的 count 
extern int count;
 
void write_extern(void)
{
   printf("count is %d\n", count);
}

运算符

算术运算符

关系运算符

逻辑运算符

位运算符

赋值运算符

杂项运算符(重要)

运算符描述实例
sizeof()返回变量的大小。sizeof(a) 将返回 4,其中 a 是整数。
&返回变量的指针地址。&a; 将给出变量a的实际指针地址。
*指向一个变量值。*a; 将指向一个变量值a。
? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y

函数

 返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
  函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
  参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。
       参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
  函数主体:函数主体包含一组定义函数执行任务的语句。
 
  return_type function_name( parameter list )
{
   body of the function
}

函数声明

函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
  return_type function_name( parameter list );
  int max(int num1, int num2);
  int max(int, int);

调用函数

当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
  调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。

#include <stdio.h>
 
/* 函数声明 */
int max(int num1, int num2);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
   int ret;
 
   /* 调用函数来获取最大值 */
   ret = max(a, b);
 
   printf( "Max value is : %d\n", ret );
 
   return 0;
}
 
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

 函数参数

 如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数
  形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
  当调用函数时,有两种向函数传递参数的方式:
  默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数。

调用类型描述
传值调用该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
引用调用通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作

  

传值方式调用函数

向函数传递参数的传值调用方法,把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
  默认情况下,C 语言使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。

 通过传递实际参数a b 来调用函数 swap(int x, int y)

#include <stdio.h>
 
/* swap函数声明 */
void swap(int x, int y);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
 
   printf("交换前,a 的值: %d\n", a );
   printf("交换前,b 的值: %d\n", b );
 
   /* 调用函数来交换值 */
   //todo 通过传递实际参数a b 来调用函数 swap(int x, int y)
   swap(a, b);
 
   printf("交换后,a 的值: %d\n", a );
   printf("交换后,b 的值: %d\n", b );
 
   return 0;
}

/* 函数定义 */
void swap(int x, int y)
{
   int temp;

   temp = x; /* 保存 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 temp 赋值给 y */
  
   return;
}

 引用方式调用函数

通过引用传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
  传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。

 通过引用传值&a &b 来调用函数 swap(int *x, int *y)

#include <stdio.h>
 
/* 函数声明 */
void swap(int *x, int *y);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
 
   printf("交换前,a 的值: %d\n", a );
   printf("交换前,b 的值: %d\n", b );
 
   /* 调用函数来交换值
     &a 表示指向 a 的指针,即变量 a 的地址 
      &b 表示指向 b 的指针,即变量 b 的地址 
   */
//todo 通过引用传值&a &b 来调用函数 swap(int *x, int *y):
   swap(&a, &b);
 
   printf("交换后,a 的值: %d\n", a );
   printf("交换后,b 的值: %d\n", b );
 
   return 0;
}


/* 函数定义 */
void swap(int *x, int *y)
{
   int temp;
   temp = *x;    /* 保存地址 x 的值 */
   *x = *y;      /* 把 y 赋值给 x */
   *y = temp;    /* 把 temp 赋值给 y */
  
   return;
}

作用域规则

 任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量:
    1.在函数或块内部的局部变量
    2.在所有函数外部的全局变量
    3.在形式参数的函数参数定义中

  局部变量

 在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。

#include <stdio.h>
 
// 变量 a、b 和 c 是 main() 函数的局部变量
int main ()
{
  /* 局部变量声明 */
  int a, b;
  int c;
 
  /* 实际初始化 */
  a = 10;
  b = 20;
  c = a + b;
 
  printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
 
  return 0;
}


  全局变量

 全局变量在声明后整个程序中都是可用的。
  全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。
全局变量可以被任何函数访问。

#include <stdio.h>
 
/* 全局变量g声明 */
int g;
 
int main ()
{
  /* 局部变量a b 声明 */
  int a, b;
 
  /* 全局变量g 局部变量a b 的实际初始化 */
  a = 10;
  b = 20;
  g = a + b;
 
  printf ("value of a = %d, b = %d and g = %d\n", a, b, g);
 
  return 0;
}

局部变量和全局变量的名称可以相同,会使用局部变量值,全局变量不会被使用。

#include <stdio.h>
 
/* 全局变量g声明 初始化 */
int g = 20;
 
int main ()
{
  /* 局部变量g声明 初始化 */
  int g = 10;
 
  printf ("value of g = %d\n",  g); // 输出g=10
 
  return 0;
}


  形式参数

函数的参数( 形式参数),被当作该函数内的局部变量,如果与全局变量同名它们会优先使用。

#include <stdio.h>
 
/* 全局变量a声明 并初始化20 */
int a = 20;
 
int main ()
{
   /* 在主函数中声明局部变量a(与全局变量a同名) b c 并初始化  */
    int a = 10;
    int b = 20;
    int c = 0;
    //函数sum(int, int)声明   行参int int
    int sum(int, int);
    printf ("main() a= %d\n",  a); // main() a= 10 使用局部变量a的值10
    c=sum(a,b);//c=30
    printf ("main() c = %d\n",  c); //main() c = 30
    return 0; 
}

/* 添加两个整数的函数 */
int sum(int a, int b)
{
    printf ("sum() a = %d\n",  a); //sum() a = 10 使用局部变量a的值10
    printf ("sum() b = %d\n",  b); // sum() b = 20 使用局部变量b的值20

    return a + b;
}

全局变量与局部变量在内存中的区别

全局变量保存在内存的全局存储区中,占用静态的存储单元;

局部变量保存在栈中,只有在所在函数被调用时才动态地为局部变量分配存储单元。

更多内容可参考:C/C++ 中 static 的用法全局变量与局部变量

初始化局部变量和全局变量

当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化默认值

数据类型全局变量初始化默认值
int0
char'\0'
float0
double0
pointerNULL

占位符

    %d  d ==int 整形的占位符
    %lf  lf == long float 的占位符  
    %f  f == float 类型占位符          
    %c  c == 字符占位符            
    %s  s == 字符串占位符

   %p == 输出某某的(指针)地址 的占位符 

  概念

 数值==数字==实体建筑房子(可以一样重复)

 指针 == 地址==存放内存地址==指针地址==家庭的门牌号邮寄地址(唯一的)

 

    int  i = 100;
    int* ip=&i;
  & = 取出某某的(指针)地址=内存指针地址ip=&i=0061FF18     

 int  i = 100;
    printf("输出i的数值是:%d\n", i);// 输出i的数值是:100
    printf("输出i的内存指针地址是=%p\n", &i); // 输出i的内存指针地址是=0061FF18
    printf("输出i的数值是:%d\n", *(&i)); // 输出i的数值是:100

    int* ip=&i;
    printf("输出i的内存指针地址是=%p\n", ip);  //  输出i的内存指针地址是=0061FF18

  

   *  获取 某某的(指针)地址 的所对应的数值
   *(指针地址)      取出 某某的(指针)地址的 所对应的数值

    int  i = 100;
    int* ip=&i;
    *ip=666;

   数值i=*ip=*(&i)=666=内存指针地址ip的对应的数值

 int number_int = 100;
 int*  intP = &number_int;
//*  获取 某某的(指针)地址 的所对应的数值
    // *(指针地址)      取出 某某的(指针)地址的 所对应的数值
    printf("number_int的(指针)地址的所对应的数值是:%d\n", *(&number_int)); // number_int的(指针)地址的所对应的数值是:100
     // *(指针地址)    取出 某某的(指针)地址的 所对应的数值
    printf("number_int的(指针)地址的所对应的数值是:%d\n", *(intP));  //number_int的(指针)地址的所对应的数值是:100

 // C语言不支持函数重载, 要先声明函数 不能写在 main的下面,会报错. 参数pInt是个指针地址
void change(int* pInt);


int mainT7(){

    int  i = 100;
    printf("输出i的数值是:%d\n", i);// 输出i的数值是:100
    printf("输出i的内存指针地址是=%p\n", &i); // 输出i的内存指针地址是=0061FF18
    printf("输出i的数值是:%d\n", *(&i)); // 输出i的数值是:100

    int* ip=&i;
    printf("输出i的内存指针地址是=%p\n", ip);  //  输出i的内存指针地址是=0061FF18
    change(ip);
    printf("再次输出i的内存指针地址是=%p\n", ip); //再次输出i的内存指针地址是=0061FF18
    printf("再次输出i的数值是:%d\n", i); //再次输出i的数值是:666
    printf("再次输出i的数值是:%d\n",  *ip); // 再次输出i的数值是:666
    printf("再次输出i的数值是:%d\n", *(&i)); // 再次输出i的数值是:666

    //todo 综上可知: 内存指针地址ip=&i=0061FF18
    //todo 综上可知: 数值i=*ip=*(&i)=666=内存指针地址ip的对应的数值
    return 0;
}


// 再实现change函数 参数pInt 是个内存指针地址    写在 main的下面
void change(int* pInt){
    printf("change函数的参数pInt内存指针地址是=%p\n", pInt); // change函数的参数pInt内存指针地址是=0061FF18
    //修改内存指针地址pInt的对应的数值*pInt = 666=*(&i)
    *pInt=666;

}

       int number_int = 100;

      int*  intP = &number_int;

     int*   double*   叫 指针地址的类型
     intP,  doubleP  指针地址的变量名称   也叫 指针地址的变量别名/指针地址的变量/指针变量
     &number_int     number_int的(指针)地址

    // int*   double*   叫 指针地址的类型
    //  intP,  doubleP  指针地址的变量名称   也叫 指针地址的变量别名/指针地址的变量/指针变量
    //  &number_int     number_int的(指针)地址
    int number_int = 100;
    printf("number_int的值是:%d\n", number_int); //number_int的值是:100
    // &   某某的(指针)地址
    printf("number_int的(指针)地址是:%p\n", &number_int); //number_int的(指针)地址是:0061FF1C
 int*  intP = &number_int;
    printf("number_int的(指针)地址是:%p\n", intP);   // number_int的(指针)地址是:0061FF18

int main(){
    int num = 999;
    // 一级指针 *  :  num_p = 数值num的指针地址
    int * num_p = &num;
    printf("数值num的指针地址(即一级指针 *)num_p=%p\n",num_p); // 数值num的指针地址(即一级指针 *)num_p=0061FF18

    // 二级指针** : num_p_p = 一级指针num_p的 指针地址
    int**  num_p_p = &num_p;
    printf("一级指针num_p的 指针地址(即二级指针**)num_p_p=%p\n",num_p_p);// 一级指针num_p的 指针地址(即二级指针**)num_p_p=0061FF14

    // 三级指针*** : num_ppp = 二级指针num_p_p的  指针地址
    int *** num_ppp = &num_p_p;
    printf("二级指针num_p_p的  指针地址(即三级指针***)num_ppp=%p\n",num_ppp);  // 二级指针num_p_p的  指针地址(即三级指针***)num_ppp=0061FF10


    printf("数值num的数值=%d\n",num); //数值num的数值=999
    printf("一级指针num_p所对应的数值*num_p=%d\n",*num_p); // 一级指针num_p所对应的数值*num_p=999
    printf("二级指针num_p_p所对应的数值**num_p_p=%d\n",**num_p_p); // 二级指针num_p_p所对应的数值**num_p_p=999
    printf("三级指针num_ppp所对应的数值***num_ppp=%d\n",***num_ppp);// 三级指针num_ppp所对应的数值***num_ppp=999
  return 0;
}

数组

C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。

数组的声明并不是声明一个个单独的变量,比如 runoob0、runoob1、...、runoob99,而是声明一个数组变量,比如 runoobArr,然后使用 runoobArr[0]、runoobArr[1]、...、runoobArr[99] 来代表一个个单独的变量。

所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。

  声明数组(没有初始化)

   在 C 中要声明一个数组,需要指定元素的类型和元素的数量,
arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C 数据类型。
  type arrayName [ arraySize ];
  double balance[10];  // 声明一个类型为 double 的包含 10 个元素的数组 balance

 初始化数组

在 C 中,您可以逐个初始化数组,也可以使用一个初始化语句,
大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
如果您省略掉了数组的[]数目大小,数组的大小则为初始化时元素的个数。  
 double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
 您将创建一个数组balance,它与前一个实例中所创建的数组balance是完全相同的。 数组balance中某个元素赋值的实例:
 把数组中第五个元素的值赋为 50.0。所有的数组都是以 0 作为它们第一个元素的索引,也被称为基索引,数组的最后一个索引是数组的总大小减去 1。
 balance[4] = 50.0;

 

 访问数组元素

数组元素可以通过数组名称balance加索引[9]进行访问。元素的索引是放在方括号内,跟在数组名称的后边
把数组balance中第 10 个元素的值赋给 salary 变量。
  double salary = balance[9];

#include <stdio.h>
 
int main ()
{
   //什么数组n n 是一个包含 10 个整数的数组
   int n[ 10 ];  
   int i,j;
 
   /* 初始化数组元素 */         
   for ( i = 0; i < 10; i++ )
   {
      n[ i ] = i + 100; /* 设置元素 i 为 i + 100 */
   }
   
   /* 输出数组中每个元素的值 */
   for (j = 0; j < 10; j++ )
   {
      printf("Element[%d] = %d\n", j, n[j] );
   }
 
   return 0;
}

/*
n[0]= Element[0] =100
n[1]= Element[1] = 101
n[2]= Element[2] = 102
n[3]= Element[3] = 103
n[4]= Element[4] = 104
n[5]= Element[5] = 105
n[6]= Element[6] = 106
n[7]= Element[7] = 107
n[8]= Element[8] = 108
n[9]= Element[9] = 109

*/

获取数组长度

数组长度可以使用 sizeof 运算符来获取数组的长度
  int numbers[] = {1, 2, 3, 4, 5};
   int length = sizeof(numbers) / sizeof(numbers[0]);

 数组名

在 C 语言中,数组名表示数组的地址,即数组首元素的地址。当我们在声明和定义一个数组时,该数组名就代表着该数组的地址。
  myArray 是数组名,它表示整数类型的数组,包含 5 个元素。
  数组名本身是一个常量指针,意味着它的值是不能被改变的,一旦确定,就不能再指向其他地方。
  myArray 也代表着数组的地址,即第一个元素的地址。数组名会自动转换为指向数组首元素的指针,这意味着我们可以直接将数组名用于指针运算。
  数组指针地址=myArray=&myArray=&myArray[0]
 
  int myArray[5] = {10, 20, 30, 40, 50};
  int *ptr = myArray=&myArray[0];  //ptr 指针变量被初始化为 myArray 的地址,即数组的第一个元素的地址。

C 中数组详解

概念描述
多维数组C 支持多维数组。多维数组最简单的形式是二维数组。
传递数组给函数您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
从函数返回数组C 允许从函数返回数组。
指向数组的指针您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。
静态数组与动态数组态数组在编译时分配内存,大小固定,而动态数组在运行时手动分配内存,大小可变。

  多维数组


  传递数组给函数

   如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,
  这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。 

 方式 1
  形式参数是一个指针param

  void myFunction(int *param){}


  方式 2
  形式参数是一个已定义大小的数组param:

void myFunction(int param[10]){}
  方式 3
  形式参数是一个未定义大小的数组param:

 void myFunction(int param[]){}

#include <stdio.h>
 
/* 函数声明 */
double getAverage(int arr[], int size);
 
int main ()
{
   /* 带有 5 个元素的整型数组 */
   int balance[5] = {1000, 2, 3, 17, 50};
   double avg;
 
   /* 传递一个指向数组的指针作为参数 */
   avg = getAverage( balance, 5 ) ;
 
   /* 输出返回值 */
   printf( "平均值是: %f ", avg );
    
   return 0;
}
 
double getAverage(int arr[], int size)
{
  int    i;
  double avg;
  double sum=0;
 
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
 
  avg = sum / size;
 
  return avg;
}


  从函数返回数组

C 语言不允许返回一个完整的数组作为函数的参数。但是,您可以通过指定不带索引的数组名来返回一个指向数组的指针。

如果您想要从函数返回一个一维数组,您必须声明一个返回指针的函数,

 int * myFunction()
{
  static   int r[10];
   return r;
}



//返回一个数值r
int * getRandom() {
    // C 不支持在函数外返回局部变量的地址p,除非定义局部变量为 static 变量
    static   int r[10];
    /* 设置种子 */
    srand( (unsigned)time( NULL ) );
    for ( int i = 0; i < 10; ++i) {
        r[i]=rand();
        printf( "r[%d] = %d\n", i, r[i]);
    }
    return r;
}
/**
r[0] = 23513
r[1] = 12048
r[2] = 28118
r[3] = 9014
r[4] = 7470
r[5] = 21353
r[6] = 11522
r[7] = 12621
r[8] = 8026
r[9] = 6616
 */



int main(){
 /* 一个指向整数的指针p */
    // C 不支持在函数外返回局部变量的地址p,除非定义局部变量为 static 变量
    static int *p;
     p=getRandom();
    printf("先输出p对应的数值*p=%d\n",*p); //先输出p对应的数值*p=26158
      for(int i=0;i<10;i++){
         printf( "*(p + %d) : %d\n", i, *(p + i));
     }
/**
 *(p + 0) : 26439
*(p + 1) : 8828
*(p + 2) : 10863
*(p + 3) : 8399
*(p + 4) : 8753
*(p + 5) : 759
*(p + 6) : 14998
*(p + 7) : 11223
*(p + 8) : 24043
*(p + 9) : 2971

 */
return 0;
}


  指向数组的指针

通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。
数组名本身是一个常量指针,意味着它的值是不能被改变的,一旦确定,就不能再指向其他地方。

//定义一个指针p 没有初始化
 double *p;
 //balance 是一个指向 &balance[0] 的指针  即数组 balance 的第一个元素的地址balance=&balance[0]
​ double balance[50];
 //给指针p 指向一个指针地址。即指向数组 balance 的第一个元素的指针地址balance=&balance[0]
  p = balance 

一旦您把数组的第一个元素的地址balance存储在 p 中,您就可以使用 *p、*(p+1)、*(p+2) 等来访问数组的所有元素值。

int main(){

//定义一个带有 5 个元素的double类型的数组balance
double balance[5]={1000.0, 2.0, 3.4, 17.0, 50.0};
//定义一个指向 double 类型的指针p ,没有初始化 (野指针)
double *p;

//给指针p 指向一个指针地址。即指向数组 balance 的第一个元素的指针地址balance=&balance[0]
  p = balance;

//todo 输出数组中每个元素的值
//方式一:使用指针p 遍历挪动指针p+i,获取数组的所有的元素值
for(int i=0;i<5;i++){
  printf("遍历挪动指针p+%d,获取数组的元素值*(p+%d)=%f\n",i,i,*(p+i));
}
/**
 遍历挪动指针p+0,获取数组的元素值*(p+0)=1000.000000
遍历挪动指针p+1,获取数组的元素值*(p+1)=2.000000
遍历挪动指针p+2,获取数组的元素值*(p+2)=3.400000
遍历挪动指针p+3,获取数组的元素值*(p+3)=17.000000
遍历挪动指针p+4,获取数组的元素值*(p+4)=50.000000
 */

//方式一:使用数组的第一个元素的指针地址balance,  遍历挪动数组的第一个元素的指针地址balance+i,  获取数组的所有的元素值 *(balance+i)
    for(int i=0;i<5;i++){
        printf("遍历挪动数组的第一个元素的指针地址balance+%d,获取数组的所有的元素值 *(balance+%d)=%f\n",i,i,*(balance+i));
    }
/**
 遍历挪动数组的第一个元素的指针地址balance+0,获取数组的所有的元素值 *(balance+0)=1000.000000
遍历挪动数组的第一个元素的指针地址balance+1,获取数组的所有的元素值 *(balance+1)=2.000000
遍历挪动数组的第一个元素的指针地址balance+2,获取数组的所有的元素值 *(balance+2)=3.400000
遍历挪动数组的第一个元素的指针地址balance+3,获取数组的所有的元素值 *(balance+3)=17.000000
遍历挪动数组的第一个元素的指针地址balance+4,获取数组的所有的元素值 *(balance+4)=50.000000
 */
return  0;

}

  静态数组与动态数组

 静态数组 3T3

静态数组:在编译时声明并分配好栈内存空间 ,在声明数组时需要指定数组的长度,数组大小固定,运行时数组大小无法改变。
            静态数组的生命周期与作用域相关,如果在函数内部声明静态数组,其生命周期为整个函数执行期间;如果在函数外部声明静态数组,其生命周期为整个程序的执行期间。

静态开辟的栈区的内存小,很容易溢出.执行调用某个函数  就会进栈 静态开辟栈区 定义的int arr[5];   int i; 都是栈成员
  函数执行完毕会弹栈  会释放所有的栈成员

 栈区:占用内存大小 最大值: 大概最大 2M  大于2M会栈溢出  跟平台有关系的
 堆区:占用内存大小 最大值: 大概最大占80%  40M没有任何问题,基本上不用担心 堆区很大的
                                                  大概80%: Windows系统 给我们的编译器给予的空间  的 百分之百八十

静态数组的声明和初始化示例:
int staticArray[5]; // 静态数组声明,大小固定 5*4=20字节
int staticArray[] = {1, 2, 3, 4, 5}; // 静态数组声明并初始化

静态数组, 使用 sizeof 运算符来获取数组长度,
int array[] = {1, 2, 3, 4, 5};
//  sizeof(array) 返回整个数组所占用的字节数, sizeof(array[0]) 返回数组中单个元素的字节数, 将两者相除,就得到了数组的长度。  
int length = sizeof(array) / sizeof(array[0]);

#include <stdio.h>

int main() {
//声明并初始化了一个静态数组 staticArray 包含了 5 个整数元素, 
    int staticArray[] = {1, 2, 3, 4, 5};  
    int length = sizeof(staticArray) / sizeof(staticArray[0]);

    
    for (int i = 0; i < length; i++) {
        printf("%d\n ", staticArray[i]); // 1 2 3 4 5
    }
   

    return 0;
}

int main(){
    //兆=M
    //1兆=1M=1*1024 * 1024
    //10兆=10M=10 * 1024 * 1024
    //int类型是4个字节

    // int类型的数组arr 占用内存大小 4 * 10*1M = 40M>2M   栈会溢出
    // int arr[10 * 1024 * 1024];

    // int类型的数组arr 占用内存大小 4 *1*1M = 4M >2M 栈会溢出
    // int arr[1 * 1024 * 1024];

    // int类型的数组arr  占用内存大小 4* 0.2*1M=0.8M<2M 不会栈溢出
    int arr[(int)(0.2 * 1024 * 1024)];

return (0);
}

#include <stdio.h>
#include <unistd.h> // 小写sleep的


//todo  执行调用某个函数  就会进栈 静态开辟栈区 定义的int arr[5];   int i; 都是栈成员
void staticAction() {
    int arr[5]; //  创建栈成员 int类型数值4*5=20字节
    for (int i = 0; i <5 ; ++i) {
      arr[i]=i;
      printf("输出数组arr的元素数值%d\n",arr[i]);
      printf("输出数值arr的元素数值%d的内存地址=%p\n",*(arr+i),arr+i);
    }

}//todo 函数执行完毕会弹栈  会释放所有的栈成员


//  静态开辟栈区
int main(){
 //执行调用某个函数  就会进栈 静态开辟栈区
 staticAction();
  return (0);
}

动态数组

在运行时通过动态内存分配数组(如 malloc 和 calloc)大小 ,在运行时可以根据需要进行调整数组大小 。
  动态数组的生命周期由程序员控制。   使用完数组后手动释放内存,以避免内存泄漏 ,以避免内存泄漏和访问无效内存的问题。
 使用 malloc、calloc 等函数来申请内存,并使用 free 函数来释放内存。
 使用 realloc 函数来重新分配内存,并改变数组的大小。
 
 

 void * 可以任意转变为   int*  double *类型指针
   malloc  在堆区给内存地址动态开辟一个内存空间
    void * malloc (size_t)  

在堆区开辟一个内存地址malloc_p , 内存地址malloc_p占用的 内存空间大小=1M=1*1M
   int* malloc_p = (int *)malloc(1 * 1024 * 1024);

在堆区开辟的内存地址malloc_p所占用的内存空间大小,必须手动释放free掉,否则占用的内存空间会越来越大   free释放后malloc_p就成了悬空指针
     free(malloc_p);
   让内存地址malloc_p重新指向一个新的内存地址00000   malloc_p = NULL=00000000 就解决了malloc_p成为悬空指针的问题
   malloc_p = NULL;


  int size = 5;
  // 使用 malloc 函数动态分配了一个整型数组
int *dynamicArray = (int *)malloc(size * sizeof(int));  
 free(dynamicArray); // 动态数组内存释放

 注意:动态数组的使用需要注意内存管理的问题,确保在不再需要使用数组时释放内存,避免内存泄漏和访问无效的内存位置

void dynamicAction() {
    //定义一个内存指针地址 p  但是没有地址指向  是个野指针
    // C的开发过程中,不能出现, 野指针(不是悬空指针)   没有地址的,空的
    int*  p;
    printf("输出p内存地址=%p\n",p); // 输出p内存地址=00402B1B
   //让内存地址p指向一个新的内存地址   p=NULL=00000000 就解决了  内存地址p的野指针的问题
    p=NULL;
    printf("输出p内存地址=%p\n",p); //输出p内存地址=00000000

    //  void * malloc (size_t)   void * 可以任意转变为   int*  double *类型指针
 // todo   在堆区开辟一个内存地址malloc_p , 内存地址malloc_p占用的 内存空间大小=1M=1*1M
 int* malloc_p = malloc(1 * 1024 * 1024);
 printf("dynamicAction函数,malloc_p自己的内存地址&malloc_p=%p,在堆区开辟的内存地址(打印的内存地址会不断变化不重复)malloc_p=%p\n", &malloc_p, malloc_p);
 // dynamicAction函数,malloc_p自己的内存地址&malloc_p=0061FF08,在堆区开辟的内存地址(打印的内存地址会不断变化不重复)malloc_p=007A5020

    // todo C工程师,在堆区开辟的内存地址malloc_p所占用的内存空间大小,必须手动释放free掉,否则占用的内存空间会越来越大   free释放后malloc_p就成了悬空指针
     free(malloc_p);
    // 此时的 内存地址malloc_p成了 悬空指针
    printf("dynamicAction函数  释放后malloc_p在堆区的内存地址(都是一样重复的,成了悬空指针):%p\n", malloc_p);  // dynamicAction函数  释放后malloc_p在堆区的内存地址(都是一样重复的,成了悬空指针):0096A020
    // todo 让内存地址malloc_p重新指向一个新的内存地址00000   malloc_p = NULL=00000000 就解决了malloc_p成为悬空指针的问题
    malloc_p = NULL;
    printf("dynamicAction函数   释放后堆区里的内存地址malloc_p会指向一个新的内存地址=%p\n", malloc_p);   // dynamicAction函数   释放后堆区里的内存地址malloc_p会指向一个新的内存地址=00000000
}


int mainT34() {
dynamicAction(); // 调用开辟20
return 0;
}
静态数组和动态数组的使用场景 T35

什么场景下 在栈区里静态开辟内存空间大小?   什么场景下在堆区里动态开辟内存空间大小?
  内存地址在栈区里静态开辟的内存空间大小,是不能修改的,如果不需要动态修改内存空间大小,就是用使用 在栈区里静态开辟内存空间大小
 尽量使用 静态开辟内存空间大小,如果实在是需要动态改变内存空间大小,才使用动态开辟内存空间大小

int mainT35(){
 //todo 在栈区里静态开辟一个 数组 static_arr  指针地址static_arr   占用内存空间大小 4*6=24字节  不能修改内存空间大小
int  static_arr [6];
printf("输出数组static_arr的内存指针地址static_arr=%p\n",static_arr);  //输出数组static_arr的内存指针地址static_arr=0061FF08
printf("输出数组static_arr的内存指针地址&static_arr=%p\n",&static_arr); //输出数组static_arr的内存指针地址&static_arr=0061FF08


//==========给内存地址在堆区里动态开辟内存空间大小 =================================================
//定义一个int类型的数值num  这里必须初始化数值为10
int num = 3;
printf("输出num数值=%d\n",num); //输出num数值=5
printf("输出数值num的内存地址&num=%p\n",&num); //输出数值num的内存地址&num=0061FF00
 printf("请输入num的数值:");
// 获取用户输入的数值
 scanf("%d", &num); //todo 必须传入指针地址&num  使用num的指针地址&num去修改num所对应的数值
 printf("输出num数值=%d\n",num); //输出num数值=5
 printf("输出数值num的内存地址&num=%p\n",&num);// 输出数值num的内存地址&num=0061FEFC
 printf("输出数值num的长度=%d\n", sizeof(int)*num ); // 输出数值num的长度=20

    //todo 在堆区 创建一个数组arr  其内存地址arr=&arr   动态开辟内存空间大小 sizeof(int) * num
    //  todo void * malloc (size_t)   void * 可以任意转变为   int*  double *
    //todo  数组arr  循环每添加一个元素值  就动态开辟一次内存空间大小

   int*  arr =  malloc(sizeof(int) *num); //  num的指针地址=*num
    printf("输出arr自己的内存地址&arr=%p\n", &arr); // 输出arr自己的内存地址&arr=0061FEF8
   printf("输出arr在堆区开辟的内存地址arr=%p\n", arr);//  输出arr在堆区开辟的内存地址arr=00672978
   printf("输出arr所对应的数值*arr=%d\n", *arr); // 输出arr所对应的数值*arr=6758944

    //在堆区 开辟一个新的内存地址
    int print_num;
    // 循环接收
    for(int i=0;i<num;++i){
        printf("请输入第%d个的值:", i);
        // 获取用户输入的值
        scanf("%d",&print_num); //todo 必须传入内存指针地址  &print_num
        arr[i]=print_num;
        printf("arr[%d]的数值=%d\n", i, arr[i]);//内部隐士处理  arr[i]= *(arr + i)
        printf("arr[%d]的数值=%d\n", i, *(arr + i));
        printf("第%d个元素的内存地址指针arr+%d=%p,所对应的数值*(arr+%d)=%d\n", i,i, arr + i,i, *(arr + i));
    }

    /**
  请输入第0个的值:0
arr[0]的数值=0
arr[0]的数值=0
第0个元素的内存地址指针arr+0=00672978,所对应的数值*(arr+0)=0
请输入第1个的值:1
arr[1]的数值=1
arr[1]的数值=1
第1个元素的内存地址指针arr+1=0067297C,所对应的数值*(arr+1)=1
请输入第2个的值:2
arr[2]的数值=2
arr[2]的数值=2
第2个元素的内存地址指针arr+2=00672980,所对应的数值*(arr+2)=2
请输入第3个的值:3
arr[3]的数值=3
arr[3]的数值=3
第3个元素的内存地址指针arr+3=00672984,所对应的数值*(arr+3)=3
请输入第4个的值:4
arr[4]的数值=4
arr[4]的数值=4
第4个元素的内存地址指针arr+4=00672988,所对应的数值*(arr+4)=4
     */


    //for循环打印 数组 arr
    for(int i=0;i<num;++i){
        printf("输出元素%d的数值arr[%d]=%d\n",i,i,arr[i]); //内部隐士处理  arr[i]= *(arr + i)
    }

 /**
 输出元素0的数值arr[0]=0
输出元素1的数值arr[1]=1
输出元素2的数值arr[2]=2
输出元素3的数值arr[3]=3
输出元素4的数值arr[4]=4
     */

    return 0;
}
realloc  在堆区给内存地址动态开辟一个内存空间  T36
int mainT36() {
    int num=4;
    //  todo void * malloc (size_t)   void * 可以任意转变为   int*  double *
    int * arr = (int *) malloc(sizeof(int) * num);
    printf("输出旧数组arr的内存地址=%p\n",arr); //输出旧数组arr的内存地址=006C2978
    for (int i = 0; i < num; ++i) {
        //todo 给每个元素的内存地址arr+i   对应的每个元素的数值arr[i](内部隐士)=*(arr+i)=*&*&*&*&*&*(arr + i)=赋值1000+i
        arr[i] = 1000+i;
      printf("输出旧数组的元素值arr[%d]=%d,当前元素值的指针地址=%p\n",i,arr[i],arr+i);
    //或者这样  // &取出内存地址 *然后去值
  //  printf("输出旧数组的元素值arr[%d]=%d,当前元素值的指针地址=%p\n",i,*(arr+i),arr+i);
  //或者这样
   // printf("输出旧数组的元素值arr[%d]=%d,当前元素值的指针地址=%p\n",i,*&*&*&*&*&*(arr + i),arr+i);
    }
    printf("输出旧数组arr的内存地址=%p\n",arr); // 输出旧数组arr的内存地址=006C2978

    /**
输出旧数组arr的内存地址=00A22978
输出旧数组的元素值arr[0]=1000,当前元素值的指针地址=00A22978
输出旧数组的元素值arr[1]=1001,当前元素值的指针地址=00A2297C
输出旧数组的元素值arr[2]=1002,当前元素值的指针地址=00A22980
输出旧数组的元素值arr[3]=1003,当前元素值的指针地址=00A22984
输出旧数组arr的内存地址=00A22978
     */
 // =================================   在堆区开辟新的空间  加长空间大小========================
   // 新增 加长空间大小new_num
    int new_num=4;
    //todo   void *realloc (void * 前面开辟的旧的指针arr, size_t总大小);
    // 总大小size_t =  原来的大小 sizeof(int) * (num) :4+  新增加的大小sizeof(int)(new_num):4

  int* new_arr=(int*) realloc(arr, sizeof(int) * num+sizeof(int) * new_num);
    // new_arr != NULL 不等于0 我才进if  【非0即true】
  if(new_arr!=NULL){
      int j = num; // j=4开始 for循环遍历
      for(;j<(num+new_num);j++){ // 5 6 7 8
          arr[j]=1000+j;
          printf("输出新添加的元素值arr[%d]的值=%d, 新添加的元素值对应的的内存指针地址=%p\n",j,*(arr+j),arr+j);
      }
  }

    printf("新数组new_arr开辟的内存指针= %p\n", new_arr);

/**
输出新添加的元素值arr[4]的值=1004, 新添加的元素值对应的的内存指针地址=00AF2988
输出新添加的元素值arr[5]的值=1005, 新添加的元素值对应的的内存指针地址=00AF298C
输出新添加的元素值arr[6]的值=1006, 新添加的元素值对应的的内存指针地址=00AF2990
输出新添加的元素值arr[7]的值=1007, 新添加的元素值对应的的内存指针地址=00AF2994
新数组new_arr开辟的内存指针= 00AF2978
 */


//===========================================================================

// 新旧元素统一打印一遍
for(int i=0;i<num+new_num;++i){
    printf("新旧元素组合后统一打印:组合后的元素%d的值arr[%d]=%d,所对应的内存地址=%p\n",i,i,*(arr+i),arr+i);
}
/**
 新旧元素组合后统一打印:组合后的元素0的值arr[0]=1000,所对应的内存地址=00AF2978
新旧元素组合后统一打印:组合后的元素1的值arr[1]=1001,所对应的内存地址=00AF297C
新旧元素组合后统一打印:组合后的元素2的值arr[2]=1002,所对应的内存地址=00AF2980
新旧元素组合后统一打印:组合后的元素3的值arr[3]=1003,所对应的内存地址=00AF2984
新旧元素组合后统一打印:组合后的元素4的值arr[4]=1004,所对应的内存地址=00AF2988
新旧元素组合后统一打印:组合后的元素5的值arr[5]=1005,所对应的内存地址=00AF298C
新旧元素组合后统一打印:组合后的元素6的值arr[6]=1006,所对应的内存地址=00AF2990
新旧元素组合后统一打印:组合后的元素7的值arr[7]=1007,所对应的内存地址=00AF2994
 */


// 必须手动释放【规则】 就是把占用的内存空间大小给清除掉  释放前先进行判断之前是否释放过 否则会崩溃
// new_arr != NULL =true   重新开辟的堆空间是成功的
if(new_arr!=NULL){
    free(new_arr); // 进行释放
    new_arr = NULL; // 如果不赋值给NULL,就是悬空指针了
    arr = NULL; // 他还在指向那块空间,为了不出现悬空指针,指向NULL的空间
}else{ // new_arr != NULL =false    重新开辟的堆空间是失败的
    free(arr);
    arr = NULL;
}
    return 0;
}

 数组arr的内存地址

     int arr[]  = {10,20,30,40};
     数组arr自身就是一个内存地址arr==数组arr的内存地址&arr == 第一个元素arr[0]的内存地址&arr[0]=0061FF0C
     数组自身内存地址==arr=&arr=&arr[0]=0061FF0C
     反推数值:  *arr=*arr_p=*(&arr[0])=10

    
     数组arr的内存地址
    数组的第一个元素值i=10,元素值10的内存地址=0061FF0C
    数组的第二个元素值i=20,元素值20的内存地址=0061FF10
    数组的第三个元素值i=30,元素值30的内存地址=0061FF14
    数组的第四个元素值i=40,元素值40的内存地址=0061FF18
    
    
      数组的指针地址arr往后挪动i个位置  : arr_p = arr_p +i     
      数组的指针arr地址往前挪动i个位置: arr_p =arr_p -i  
     int* arr_p = arr;
     数组arr自身就是一个内存地址== 第一个元素arr[0]的内存地址&arr[0]=0061FF0C
     数组arr的内存地址arr_p所对应的数值*arr_p=10=元素一内存地址&arr[0]所对应的数值*(&arr[0])=10
    
    arr_p =arr_p+1;
    指针地址移动到下一个==元素二的内存指针地址=0061FF10
    数值arr的第二个元素的内存地址的所对应的数值*arr_p=20=元素二内存地址&arr[1]所对应的数值*(&arr[1])=20
    
    
    arr_p=arr_p+2;
    当前arr_p是第二个元素的内存指针地址,往下移动二个==元素四的内存指针地址=0061FF18
    数值arr的第四个元素的内存指针地址的所对应的数值*arr_p=40=元素四内存地址&arr[3]所对应的数值*(&arr[3])=40

     arr_p=arr_p-3;
     当前arr_p是第四个元素的内存指针地址,往前移动三位==元素一的内存指针地址=0061FF0C
     当前指针地址arr_p所对应的数值*arr_p=10=元素一内存地址&arr[0]所对应的数值*(&arr[0])=10
   

int main(){

    // 定义数组
    // int [] arr = {1,2,3,4}; 错误的写法
    int arr[]  = {10,20,30,40};

    
    // Linux上没有问题,Clion没有问题,Mac没有问题
    int i = 0;
    for (i = 0; i < 4; ++i) {
        printf("输出数组的每个元素值i=%d,元素值%d的内存地址=%p\n", arr[i],arr[i],&arr[i]);
    }

    /*
     输出数组的每个元素值i=10,元素值10的内存地址=0061FF0C
输出数组的每个元素值i=20,元素值20的内存地址=0061FF10
输出数组的每个元素值i=30,元素值30的内存地址=0061FF14
输出数组的每个元素值i=40,元素值40的内存地址=0061FF18
     */

     // todo 数组arr的内存地址
    // todo 数组arr自身就是一个内存地址arr==数组arr的内存地址&arr == 第一个元素arr[0]的内存地址&arr[0]=0061FF0C
    //todo 数组自身内存地址==arr=&arr=&arr[0]=0061FF0C

    printf("数组arr本身自己就是一个内存地址ar=%p\n", arr);//数组arr本身自己就是一个内存地址arr=0061FF0C
    printf("数组arr的内存地址&arr=%p\n", &arr); //数组arr的内存地址&arr=0061FF0C
    printf("数组arr的第一个元素arr[0]的内存地址&arr[0]=%p\n", &arr[0]); // 数组arr的第一个元素arr[0]的内存地址&arr[0]=0061FF0C


    //todo ======================================================================
    //todo 反推数值:  *arr=*arr_p=*(&arr[0])=10

    //todo 数组arr自身就是一个内存地址== 第一个元素arr[0]的内存地址&arr[0]
    int* arr_p = arr;
    printf("数组自身的内存地址arr所对应的数值*arr=%d\n",*arr); //数组arr自身的内存地址所对应的数值*arr=10
    printf("数组arr的内存地址arr_p所对应的数值是*arr_p=%d\n",*arr_p); //数组arr的内存地址arr_p所对应的数值是*arr_p=10

    printf("取出数组arr的内存地址arr_p所对应的数值*arr_p=%d=元素一内存地址&arr[0]所对应的数值*(&arr[0])=%d\n",*arr_p,*(&arr[0]));
    //取出数组arr的内存地址arr_p所对应的数值*arr_p=10=元素一内存地址&arr[0]所对应的数值*(&arr[0])=10

    //todo 指针地址移动到下一个==元素二的内存指针地址
     arr_p =arr_p+1;
    printf("取出数值arr的第二个元素的内存地址的所对应的数值*arr_p=%d=元素二内存地址&arr[1]所对应的数值*(&arr[1])=%d\n",*arr_p,*(&arr[1]));
   // 取出数值arr的第二个元素的内存地址的所对应的数值*arr_p=20=元素二内存地址&arr[1]所对应的数值*(&arr[1])=20

    //todo 当前arr_p是第二个元素的内存指针地址,往下移动二个==元素四的内存指针地址
    arr_p=arr_p+2;
    printf("取出数值arr的第四个元素的内存指针地址的所对应的数值*arr_p=%d=元素四内存地址&arr[3]所对应的数值*(&arr[3])=%d\n",*arr_p,*(&arr[3]));
      // 取出数值arr的第四个元素的内存指针地址的所对应的数值*arr_p=40=元素四内存地址&arr[3]所对应的数值*(&arr[3])=40

   //todo  当前arr_p是第四个元素的内存指针地址,往前移动三位==元素一的内存指针地址
   arr_p=arr_p-3;
   printf("输出当前指针地址arr_p所对应的数值*arr_p=%d=元素一内存地址&arr[0]所对应的数值*(&arr[0])=%d\n",*arr_p,*(&arr[0]));
  // 输出当前指针地址arr_p所对应的数值*arr_p=10=元素一内存地址&arr[0]所对应的数值*(&arr[0])=10

    // todo 当前arr_p是第一个元素的内存指针地址,往下移动2000位
    arr_p=arr_p+2000;
    printf("当前指针地址arr_p往下移动2000位后生成的新的指针地址arr_p,所对应的数值*arr_p=%d",*arr_p);
   //  当前指针地址arr_p往下移动2000位后生成的新的指针地址arr_p,所对应的数值*arr_p=572662306  是个系统值
    return 0;
}

使用指针遍历数组  2T4


需要指针地址arr_p随着数组的遍历循环+i=下一个指针的地址位置arr_p+i
数组是连续的内存空间(没有断层,有规律) int数组每次挪动 4个字节

// 4.使用指针遍历数组。
//需要指针地址arr_p随着数组的遍历循环+i=下一个指针的地址位置arr_p+i
int mainT24() {
    // todo 数组是连续的内存空间(没有断层,有规律) int数组每次挪动 4个字节

    // 定义数组
    // int [] arr = {1,2,3,4}; 错误的写法
    int arr[]  = {10,20,30,40};


    //数组arr的内存地址 arr_p=数组arr的第一个元素的内存地址&arr[0]
    int* arr_p=arr;

     //遍历数组
    int i=0;
    for(i = 0; i < 4; ++i){
        printf("位置%d的元素数值%d的内存地址=%p\n",i,*(arr_p+i),(arr_p+i));
        printf("位置%d的元素数值=%d\n",i,*(arr_p+i));
    }

  /**
位置0的元素数值10的内存地址=0061FF08
位置0的元素数值=10
位置1的元素数值20的内存地址=0061FF0C
位置1的元素数值=20
位置2的元素数值30的内存地址=0061FF10
位置2的元素数值=30
位置3的元素数值40的内存地址=0061FF14
位置3的元素数值=40

   */

    return 0;
}

循环时给数组赋值


   数组每个元素的内存地址 arr_p + i
   数组每个元素的内存地址所对应的数值  * (arr_p + i)
   遍历循环数组arr时 给数组的每个元素的内存地址arr_p+i  所对应的旧的数值*(arr_p+i)赋新的数值1000+i  

   即*(arr_p+i)=1000+i
    元素0的内存地址0061FF08所对应的赋值了新的数值=1000
    元素1的内存地址0061FF0C所对应的赋值了新的数值=1001
    元素2的内存地址0061FF10所对应的赋值了新的数值=1002
    元素3的内存地址0061FF14所对应的赋值了新的数值=1003

数组的长度

int类型的数组arr的长度 = sizeof arr  / sizeof(int)=16/4=4

int mainT25() {
    // 定义数组
    // int [] arr = {1,2,3,4}; 错误的写法
    int arr[]  = {10,20,30,40};

    //定义数值arr的内存指针地址 =第一个元素的内存地址
    int* arr_p=arr;

    // 循环赋值操作
    int i=0;
    for(i=0;i<4;++i){
        // 1.输出 元素一 元素二 元素三 元素四 的内存地址   (arr_p + i)
       printf("元素%d的内存地址=%p\n",i,(arr_p+i));
        // 2.输出 元素一 元素二 元素三 元素四 的内存地址 所对应的值 * (arr_p + i)
     printf("元素%d的内存地址%p所对应的原来数值=%d\n",i,(arr_p+i),*(arr_p+i));
    //todo 给数组的每个元素的内存地址arr_p+i  所对应的旧的数值*(arr_p+i)赋新的数值1000+i
     //元素0的内存地址0061FF08所对应的赋值了新的数值=1000
     //元素1的内存地址0061FF0C所对应的赋值了新的数值=1001
     //元素2的内存地址0061FF10所对应的赋值了新的数值=1002
     //元素3的内存地址0061FF14所对应的赋值了新的数值=1003
      *(arr_p+i)=1000+i;
      printf("元素%d的数值赋值后,元素%d的内存地址%p所对应的赋值了新的数值=%d\n",i,i,(arr_p+i),*(arr_p+i));
    }

/**
 元素0的内存地址=0061FF08
元素0的内存地址0061FF08所对应的原来数值=10
元素0的数值赋值后,元素0的内存地址0061FF08所对应的赋值了新的数值=1000
元素1的内存地址=0061FF0C
元素1的内存地址0061FF0C所对应的原来数值=20
元素1的数值赋值后,元素1的内存地址0061FF0C所对应的赋值了新的数值=1001
元素2的内存地址=0061FF10
元素2的内存地址0061FF10所对应的原来数值=30
元素2的数值赋值后,元素2的内存地址0061FF10所对应的赋值了新的数值=1002
元素3的内存地址=0061FF14
元素3的内存地址0061FF14所对应的原来数值=40
元素3的数值赋值后,元素3的内存地址0061FF14所对应的赋值了新的数值=1003
 */


  // todo int类型的数组arr的长度 = sizeof arr  / sizeof(int)=16/4=4
    printf("数组arr的长度=%d\n", sizeof arr / sizeof(int));// 数组arr的长度=4
    printf("输出 sizeof arr=%d\n", sizeof arr);  //输出 sizeof arr=16
   printf("输出 sizeof(arr)=%d\n", sizeof(arr));  // 输出 sizeof(arr)=16
    printf("输出 sizeof(int)=%d\n", sizeof(int)); //输出 sizeof(int)=4


    // 变量 刚刚赋值的数组
    for (int i = 0; i <  sizeof arr / sizeof(int); ++i) {
        printf("输出位置%d的新的数值是=%d\n", i, * (arr_p + i));
    }

  /**
输出位置0的新的数值是=1000
输出位置1的新的数值是=1001
输出位置2的新的数值是=1002
输出位置3的新的数值是=1003

   */


    return 0;
}

C 指针


  通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。
  每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。

#include <stdio.h>
 
int main ()
{
    int var_runoob = 10;
    int *p;              // 定义指针变量
    p = &var_runoob;
 
   printf("var_runoob 变量的地址: %p\n", p); //var_runoob 变量的地址: 0x7ffeeaae08d8

   return 0;
}

1. 什么是指针?

变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量

内存其实就是一组有序字节组成的数组,数组中,每个字节大大小固定,都是 8bit。对这些连续的字节从 0 开始进行编号,每个字节都有唯一的一个编号,这个编号就是内存地址。

  指针也就是内存地址,指针变量是用来存放内存地址的变量。
  就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
  type 是指针的基类型,它必须是一个有效的 C 数据类型, var_name 是指针变量的名称。
  用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。
  type *var_name;

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

2.为什么要使用指针?

在C语言中,指针的使用非常广泛,因为使用指针往往可以生成更高效、更紧凑的代码。总的来说,使用指针有如下好处:
 1)指针的使用使得不同区域的代码可以轻易的共享内存数据,这样可以使程序更为快速高效;
 2)C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等;
  3)C语言是传值调用,而有些操作传值调用是无法完成的,如通过被调函数修改调用函数的对象,但是这种操作可以由指针来完成,而且并不违背传值调用。

  3.如何声明一个指针

   3.1声明并初始化一个指针

   指针其实就是一个变量,指针的声明方式与一般的变量声明方式没太大区别:
  指针的声明比普通变量的声明多了一个一元运算符 “*”。运算符 “*” 是间接寻址或者间接引用运算符。

  // 方法1:使指针p指向现有的内存地址
    int i=3;
    // 声明一个 int 类型的指针 p    p 是一个指针,保存着一个指针内存地址
    int * p;
    // 指针变量并不会自动分配任何内存。必须对指针p进行初始化。
    //指针的初始化实际上就是给指针一个合法的地址,让程序能够清楚地知道指针指向哪儿。
     p = &i;
    printf("p 是一个指针,保存着一个指针内存地址p=%p\n",p); // p 是一个指针,保存着一个指针内存地址p=0061FF18
    // *p 则会访问这个地址所指向的变量。
    printf("*p 则会访问这个地址所指向的变量*p=%d\n",*p); //*p 则会访问这个地址所指向的变量*p=3

    //方法2:malloc动态分配内存给指针pInt
    int *pInt;
    pInt = (int *)malloc(sizeof(int) * 10);
    // free 函数用于释放一块已经分配的内存,常与 malloc 函数一起使用,要使用这两个函数需要头文件 stdlib.h
    free(pInt);


    char * cp;        // 声明一个 char 类型的指针 cp
    int *arr[10];   // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
    int (*array)[10]; // 声明一个数组指针,该指针指向一个 int 类型的一维数组
    int **cpp;       // 声明一个指针 cpp,该指针指向一个 int 类型的指针

3.2未初始化和非法的指针

  如果一个指针没有被初始化,那么程序就不知道它指向哪里。它可能指向一个非法地址,这时,程序会报错,
  它也可能指向一个合法地址,实际上,这种情况更严重,你的程序或许能正常运行,
  但是这个没有被初始化的指针所指向的那个位置的值将会被修改,而你并无意去修改它。

   int x = 1;
    int *p2=&x;
    printf("输出指针p2指向的数值变量*p2=%d\n",*p2);// 输出指针p2指向的数值变量*p2=1
     *p2 = 2; //给指针p2指向的数值变量*p2 赋值为2
    printf("输出指针p2指向的数值变量*p2=%d\n",*p2); // 输出指针p2指向的数值变量*p2=2

  使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。
  这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。

 int  var = 20;   /* 实际变量的声明 */
    int  *ip;        /* 指针变量的声明 */
    ip = &var;  /* 在指针变量ip中存储 var 的地址&var */
    printf("var 变量的地址: %p\n", &var  ); //var 变量的地址: 0061FEC4
   /* 在指针变量ip中存储的地址 */
    printf("ip 变量存储的地址: %p\n", ip ); //ip 变量存储的地址: 0061FEC4
    /* 使用指针访问值 */
    printf("*ip 变量的值*ip= %d\n", *ip );//*ip 变量的值*ip= 20

3.3 NULL 指针

NULL 指针是一个特殊的指针变量,表示不指向任何东西。可以通过给一个指针赋一个零值来生成一个 NULL 指针。

在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。

#include <stdio.h>
 
int main ()
{
     int *p3;
    p3 = NULL;
    printf("p3的指针地址为p3 = %p\n",p);  //p3的指针地址为p3 = 0061FF0C 

 
   return 0;
}

 4.指针的运算

  C 指针的算术运算只限于两种形式:

4.1 指针 +/- 整数 :
 

  可以对指针变量 p 进行 p++、p--、p + i 等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i 个操作数。
   用一张图来说明一下:

  4.2指针 - 指针


 只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。 两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位,因为减法运算的结果将除以数组元素类型的长度。)

   //0 1 2 3 4 5 6 7 8 9
    int a[10] = {1,2,3,4,5,6,7,8,9,0};
    //定义并初始化指针p1
    int * p1 = &a[2];
    printf("输出数组的元素a[2]的值=%d,a[2]的指针地址&a[2]=%p,指针p1=%p\n",a[2],&a[2],p1); //输出数组的元素a[2]的值=3,a[2]的指针地址&a[2]=0061FEBC,指针p1=0061FEBC
    //定义并初始化指针p22
    int *p22 = &a[8];
    printf("输出数组的元素a[8]的值=%d,a[8]的指针地址&a[8]=%p,指针p22=%p\n",a[8],&a[8],p22); // 输出数组的元素a[8]的值=9,a[8]的指针地址&a[8]=0061FED0,指针p22=0061FED0
   int sub;
   sub=p22-p1;
   printf("输出数值sub=%d\n", sub);  // 输出数值sub=6

5.指针与数组

指针与数组之间的关系十分密切。实际上,许多可以用数组完成的工作都可以使用指针来完成。
  一般来说,用指针编写的程序比用数组编写的程序执行速度快,但另一方面,用指针实现的程序理解起来稍微困难一些。 

 5.1. 指针与数组的关系

很显然,一个通过数组和下标实现的表达式可以等价地通过指针及其偏移量来实现,这就是数组和指针的互通之处。
  但有一点要明确的是,数组和指针并不是完全等价,指针是一个变量,而数组名不是变量,它数组中第 1 个元素的地址,数组可以看做是一个用于保存变量的容器。
  更直接的方法,我们可以直接看二者的地址,并不一样:

// 声明一个int类型的数组,这个数组有10个元素
 //我们可以用 arr[0]、arr[1]、...、arr[9] 来表示这个数组中的10个元素,这10个元素是存储在一段连续相邻的内存区域中的。
int arr[10]; 

 // 声明一个int类型的指针变量p
int *p;

// 对指针p进行初始化,p将指向数组 arr 的第 1 个元素 arr[0]
p = &arr[0];

对指针进行自增操作会让指针指向与当前元素相邻的下一个元素,即 *(p + 0) *(p + 1) *(p + 2)... *(p + i) 将指向arr[0] arr[1] arr[2]...arr[i] 。
因此,我们可以使用该指针来遍历数组 arr[10] 的所有元素。可以看到,数组arr下标与指针p运算之间的关系是一一对应的。
数组类型的变量或表达式的值是该数组第 1 个元素的地址,且数组名arr所代表的的就是该数组第 1 个元素的地址,故,上述赋值语句可以直接写成:

// arr 为数组名,代表该数组最开始的一个元素的地址&arr[0]   
p = arr=&arr[0];

5.2.指针数组

指针是一个变量,而数组是用于存储变量的容器,
  因此,指针也可以像其他变量一样存储在数组中,也就是指针数组。
  指针数组是一个数组,数组中的每一个元素都是指针。声明一个指针数组的方法如下:

// 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向int类型的指针
int *p[10]; 

由于 [] 的优先级比 * 高,故 p 先与 [] 结合,成为一个数组 p[];
再由 int * 指明这是一个 int 类型的指针数组,数组中的元素都是 int 类型的指针。
  数组的第 i 个元素是 *p[i],而 p[i] 是一个指针。
  由于指针数组中存放着多个指针,操作灵活,在一些需要操作大量数据的程序中使用,可以使程序更灵活快速。

5.3.数组指针

数组指针是一个指针,它指向一个数组。声明一个数组指针的方法如下:
    
    // 声明一个数组指针 p ,该指针指向一个数组[10]
     int (*p)[10];   

由于 () 的优先级最高,所以 p 是一个指针,指向一个 int 类型的一维数组,这个一维数组的长度是 10,

6.指针与函数

C语言的所有参数均是以“传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。
  这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。

6.1 指针作为函数的参数

传值调用的好处是 被调函数不会改变调用函数传过来的值,可以放心修改。
  但是有时候需要被调函数回传一个值给调用函数,这样的话,传值调用就无法做到。为了解决这个问题,可以使用传指针调用。
  通过指针参数使得被调函数能够访问和修改主调函数中对象的数值内容。

用一个例子来说明:

#include "stdio.h"


// 参数 a  b  为普通的 int 变量
//在被调函数swap1里不会改变调用函数传过来的值x=a=1 y=b=2,可以放心修改。
void swap1(int a,int b) {
    //以下逻辑的修改是无效的
   int temp;
    temp = a;
     a = b;
    b = temp;
}

// 参数a b 为指针,接收调用函数传递过来的变量地址a b作为参数,对所指地址处的内容进行操作修改有效
void swap2(int* a,int* b) {
    // 最终结果是,指针地址本身并没有改变,但是这一地址所对应的内存段中的数值内容发生了变化,即x,y的值发生了变化
    int temp;
    // 指针 a b的对应的数值*a  *b
    printf("输出指针 a b的对应的数值*a=%d,*b=%d\n",*a,*b); //输出指针 a b的对应的数值*a=1,*b=2
    temp = *a;
    *a = *b;
    *b = temp;
    printf("再次输出指针 a b的对应的数值*a=%d,*b=%d\n",*a,*b); //再次输出指针 a b的对应的数值*a=2,*b=1
}

int  main(){
     int x = 1,y = 2;
    printf("输出结果 x=%d y=%d\n",x,y); //输出结果 x=1 y=2
    // 将 x,y 的值本身作为参数传递给了被调函数swap1 在被调函数里无法修改x = 1,y = 2的值
    swap1(x,y);
    printf("输出结果依然是 x=%d y=%d\n",x,y); //输出结果依然是 x=1 y=2

    int *xp=&x;
    int *yp=&y;
    // 将 x,y 的指针地址xp yp 作为参数传递给了被调函数swap2,传递过去的也是一个值,与传值调用不冲突
    swap2(xp,yp);
    printf("输出结果依然是 x=%d y=%d\n",x,y); // 输出结果依然是 x=2 y=1
    return 0;
}

 6.2 指向函数的指针

 在C语言中,函数本身不是变量,但是可以定义指向函数的指针,也称作函数指针,函数指针指向函数的入口地址。
这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。
 声明一个函数指针的方法如下:
返回值类型 (* 指针变量名)([形参列表]);
int          (*pointer)     (int *,int *); 

 声明了一个函数指针 pointer  该指针指向一个函数(int *,int *) 函数具有两个 int * 类型的参数,且返回值类型为 int。

     



// 声明一个函数 str_comp,该函数有两个 const char 类型的指针,函数的返回值为 int 类型
int str_comp(const char *m,const char *n);
// 声明一个函数 comp ,注意该函数的第三个参数,是一个函数指针prr
void comp(char *a, char *b, int (*prr)(const char *, const char *));



int main(){

// 声明一个字符数组
    char str1[20];
    char str2[20] ;

    // 声明并初始化一个函数指针,该指针所指向的函数有两个 const char 类型的指针,且返回值为 int 类型
    int (*p)(const char*,const char*)=str_comp;

     //这段代码的功能是从键盘读取两行字符串(长度不超过20),判断二者是否相等。
    // char * gets (char *);   使用 gets() 函数从 I/O 读取一行字符串
     gets(str1);
     gets(str2);
     printf("str1=%s  str2=%s \n",str1,str2);

    // 函数指针 p 作为参数传给 comp 函数
    comp(str1,str2,p);

    return 0;

}



int str_comp(const char *m,const char *n){
    // 库函数 strcmp 用于比较两个字符串,其原型是: int strcmp(const char *s1,const char *s2);
    if(strcmp(m,n) ==0){
        printf("strcmp(m,n)=%d,m=n\n",strcmp(m,n));
        return 0;
    }else{
       return 1;
    }
}

// 函数 comp 接受一个函数指针prr作为它的第三个参数
void comp(char *a, char *b, int (*prr)(const char *, const char *)){
   int result= (*prr)(a,b);
     if(result==0){
         printf("str1 = str2\n");
     }else{
         printf("str1 != str2\n");
     }

}



7.C 指针详解

概念描述
指针的算术运算可以对指针进行四种算术运算:++、--、+、-
指针数组可以定义用来存储指针的数组。
指向指针的指针C 允许指向指针的指针。
传递指针给函数通过引用或地址传递参数,使传递的参数在调用函数中被改变。
从函数返回指针C 允许函数返回指针到局部变量、静态变量和动态内存分配。

 7.1 C 指针的算术运算

   C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。
  假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:
  int * ptr=1000;
  ptr++

在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 字节。
这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。
如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。

递增一个指针

  指针的每一次递增,它其实会指向下一个元素的存储单元。
  指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。
  我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。
  下面的程序递增变量指针,以便顺序访问数组中的每一个元素:

    int var[]={10, 100, 200};
    int* ptr;
    ptr=varr; //指针指向数组地址
    for (int i = 0; i <3 ; i++) {
        printf("ptr存储的指针地址var[%d]=%p\n",i,ptr);
        printf("数组存储的元素值var[%d]=%d\n",i,*ptr);
        ptr++; //指针指向下一个位置
    }

/**
ptr存储的指针地址var[0]=0061FEB0
数组存储的元素值var[0]=10
ptr存储的指针地址var[1]=0061FEB4
数组存储的元素值var[1]=100
ptr存储的指针地址var[2]=0061FEB8
数组存储的元素值var[2]=200
*/

递减一个指针

指针的每一次递减,它都会指向前一个元素的存储单元。
  指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。
  对指针进行递减运算,即把值减去其数据类型的字节数

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指针中最后一个元素的地址 */
   ptr = &var[MAX-1];
   for ( i = MAX; i > 0; i--)
   {
 
      printf("存储地址:var[%d] = %p\n", i-1, ptr );
      printf("存储值:var[%d] = %d\n", i-1, *ptr );
 
      /* 指向下一个位置 */
      ptr--;
   }
   return 0;
}

7.2 C 指针数组

定义一个用来存储指针的数组

//定义一个有 3 个整数组成的数组var
    int  var[] = {10, 100, 200};
    //声明一个指向3个整数类型指针的数组ptr 。ptr 中的每个元素,都是一个指向 int 值的指针。
    int* ptr[3];

int main(){
    //定义一个有 3 个整数组成的数组var
    int  var[] = {10, 100, 200};
    //声明一个指向3个整数类型指针的数组ptr 。ptr 中的每个元素,都是一个指向 int 值的指针。
    int* ptr[3];

    for(int i=0;i<3;i++){
        ptr[i]=&var[i]; // 获取数组var中的每一个int类型元素值的指针地址 赋值给 指针数组ptr[i]
    }

    for(int i=0;i<3;i++){
        printf("输出指针数值ptr中的每一个指针地址ptr[%d]=%p,对应的数值var[%d]=%d\n",i,ptr[i],i,*ptr[i]);
    }

    /**
 输出指针数值ptr中的每一个指针地址ptr[0]=0061FF0C,对应的数值var[0]=10
输出指针数值ptr中的每一个指针地址ptr[1]=0061FF10,对应的数值var[1]=100
输出指针数值ptr中的每一个指针地址ptr[2]=0061FF14,对应的数值var[2]=200
     */

}

定义一个指向字符的指针数组names 来存储一个个字符串列表
  const char *names[] = {"Zara Ali","Hina Ali", "Nuha Ali", "Sara Ali", };

// 定义一个指向字符的指针数组names 来存储一个个字符串列表
    const char* names[]={"Zara Ali","Hina Ali","Nuha Ali","Sara Ali"};
    for(int i=0;i<4;i++){
        printf("获取指针数组names的元素值names[%d]=%s\n",i,names[i]);
    }
    
    /**
 获取指针数组names的元素值names[0]=Zara Ali
获取指针数组names的元素值names[1]=Hina Ali
获取指针数组names的元素值names[2]=Nuha Ali
获取指针数组names的元素值names[3]=Sara Ali
     */

7.3 指向指针的指针(多级指针)

C 允许指向指针的指针。
  多级指针:指针内存地址本身它自己在内存里也还个指针地址
 数值的指针内存地址
 数值的指针内存地址的  指针内存地址
 数值的指针内存地址的  指针内存地址 的指针内存地址
 数值的指针内存地址的  指针内存地址 的指针内存地址 的指针内存地址
 ......

int num = 999;
 int * num_p = &num; // 一级指针 *  :  num_p = 数值num的指针地址
 //声明了一个指向 int 类型指针num_p  的指针num_p_p
 int**  num_p_p = &num_p;  // 二级指针** : num_p_p = 一级指针num_p的 指针地址
 int *** num_ppp = &num_p_p; // 三级指针*** : num_ppp = 二级指针num_p_p的  指针地址

 int main(){
    int num = 999;
    // 一级指针 *  :  num_p = 数值num的指针地址
    int * num_p = &num;
    printf("数值num的指针地址(即一级指针 *)num_p=%p\n",num_p); // 数值num的指针地址(即一级指针 *)num_p=0061FF18

    // 二级指针** : num_p_p = 一级指针num_p的 指针地址
    int**  num_p_p = &num_p;
    printf("一级指针num_p的 指针地址(即二级指针**)num_p_p=%p\n",num_p_p);// 一级指针num_p的 指针地址(即二级指针**)num_p_p=0061FF14

    // 三级指针*** : num_ppp = 二级指针num_p_p的  指针地址
    int *** num_ppp = &num_p_p;
    printf("二级指针num_p_p的  指针地址(即三级指针***)num_ppp=%p\n",num_ppp);  // 二级指针num_p_p的  指针地址(即三级指针***)num_ppp=0061FF10


    printf("数值num的数值=%d\n",num); //数值num的数值=999
    printf("一级指针num_p所对应的数值*num_p=%d\n",*num_p); // 一级指针num_p所对应的数值*num_p=999
    printf("二级指针num_p_p所对应的数值**num_p_p=%d\n",**num_p_p); // 二级指针num_p_p所对应的数值**num_p_p=999
    printf("三级指针num_ppp所对应的数值***num_ppp=%d\n",***num_ppp);// 三级指针num_ppp所对应的数值***num_ppp=999
  return 0;
}

int main(){
    int  V;
    int  *Pt1;
    int  **Pt2;

    //初始化变量V的数值
    V=100;
    //初始化变量V数值的指针地址 Pt1
    Pt1=&V;
    //初始化 指针地址 Pt1 的指针地址Pt2 (二级指针)
    Pt2=&Pt1;

    printf("输出变量V的数值=%d\n",V); // 输出变量V的数值=100
    printf("输出指针地址Pt1=%p\n",Pt1); //输出指针地址Pt1=0061FF18
    printf("输出指针地址Pt1的对应的数值*Pt1=%d\n",*Pt1); //输出指针地址Pt1的对应的数值*Pt1=100
    printf("输出 指针地址Pt1 的指针地址Pt2=&Pt1=%p\n",Pt2); //输出 指针地址Pt1 的指针地址Pt2=&Pt1=0061FF14
    printf("输出(二级)指针地址Pt2的对应的变量值*Pt2=(一级)指针地址Pt1=%p\n",*Pt2);//输出(二级)指针地址Pt2的对应的变量值*Pt2=(一级)指针地址Pt1=0061FF18
    printf("输出(二级)指针地址Pt2的对应的变量值*Pt2=(一级)指针地址Pt1的对应的数值=%d\n",**Pt2); //输出(二级)指针地址Pt2的对应的变量值*Pt2=(一级)指针地址Pt1的对应的数值=100

}
void getSeconds(unsigned long *par);

int main(){

unsigned  long sec ;
    getSeconds( &sec );
    /* 输出实际值 */
    printf("实际秒数值=%ld\n", sec );  // 实际秒数值=1690873920
    return 0;

}

void getSeconds(unsigned long *par){
    /* 获取当前的秒数 */
    *par=time(NULL);
    return;
}

7.4 传递指针给函数

通过引用或地址传递参数,使传递的参数在调用函数中被改变。
  C 语言允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。

void getSeconds(unsigned long *par);


int main(){
  unsigned  long sec ;
    //传递一个无符号的 long 型指针给函数,并在函数内改变这个值
    getSeconds( &sec );
    /* 输出实际值 */
    printf("实际秒数值=%ld\n", sec );  // 实际秒数值=1690873920

 return 0;
}


void getSeconds(unsigned long *par){
    /* 获取当前的秒数 */
    *par=time(NULL);
    return;
}
//函数声明
double getAverage(int *arr, int size);


int main(){

//定义一个带有 5 个元素的整型数组balance
    int balance[5] = {1000, 2, 3, 17, 50};
    double avg;

   // 传递一个指向数组的指针balance作为参数
     avg= getAverage(balance,5);

     printf("avg=%f\n",avg); // avg=214.400000


return 0;


}


//参数 数组指针arr
double getAverage(int *arr, int size){
    int sum=0;
    double avg;
    for(int i=0;i<size;++i){
        printf("输出数组的元素值=%d\n",*(arr+i));
        printf("输出数组的元素值=%d\n",arr[i]);
        sum+=arr[i];
    }
    avg = (double)sum/size;
    return  avg;
}


7.5   从函数返回指针

 C 允许函数返回指针到局部变量、静态变量和动态内存分配。
  C 允许您从函数返回指针。
  C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
  为什么?
  因为局部变量是存储在内存的栈区内,当函数调用结束后,局部变量所占的内存地址便被释放了,
  因此当其函数执行完毕后,函数内的局部变量便不再拥有那个内存地址,所以不能返回其指针。
  除非将局部变量定义为 static 变量,static 变量的值存放在内存中的静态数据区,不会随着函数执行的结束而被清除,故能返回其地址。
 int * myFunction(){
  // 局部变量数组指针r[10]要设置为static
  static int r[10];
  return r;//局部变量数组r 即为返回的指针
  }

//.返回一个整数类型的指针
int *getRandom() {
   // 局部变量数组指针r[10]要设置为static
   static int r[10];
   //  设置种子
    srand( (unsigned)time( NULL ) );
    for(int i=0;i<10;i++){
        r[i]=rand();
        printf("输出数组元素值r[%d]=%d\n",i,r[i]);
    }
    return r;//局部变量数组r 即为返回的指针
}

/**
 输出数组元素值r[0]=29622
输出数组元素值r[1]=10224
输出数组元素值r[2]=10614
输出数组元素值r[3]=18101
输出数组元素值r[4]=21551
输出数组元素值r[5]=23563
输出数组元素值r[6]=21448
输出数组元素值r[7]=7694
输出数组元素值r[8]=19575
输出数组元素值r[9]=29822
 */


int main(){

//一个指向整数的指针p(数组指针 指向数组第一个元素的指针地址)
 int * p;
 p=getRandom();
 for(int i=0;i<10;i++){
  //挪动数组指针p+i 获取对应变量数值*(p+i)
  printf("*(p+%d)=%d\n",i,*(p+i));
 }
 return 0;
}

/**
*(p+0)=29622
*(p+1)=10224
*(p+2)=10614
*(p+3)=18101
*(p+4)=21551
*(p+5)=23563
*(p+6)=21448
*(p+7)=7694
*(p+8)=19575
*(p+9)=29822
 */

8.指针类型有何用  T27


指针占用的内存大小是?  int double char xxx类型的 的指针 永远都是   4个字节(32位)
指针类型有何用? 规定指针的类型是为了  计算指针的偏移量

/**
指针类型有何用
指针占用的内存大小是?  int double char xxx类型的 的指针 永远都是   4个字节(32位)
指针类型有何用? 规定指针的类型是为了  计算指针的偏移量
 */
int mainT27(){
    int num = 12;
    int* num_p = &num;
    printf("int类型的指针的占用内存的长度=%d\n",sizeof num_p); // int类型的指针的占用内存的长度=4

    double dou = 123.123;
    double* dou_p = &dou;
    printf("double类型的指针的占用内存的长度=%d\n",sizeof dou_p); //double类型的指针的占用内存的长度=4

   char cha='a';
   char* cha_p=&cha;
   printf("char类型的指针的占用内存的长度=%d\n",sizeof cha_p); // char类型的指针的占用内存的长度=4
   
   return 0;
};

函数指针

函数指针1   T28


    函数本身就是一个指针地址
    add的函数指针地址 : add函数自身就是一个指针地址add=&add=00402022
    
    void(*method)(int,int)  函数指针的声明格式
    void 返回值
    (*method) 函数指针  method函数指针别名
    (int,int) 传递两个参数

void add(int num1, int num2); // 先声明
void mins(int num1, int num2) {
    printf("num1 - num2 = %d\n", (num1 - num2));
}


// void(*method)(int,int)  函数指针的声明格式
// void 返回值
// (*method) 函数指针  method函数指针别名
// (int,int) 传递两个参数
void opreate(void(*method)(int,int),int num1,int num2){
    //如果 形参函数指针method=实参函数指针add/&add
    // opreate函数的 形参method函数指针=00402022 =add=&add
    //如果 形参函数指针method=实参函数指针mins/&mins
    // opreate函数的 形参method函数指针=00401F10= mins=&mins
    method(num1, num2);
    printf("opreate函数的 形参method函数指针=%p\n", method);

}


//函数指针1  :函数本身就是一个指针地址
int mainT28(){
   add(1,2); //num1 + num2 = 3
   mins(3,2); // num1 - num2 = 1
    printf("main函数的 add函数的指针add=%p\n", add); //main函数的 add函数的指针add=00402022
    printf("main函数的 add函数的指针&add=%p\n", &add);//main函数的 add函数的指针&add=00402022
    printf("main函数的 mins函数指针mins=%p\n", mins); //main函数的 mins函数指针mins=00401F10
    printf("main函数的 mins函数指针&mins=%p\n", &mins); //main函数的 mins函数指针&mins=00401F10

    //todo add的函数指针地址 : add函数自身就是一个指针地址add=&add=00402022
    //传递实参参数: 函数指针void(*method)(int,int)=add/&add    数值num1=10  num2=10
    opreate(add,  10, 10);
    opreate(&add,  10, 10);
    // num1 + num2 = 20  opreate函数的 形参method函数指针=00402022 =add=&add

    //传递实参参数: 函数指针void(*method)(int,int)=mins/&mins    数值num1=10  num2=100
     opreate(mins,  10, 100);
     opreate(&mins,  10, 100);
  // num1 - num2 = -90   opreate函数的 形参method函数指针=00401F10= mins=&mins

    return 0;
}

// 再实现 使用
void add(int num1, int num2) {
    printf("num1 + num2 = %d\n", (num1 + num2));
}

    函数指针2: T29


    函数本身就是一个指针地址
    callBackMethod的函数指针地址 : callBackMethod函数自身就是一个指针地址callBackMethod=&callBackMethod=00402074

    
    void callBackMethod(char* fileName, int current,int total){
    printf("%s图片压缩的进度是:%d/%d\n", fileName, current, total);
    }


   //创建一个函数compress()
//定义一个函数指针地址声明   返回值(*名称)(char *,int,int)= void(*callBackP)(char *,int,int)
//函数指针名称callBackP=接收传进来的实参函数指针callBackMethod/&callBackMethod
//形参: 字符串char* fileName,数值int current,数值int total
void compress(char* fileName,int current,int total,void(*callBackP)(char *,int,int)){
     //函数指针callBackP=callBackMethod/&callBackMethod
     // callBackP(fileName,current,total)=callBackMethod("方明飞.png",5,100)
    // 形参 fileName= "方明飞.png",current=5,total=100
    callBackP(fileName,current,total);
     // 或者这样写法
    (*callBackP)(fileName,current,total);
    // 或者这样写法  // 省略*
    (callBackP)(fileName,current,total);
}

    
 给函数callBackMethod  定义一个函数指针地址void (* call1)(char*,int,int)  方式一    
  void (* call1)(char*,int,int)=callBackMethod;    
  void (* call2)(char*,int,int)=&callBackMethod;  
 
    
    
给函数callBackMethod  定义一个函数指针地址  方式二
 //先定义
// call3 函数指针地址  

/./形参 char*,int,int
 void (* call3)(char*,int,int);    
//再给指针call3赋值callBackMethod
call3 =callBackMethod;    
call3 =&callBackMethod;  
 
    
   //先定义
   void (* call5) (char *, int ,int);
    void (* call6) (char *, int ,int);
    void (* call7) (char *, int ,int);
    void (* call8) (char *, int ,int);
     //在赋值
    call6 =callBackMethod;
    call8 =&callBackMethod;    
    
   

//定义一个函数
//callBackMethod的函数指针地址 : callBackMethod函数自身就是一个指针地址callBackMethod=&callBackMethod=00402074
void callBackMethod(char* fileName, int current,int total){
    printf("%s图片压缩的进度是:%d/%d\n", fileName, current, total);
}


   //创建一个函数compress()
//定义一个函数指针地址声明   返回值(*名称)(char *,int,int)= void(*callBackP)(char *,int,int)
//函数指针名称callBackP=接收传进来的实参函数指针callBackMethod/&callBackMethod
//形参: 字符串char* fileName,数值int current,数值int total
void compress(char* fileName,int current,int total,void(*callBackP)(char *,int,int)){
     //函数指针callBackP=callBackMethod/&callBackMethod
     // callBackP(fileName,current,total)=callBackMethod("方明飞.png",5,100)
    // 形参 fileName= "方明飞.png",current=5,total=100
    callBackP(fileName,current,total);
     // 或者这样写法
    (*callBackP)(fileName,current,total);
    // 或者这样写法  // 省略*
    (callBackP)(fileName,current,total);
}


// 函数指针2: 函数本身就是一个指针地址

int main(){
      //定义一个字符串
     char* str = "方明飞";
     printf("字符串str的数值=%s\n",str); //字符串str的数值=方明飞
    printf("字符串str的指针地址=%p\n",str); // 字符串str的指针地址=00406AAC
    printf("字符串str的指针地址=%p\n",&str); //字符串str的指针地址=0061FF1C

    //todo  callBackMethod的函数指针地址 : callBackMethod函数自身就是一个指针地址callBackMethod=&callBackMethod=00402074
    printf("函数callBackMethod的指针地址callBackMethod=%p\n",callBackMethod); //函数callBackMethod的指针地址callBackMethod=00402074
    printf("函数callBackMethod的指针地址&callBackMethod=%p\n",&callBackMethod); // 函数callBackMethod的指针地址&callBackMethod=00402074


    compress("方明飞-2.png",-2,100,callBackMethod); //方明飞-2.png图片压缩的进度是:-2/100
    compress("方明飞-1.png",-1,100,&callBackMethod); //方明飞-1.png图片压缩的进度是:-1/100

    // 给函数callBackMethod  定义一个函数指针地址void (* call1)(char*,int,int)  方式一
     void (* call1)(char*,int,int)=callBackMethod;
    compress("方明飞1.png",5,100,call1); // 方明飞1.png图片压缩的进度是:5/100
    void (* call2)(char*,int,int)=&callBackMethod;
    compress("方明飞2.png",15,100,call2); //方明飞2.png图片压缩的进度是:15/100


    // 给函数callBackMethod  定义一个函数指针地址  方式二
    //先定义
    // call3 函数指针地址   形参 char*,int,int
    void (* call3)(char*,int,int);
    //再给指针call3赋值callBackMethod
    call3 =callBackMethod;
    compress("方明飞3.png",25,100,call3); //方明飞3.png图片压缩的进度是:25/100
    //先定义
    void (* call4) (char *, int ,int);
    //在赋值
    call4 =&callBackMethod;
    compress("方明飞4.png",35,100,call4); //方明飞4.png图片压缩的进度是:35/100

    void (* call5) (char *, int ,int);
    void (* call6) (char *, int ,int);
    void (* call7) (char *, int ,int);
    void (* call8) (char *, int ,int);
    call6 =callBackMethod;
    call8 =&callBackMethod;
    compress("方明飞8.png",85,100,call8); //方明飞8.png图片压缩的进度是:85/100



    return  0;

 函数指针练习

   几种函数指针的写法

   test(addMethod, 9, 9); // i+j=18
    test(&addMethod, 8, 8); //i+j=16

    void(*p1)(int,int)=addMethod;
    test(p1,7,7);  //i+j=14
    void(*p2)(int,int)=&addMethod;
    test(p2,6,6); //i+j=12

    void(*p3)(int,int);
    p3=addMethod;
    test(p3,5,5); //i+j=10

    void(*p4)(int,int);
    p4=&addMethod;
    test(p4,4,4); //i+j=8

#include <stdio.h>

void addMethod(int i, int j) {
    printf("i+j=%d\n", i+j);
}



// 创建函数test
// 定义一个函数指针声明void(*p)(int, int)
// 函数指针名称p=接收传进来的实参函数指针addMethod
void test(void(*p)(int,int),int num1,int num2){
    //p(num1, num2)=addMethod(9, 9)
    p(num1, num2); // 上节课 省略*
    (*p)(num1, num2); //或者这样写
    (p)(num1, num2); // 省略*   //或者这样写
    // (&p)(9, 9); 在源码没有看到这种写法
}



int main() {
    test(addMethod, 9, 9); // i+j=18
    test(&addMethod, 8, 8); //i+j=16

    void(*p1)(int,int)=addMethod;
    test(p1,7,7);  //i+j=14
    void(*p2)(int,int)=&addMethod;
    test(p2,6,6); //i+j=12

    void(*p3)(int,int);
    p3=addMethod;
    test(p3,5,5); //i+j=10

    void(*p4)(int,int);
    p4=&addMethod;
    test(p4,4,4); //i+j=8
    return 0;
}

函数指针回调对应的实际函数

函数指针变量可以作为某个函数的参数来使用的,回调函数就是通过一个函数指针 来调用其对应的的函数。
  简单讲:回调函数是由别人的函数执行时调用你实现的函数。

 以下是来自知乎作者常溪玲的解说:
    你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,
    店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,
    你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,
    你到店里去取货叫做响应回调事件。

#include <stdio.h>
#include <stdlib.h>
 
 // todo 函数指针getNextValue 的回调执行函数getNextRandomValue(void)  函数指针pFunction的回调执行函数addMeth(int i, int j)
  void populate_array(int *array[10], size_t arraySize, int (*pFunction)(void),
                     void (*pFunction1)(int, int), int num1, int num2) {
    for(size_t i=0;i<arraySize;i++){
        array[i]=pFunction();
        printf("输出数组元素值arr[%d]=%d\n",i,array[i]);
        printf("输出函数指针pFunction 的回调执行函数getNextRandomValue()=%d\n",pFunction());
    }
     pFunction1(num1,num2);// 实际执行回调的函数是addMeth(int i, int j)
}

/**
 输出数组元素值arr[0]=41
输出函数指针pFunction 的回调执行函数getNextRandomValue()=18467
输出数组元素值arr[1]=6334
输出函数指针pFunction 的回调执行函数getNextRandomValue()=26500
输出数组元素值arr[2]=19169
输出函数指针pFunction 的回调执行函数getNextRandomValue()=15724
输出数组元素值arr[3]=11478
输出函数指针pFunction 的回调执行函数getNextRandomValue()=29358
输出数组元素值arr[4]=26962
输出函数指针pFunction 的回调执行函数getNextRandomValue()=24464
执行回调函数addMeth输出结果:i+j=3
 */

int getNextRandomValue(void){
    return rand();
}

void addMeth(int i, int j) {
    printf("执行回调函数addMeth输出结果:i+j=%d\n", i+j);
}

int main(){
    int myArray[10];
    //把实际函数getNextRandomValue(void) 转为函数指针getNextRandomValue
    int (*pIntMeth)(void)=getNextRandomValue;
    //把实际的函数addMeth(int i, int j) 转为函数指针 addMeth
    void(*meth1)(int,int)=addMeth;

    // 传入函数指针名称 pIntMeth   传入函数指针名称 method1
    populate_array(myArray, 5, pIntMeth, meth1, 1, 2);
    return 0;
}

打印随机数  

srand((unsigned) time(NULL));

// 时间单位 给他才行
    srand((unsigned) time(NULL));

    int i;
    for (int i = 0; i <10 ;i++) {
        printf("随机数%d\n", rand() % 100);
    }

C中的布尔 

非0即true, 0就是false

  if (0) {
        printf("真\n");
    } else {
        printf("假\n");
    }

    if (43665) {
        printf("真\n");    // 1  走
    } else {
        printf("假\n");    // 2
    }

    if (-545) {
        printf("真\n");    // 1
    } else {
        printf("假\n");    // 2
    }

  C 结构体  T5

 C 数组允许定义可存储相同类型数据项的变量,结构是 C 编程中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
  结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等。

实际使用中,主要有下面两种情况,需要更灵活强大的复合类型。

复杂的物体需要使用多个变量描述,这些变量都是相关的,最好有某种机制将它们联系起来。
 某些函数需要传入多个参数,如果一个个按照顺序传入,非常麻烦,最好能组合成一个复合结构传入。

  为了解决这些问题,C 语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起。
    这样不仅为编程提供方便,也有利于增强代码的可读性。C 语言没有其他语言的对象(object)和类(class)的概念,
    struct 结构很大程度上提供了对象和类的功能。

 1.定义结构

 结构体定义由关键字 struct 和结构体名组成,结构体名可以根据需要自行定义。
 
  struct 语句定义了一个包含多个成员的新的数据类型tag
  tag 是结构体标签。
  member-list 是结构体tag的变量定义,比如 int i; 或者 float f;,或者其他有效的变量定义。
  variable-list 结构体变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。
 
  struct tag {
    member-list
    member-list
    member-list  
    ...
} variable-list ;
 

定义了一个分数的数据类型struct fraction,包含两个属性numerator和denominator

作为一个自定义的数据类型,它的类型名要包括struct关键字,比如上例是struct fraction,单独的fraction没有任何意义,
甚至脚本还可以另外定义名为fraction的变量,虽然这样很容易造成混淆。
另外,struct语句结尾的分号不能省略,否则很容易产生错误。

//先声明了一个struct fraction类型的变量f1,这时编译器就会为f1分配内存,接着就可以为f1的不同属性赋值。
 //struct 结构的属性通过点(.)来表示,比如numerator属性要写成f1.numerator
 //声明自定义类型的变量时,类型名前面,不要忘记加上struct关键字。也就是说,必须使用struct fraction f1声明变量,不能写成fraction f1。
    
struct fraction {
  int numerator;
  int denominator;
};

struct fraction f1;
f1.numerator = 22;
f1.denominator = 7;

除了逐一对属性赋值,也可以使用大括号,一次性对 struct 结构的所有属性赋值。

  //变量saturn是struct car类型,大括号里面同时对它的三个属性赋值。
//如果大括号里面的值的数量,少于属性的数量,那么缺失的属性自动初始化为0。
struct car {
  char* name;
  float price;
  int speed;
};

 // 大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。
struct car saturn = {"Saturn SL/2", 16000.99, 175};
saturn.speed = 168;

//结构体的标签被命名为SIMPLE,
// 没有声明结构体变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了结构体变量t1、t2、t3
struct  SIMPLE  t1,t2[20],*t3; 

struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。

同时声明了数据类型book和该类型的变量b1。如果类型标识符book只用在这一个地方,后面不再用到,这里可以将类型名省略。
struct book {
  char title[500];
  char author[100];
  float value;
} b1;

struct声明了一个匿名数据类型,然后又声明了这个类型的变量b1 

//这个结构体并没有标明其标签名称
// 又声明了结构体变量b1

struct {
  char title[500];
  char author[100];
  float value;
} b1;

struct声明了一个匿名数据类型,可以在声明变量的同时,对变量赋值。

//在声明变量b1b2的同时,为它们赋值。

struct {
  char title[500];
  char author[100];
  float value;
} b1 = {"Harry Potter", "J. K. Rowling", 10.0},
  b2 = {"Cancer Ward", "Aleksandr Solzhenitsyn", 7.85};

 结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针

// 结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针

//此结构体的声明包含了其他的结构体
struct COMPLEX{
    char string[100];
    struct SIMPLE a;
};

//此结构体的声明包含了指向自己类型的指针
struct NODE{
    char string[100];
    struct NODE* next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明

//如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明

//对结构体B进行不完整声明
struct B;

//结构体A中包含指向结构体B的指针
struct A {

    struct B* partner;
};

//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B{
    struct A* partner;
};


 

 2. 结构体变量的初始化

和其它类型变量一样,对结构体变量可以在定义时指定初始值。

#include <stdio.h>
#include <string.h

//C结构体定义与使用。  第一种写法
struct Dog {
    // 成员
    char name[10]; //char类型 数组     需要copy进去 字符串
    int age;
    char sex;

}; // 必须给写;


// C结构体定义与使用。  第2种写法
struct  Person{
    // 成员
    char * name; //  字符指针name = 直接指向 一个字符串常量"DerryO"
    int age;
    char sex;

}ppp={ "Derry", 33, 'M' },  // 结构体的成员变量在ppp这里直接初始化赋值
ppp2,
ppp3,
pppp4,
pppp5;

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};


// C结构体定义与使用。  第3种写法
struct  Study{
    char*  studyContent; // 学习的内容  //  字符指针studyContent = 直接指向一个字符串常量  "学习C"
};

struct  Student{
    char name[10];     //char类型 数组     需要copy进去 字符串
    int age;
    char sex;

    // Study study; // VS的写法
    struct Study study; // Clion工具的写法

   struct  Wan{
       char * wanContent; // 玩的内容  //    字符指针wanContent = 直接指向一个字符串常量 "王者农药"
   } wan;

     struct  Sleep{
       char* sleepTime;//    字符指针sleepTime = 直接指向一个字符串常量
   } sleep ;

};



int main5T345() {

    //========================第一种写法=========================================
    // 这样写完,成员是没有任何初始化的,成员默认值 是系统值(name:?@, age:3133440, sex:€)
    struct Dog dog;
    printf("name:%s, age:%d, sex:%c \n", dog.name, dog.age, dog.sex); //name:跦@, age:3645440, sex:€

    // 赋值操作
    // dog.name = "旺财"; //错误的写法
    strcpy(dog.name,"旺财9527");
    printf("输出dog.name=%s\n",dog.name); // 输出dog.name=旺财9527
    dog.age = 3;
    dog.sex = 'G';
    printf("name:%s, age:%d, sex:%c \n", dog.name, dog.age, dog.sex); // name:旺财9527, age:3, sex:G

    //=========================第二种写法========================================
   // struct Person ppp;
    printf("ppp.name=%s, ppp.age=%d, ppp.sex=%c \n", ppp.name, ppp.age, ppp.sex); //ppp.name=Derry, ppp.age=33, ppp.sex=M

     printf("pppp5.name=%s, pppp5.age=%d, pppp5.sex=%c \n", pppp5.name, pppp5.age, pppp5.sex); // ppp5 没有值   pppp5.name=(null), pppp5.age=0, pppp5.sex=

    // 给pppp5  赋值
    // strcpy(pppp5.name, "Derry5"); // Copy不进去
    pppp5.name=  "Derry5";
    pppp5.age = 5;
    pppp5.sex = 'M';
    printf("pppp5.name=%s, pppp5.age=%d, pppp5.sex=%c \n", pppp5.name, pppp5.age, pppp5.sex);  // pppp5.name=Derry5, pppp5.age=5, pppp5.sex=M


  //========================= 第三种写法===============================================
    //初始化
    struct Student student= {
            "李元霸", 88, 'm' ,
            {"学习C"},
            {"王者农药"},
            {"睡5个小时"}
    };

    printf(" student.name=%s, student.age=%d,  student.sex=%c,student.study.studyContent=%s, student.wan.wanContent=%s ,student.sleep.sleepTime=%s\n",
           student.name,student.age, student.sex, student.study.studyContent,student.wan.wanContent,student.sleep.sleepTime);
  //student.name=李元霸, student.age=88,  student.sex=m,student.study.studyContent=学习C, student.wan.wanContent=王者农药,student.sleep.sleepTime=睡5个小时

  return 0;
}

2.2 struct 结构体的嵌套

struct 结构的成员可以是另一个 struct 结构。

下面 示例展示了嵌套 Struct 结构的四种赋值写法。另外,引用breed属性的内部属性,要使用两次点运算符(shark.breed.name)。

struct species {
  char* name;
  int kinds;
};

struct fish {
  char* name;
  int age;
  struct species breed;  //fish的属性breed是另一个 struct 结构species。
};


int main(){
 //赋值的时候有多种写法。
// 写法一
struct fish shark = {"shark", 9, {"Selachimorpha", 500}};


// 写法二
struct species myBreed = {"Selachimorpha", 500};
struct fish shark = {"shark", 9, myBreed};

// 写法三
struct fish shark = {
  .name="shark",
  .age=9,
  .breed={"Selachimorpha", 500}
};


// 写法四
struct fish shark = {
  .name="shark",
  .age=9,
  .breed.name="Selachimorpha",
  .breed.kinds=500
};

printf("Shark's species is %s", shark.breed.name);


return 0;
}

再来一个嵌套 struct 的例子


//自定义类型student的name属性是另一个自定义类型,如果要引用后者的属性,就必须使用两个.运算符,比如student1.name.first
//对字符数组属性赋值,要使用strcpy()函数,不能直接赋值,因为直接改掉字符数组名的地址会报错。

struct name {
  char first[50];
  char last[50];
};

struct student {
  struct name name;
  short age;
  char sex;
} student1;

int main(){
strcpy(student1.name.first, "Harry");
strcpy(student1.name.last, "Potter");


// or
struct name myname = {"Harry", "Potter"};
student1.name = myname;


return 0;
}

3. 访问结构成员

    访问结构的成员, 使用成员访问运算符(.)
  成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号
  您可以使用 struct 关键字来定义结构类型的变量

#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;

   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 输出 Book1 信息 */
   printf( "Book 1 title : %s\n", Book1.title);
   printf( "Book 1 author : %s\n", Book1.author);
   printf( "Book 1 subject : %s\n", Book1.subject);
   printf( "Book 1 book_id : %d\n", Book1.book_id);

   /* 输出 Book2 信息 */
   printf( "Book 2 title : %s\n", Book2.title);
   printf( "Book 2 author : %s\n", Book2.author);
   printf( "Book 2 subject : %s\n", Book2.subject);
   printf( "Book 2 book_id : %d\n", Book2.book_id);

   return 0;
}

4.结构体作为函数参数

您可以把结构体作为函数参数,传参方式与其他类型的变量或指针类似。

#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

/* 函数声明 */
void printBook( struct Books book );
int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;

   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 输出 Book1 信息 */
   printBook( Book1 );

   /* 输出 Book2 信息 */
   printBook( Book2 );

   return 0;
}
void printBook( struct Books book )
{
   printf( "Book title : %s\n", book.title);
   printf( "Book author : %s\n", book.author);
   printf( "Book subject : %s\n", book.subject);
   printf( "Book book_id : %d\n", book.book_id);
}

5.指针变量 指向struct结构

 您可以定义指向结构体的指针,方式与定义指向其他类型变量的指针相似,

 struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

 //给结构体 struct Books 声明一个变量 Book1
   struct Books Book1;  
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali");
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;

 
//变量指针struct_pointer    指向struct Books数据类型的实例
struct Books *struct_pointer;
  //您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算 符放在结构名称的前面,
  struct_pointer = &Book1;
  //为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符
   strcpy( struct_pointer->title, "Telecom Billing");
   strcpy( struct_pointer->author, "Zara Ali");
   strcpy( struct_pointer->subject, "Telecom Billing Tutorial");
   struct_pointer->book_id = 6495700;

// 变量b1是一个指针,指向的数据是struct book类型的实例
struct book {
  char title[500];
  char author[100];
  float value;
}* b1;

// 或者写成两个语句
struct book {
  char title[500];
  char author[100];
  float value;
};
// 变量b1是一个指针,指向的数据是struct book类型的实例
struct book* b1;
strcpy( struct_pointer->title, "Telecom Billing");
strcpy( struct_pointer->author, "Zara Ali");
strcpy( struct_pointer->subject, "Telecom Billing Tutorial");
struct_pointer->book_id = 6495700;

#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

/* 函数声明 */
void printBook( struct Books *book );
int main( )
{
    /* 声明 Book1,类型为 Books 直接初始化数据 */
   struct Books Book1={
    "C Programming",
    "Nuha Ali",
   "C Programming Tutorial"
   ,6495407
    }; 

  /* 声明 Book2,类型为 Books */       
   struct Books Book2;      
 
   

   // Book2 详述  通过结构体声明的变量 调用成员赋值
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
  
  struct Books *struct_pointer1;
  struct_pointer1 = &Book1;
 //为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符
  strcpy( struct_pointer1->title, "Al创世者");
   strcpy( struct_pointer1->author, "保罗");
   strcpy( struct_pointer1->subject, "科幻");
   struct_pointer1->book_id = 00123456;
 
  /* 通过传 Book1 的地址来输出 Book1 信息 */
   printBook( struct_pointer1  );

 

 struct Books *struct_pointer2;
  struct_pointer2 = &Book2;
 //为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符
  strcpy( struct_pointer2->title, "第九区");
   strcpy( struct_pointer2->author, "斯皮尔伯格");
   strcpy( struct_pointer2->subject, "科幻");
   struct_pointer2->book_id = 004567987;

  /* 通过传 Book2 的地址来输出 Book2 信息 */
   printBook( struct_pointer2  );

   return 0;
}
void printBook( struct Books *book )
{
   printf( "Book title : %s\n", book->title);
   printf( "Book author : %s\n", book->author);
   printf( "Book subject : %s\n", book->subject);
   printf( "Book book_id : %d\n", book->book_id);
}

   5T67

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
 /**
 结构体指针 与 动态内存开辟。
 */

struct  Cat{
    char name[10];   //char类型 数组     需要copy进去 字符串
    int age;
};


int main5T67() { // 栈

 //======================结构体指针======================
 // 声明一个结构体    初始化结构体
 struct  Cat  cat = {"小花猫", 2};
    printf("cat.name=%s, cat.age=%d \n", cat.name, cat.age); //cat.name=小花猫, cat.age=2

    // 结构体 指针
    // VS的结构体 指针写法  Cat * catp = &cat;
    // Clion的结构体 指针写法  struct Cat * catp = &cat;
    struct Cat * catp;
       catp = &cat;
    //   -> 调用一级指针catp的成员变量age
    catp->age=3;
    strcpy(catp->name,"小黑猫");
    printf("catp->name=%s, catp->age=%d \n", catp->name, catp->age); //catp->name=小黑猫, catp->age=3



   //=================== 结构体指针 与 动态内存开辟==================================
    // VS的写法:动态内存开辟 : 必须强转 Cat * cat = (Cat *) malloc(sizeof(Cat));
   //  Clion的 动态内存开辟    struct Cat *cat2 = malloc(sizeof(struct Cat));
    struct Cat *cat2 = malloc(sizeof(struct Cat));
    cat2->age=4;
    strcpy(cat2->name,"小白猫");
    printf("cat2->name=%s, cat2->age=%d \n", cat2->name, cat2->age); // cat2->name=小白猫, cat2->age=4

    // 堆区的必须释放
    free(cat2);
    cat2 = NULL;
    return 0;
}

6.struct 结构指针    

#include <stdio.h>
#include <string.h>
struct turtle {
    char name[100];
    char  species[100];
    int age;
};
/**
 * 函数happy()传入的是一个 struct turtle 变量myTurtle
 * 函数内部对变量myTurtle 的成员name  species  age 进行了修改操作
 * 但是,执行完happy()以后,函数外部调用后再次输出结构体struct turtle 变量myTurtle的成员信息 没有发生改变。
 *
 * 原因就是函数内部得到的是 struct 变量的副本,改变副本数据影响不到函数外部的原始数据。
 * */
void happy(struct turtle t) {
    strcat(t.name,"mingfei");
    strcat(t.species,"0210");
    t.age = t.age + 1;
}

/**
 * 通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。
 * 这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性,就可以影响到函数外部。

  t是 struct turtle 结构的指针,调用函数时传入的是指针。
 函数内部也必须使用(*t).age的写法,从指针拿到 struct 结构本身。
 (*t).age不能写成*t.age,因为点运算符.的优先级高于*。*t.age这种写法会将t.age看成一个指针,然后取它对应的值,会出现无法预料的结果。

 */
void happy2(struct turtle* t) {
    strcat((*t).name,"ming");
    strcat((*t).species,"02");
    (*t).age=(*t).age+1;
}

/**
 * (*t).age这样的写法很麻烦。C 语言就引入了一个新的箭头运算符(->),可以从 struct 指针上直接获取属性,大大增强了代码的可读性。
 * */
void happy3(struct turtle* t) {
    strcat(t->name,"fei");
    strcat(t->species,"10");
    t->age=t->age+1;
}


int  main(){
    struct turtle myTurtle = {"MyTurtle", "sea turtle", 99};
    printf("执行happy函数前输出 myTurtle.name=%s ,myTurtle.species=%s  myTurtle.age=%d\n",myTurtle.name,myTurtle.species,myTurtle.age );
    //执行happy函数前输出 myTurtle.name=MyTurtle ,myTurtle.species=sea turtle  myTurtle.age=99
    strcat(myTurtle.name,"fang");
    strcat(myTurtle.species,"1987");
    myTurtle.age=myTurtle.age+1;
    printf("执行happy函数前再次输出 myTurtle.name=%s ,myTurtle.species=%s  myTurtle.age=%d\n",myTurtle.name,myTurtle.species,myTurtle.age );
    //执行happy函数前再次输出 myTurtle.name=MyTurtlefang ,myTurtle.species=sea turtle1987  myTurtle.age=100
    happy(myTurtle);//todo 变量成员没发生改变
    printf("执行happy函数后输出 myTurtle.name=%s ,myTurtle.species=%s  myTurtle.age=%d\n",myTurtle.name,myTurtle.species,myTurtle.age );
    //执行happy函数后输出 myTurtle.name=MyTurtlefang ,myTurtle.species=sea turtle1987  myTurtle.age=100

   struct turtle*  myTurtle_p=&myTurtle;
   happy2(myTurtle_p);//todo 变量成员发生改变
   printf("执行happy2函数后输出 myTurtle.name=%s ,myTurtle.species=%s  myTurtle.age=%d\n",myTurtle.name,myTurtle.species,myTurtle.age );
   //执行happy2函数后输出 myTurtle.name=MyTurtlefangming ,myTurtle.species=sea turtle198702  myTurtle.age=101
    happy3(myTurtle_p);//todo 变量成员发生改变
    printf("执行happy3函数后输出 myTurtle.name=%s ,myTurtle.species=%s  myTurtle.age=%d\n",myTurtle.name,myTurtle.species,myTurtle.age );
    //执行happy3函数后输出 myTurtle.name=MyTurtlefangmingfei ,myTurtle.species=sea turtle19870210  myTurtle.age=102
}

7.结构体作为数组。 5T8

struct fraction{
    int numerator;
    int denominator;
};
int main5T8(){
    // 声明了一个有1000个成员的数组numbers,每个成员都是自定义类型fraction的实例
    struct  fraction numberArr[1000];
    numberArr[0].denominator=22;
    numberArr[0].numerator=7;

}



#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/**
 结构体的数组。
  */
struct Cat3 {
    char name[10];  //char类型 数组     需要copy进去 字符串
    int age;
};

int main(){

    //=================== 栈区 静态内存 申请   结构体数组 catArr===================
    struct   Cat3 catArr[10]={
            {"小黄", 0},
            {"小白", 1},
            {"小黑", 2},
            {},
            {},
            {},
            {},
            {},
            {},
            {"小红", 99}
    };
   for(int i=0;i<10;i++){
       printf("catArr[%d].name=%s, catArr[%d].age=%d   ",i, catArr[i].name,i, catArr[i].age);
   }
/**
 * catArr[0].name=小黄, catArr[0].age=0   catArr[1].name=小白, catArr[1].age=1   catArr[2].name=小黑, catArr[2].age=2   cat
Arr[3].name=, catArr[3].age=0   catArr[4].name=, catArr[4].age=0   catArr[5].name=, catArr[5].age=0   catArr[6].name=, c
atArr[6].age=0   catArr[7].name=, catArr[7].age=0   catArr[8].name=, catArr[8].age=0   catArr[9].name=小红, catArr[9].age=99
 */

   // 给   结构体数组 catArr 最后一个元素赋值
    // VS的写法
    // cat[9] = {"小黑9", 9},
    // CLION的写法
    //声明一个结构体cat9   并初始化数据
    struct  Cat3 cat9 = {"小黑9", 9};
    // 直接get数组 第9个元素catArr[9] 赋值cat9
     catArr[9]=cat9;


    //  修改 结构体cat9 的数据
     strcpy(cat9.name,"小黑92");
     cat9.age=92;

    // 或者 通过数组指针挪到catArr +9  给*(catArr + 9)再次赋值cat9
    *(catArr + 9)=cat9;

    for(int i=0;i<10;i++){
        printf("catArr[%d].name=%s, catArr[%d].age=%d   ",i, catArr[i].name,i, catArr[i].age);
    }

    /**
 catArr[0].name=小黄, catArr[0].age=0   catArr[1].name=小白, catArr[1].age=1   catArr[2].name=小黑, catArr[2].age=2
     catArr[3].name=, catArr[3].age=0   catArr[4].name=, catArr[4].age=0   catArr[5].name=, catArr[5].age=0   catArr[6].n
 ame=, catArr[6].age=0   catArr[7].name=, catArr[7].age=0   catArr[8].name=, catArr[8].age=0   catArr[9].name=小黑9, catArr[9].age=9
 */
/**
 catArr[0].name=小黄, catArr[0].age=0   catArr[1].name=小白, catArr[1].age=1   catArr[2].name=小黑, catArr[2].age=2
 catArr[3].name=, catArr[3].age=0   catArr[4].name=, catArr[4].age=0   catArr[5].name=, catArr[5].age=0   catArr[6].n
ame=, catArr[6].age=0   catArr[7].name=, catArr[7].age=0   catArr[8].name=, catArr[8].age=0   catArr[9].name=小黑92, catArr[9].age=92
 */

    printf("\n\n\n");
//================== 堆区 动态内存申请  结构体数组 catArr2  ==============================
    struct  Cat3 * catArr2 = malloc(sizeof (struct Cat3)* 10 );
    for(int i=0;i<10;i++){
        printf("catArr2[%d].name=%s, catArr2[%d].age=%d   ",i, catArr2[i].name,i, catArr2[i].age);
    }
    printf("\n\n\n==============================\n\n\n");
/**
 *  空的  数组catArr2  没数据
 * */

    // 【1元素地址的操作】给他赋值,请问是赋值,那个元素  (默认指向首元素地址)
   strcpy(catArr2->name,"小花猫000");
    catArr2->age = 1000;
    for(int i=0;i<10;i++){
        printf("catArr2[%d].name=%s, catArr2[%d].age=%d ||  ",i, catArr2[i].name,i, catArr2[i].age);
    }
    printf("\n\n\n==========================\n\n\n");
    /**
 catArr2[0].name=小花猫000, catArr2[0].age=1000 ||  catArr2[1].name=p+? catArr2[1].age=1346586189 ||  catArr2[2].name=P\x
x4_ndk_cpp\1\cmake-build-debug, catArr2[2].age=1546738800 ||  catArr2[3].name=cmake-build-debug, catArr2[3].age=19693827
56 ||  catArr2[4].name=g, catArr2[4].age=45864 ||  catArr2[5].name=p+? catArr2[5].age=1597274232 ||  catArr2[6].name=ndk
_cpp.exe, catArr2[6].age=6646625 ||  catArr2[7].name=€                                               鬍?? catArr2[7].a
ge=538976288 ||  catArr2[8].name=                                鬍?? catArr2[8].age=538976288 ||  catArr2[9].name=
            鬍?? catArr2[9].age=538976288 ||
 */

    // 【8元素地址的操作】 给第八个元素赋值
    //将当前指针catArr2  挪动到第8个位置 catArr2+7
    //catArr2  = catArr2+ 7;
    strcpy((catArr2+7)->name, "小花猫888");
    (catArr2+7)->age = 888;
    printf("(catArr2+%d)->name=%s, (catArr2+%d)->age=%d  \n\n ",7, (catArr2+7)->name,7, (catArr2+7)->age  );  //(catArr2+7)->name=小花猫888, (catArr2+7)->age=888

  //遍历挪动指针catArr2 看看效果
    for(int i=0;i<10;i++){
        printf("(catArr2+%d)->name=%s, (catArr2+%d)->age=%d \n  ",i, (catArr2+i)->name,i, (catArr2+i)->age);
    }
/**
 *  (catArr2+0)->name=小花猫000, (catArr2+0)->age=1000
  (catArr2+1)->name=p+f, (catArr2+1)->age=1346586189
  (catArr2+2)->name=P\xx4_ndk_cpp\1\cmake-build-debug, (catArr2+2)->age=1546738800
  (catArr2+3)->name=cmake-build-debug, (catArr2+3)->age=1969382756
  (catArr2+4)->name=g, (catArr2+4)->age=59919
  (catArr2+5)->name=p+f, (catArr2+5)->age=1597274232
  (catArr2+6)->name=ndk_cpp.exe, (catArr2+6)->age=6646625
  (catArr2+7)->name=小花猫888, (catArr2+7)->age=888
  (catArr2+8)->name=                                Y?? (catArr2+8)->age=538976288
  (catArr2+9)->name=                Y?? (catArr2+9)->age=538976288
  */

    free(catArr2);
    catArr2 = NULL;
    return  0;
}

7.2  弹性数组成员

 很多时候,不能事先确定数组到底有多少个成员。如果声明数组的时候,事先给出一个很大的成员数,就会很浪费空间。
 C 语言提供了一个解决方法,叫做弹性数组成员(flexible array member)。

/*
 struct vstring结构有两个属性。
len属性用来记录数组chars的长度,chars属性是一个数组,但是没有给出成员数量。
chars数组到底有多少个成员,可以在为vstring分配内存时确定。
*/
 struct vstring {
  int len; //len属性用来记录数组chars的长度
  char chars[]; //chars属性是一个数组,但是没有给出成员数量。
};
 
//chars数组到底有多少个成员,可以在为vstring分配内存时确定。 
 struct vstring* str = malloc(sizeof(struct vstring) + n * sizeof(char));
str->len = n;
 

上面示例中,假定chars数组的成员数量是n,只有在运行时才能知道n到底是多少。然后,就为struct vstring分配它需要的内存:它本身占用的内存长度,再加上n个数组成员占用的内存长度。最后,len属性记录一下n是多少。

这样就可以让数组charsn个成员,不用事先确定,可以跟运行时的需要保持一致。

弹性数组成员有一些专门的规则。首先,弹性成员的数组,必须是 struct 结构的最后一个属性。另外,除了弹性数组成员,struct 结构必须至少还有一个其他属性。

8.结构体大小的计算

C 语言中,我们可以使用 sizeof 运算符来计算结构体的大小,sizeof 返回的是给定类型或变量的字节大小。
  对于结构体,sizeof 将返回结构体的总字节数,包括所有成员变量的大小以及可能的填充字节。
 
  struct Person person;
  sizeof(person)

typedef   定义类型名称别名 (配合结构体学习)(重要)

typedef的几种使用方式

typedef命令用来为某个类型起别名的关键字。type代表类型名,name代表别名。

typedef type name;
 

 // typedef命令为类型unsign char起别名BYTE, 为别名BYTE 声明多个不同的变量a b c
    typedef unsigned char BYTE;
     BYTE a, b ,c;
     // 对变量a b c 进行初始化
    a='x';
    b='y';
    c = 'z';
    printf("a=%c  b=%c  c=%c \n",a,b,c); //a=x  b=y  c=z
// typedef 可以一次指定多个别名。
   // typedef一次性为int类型起了三个不同别名antelope, bagel, mushroom。
    typedef int antelope, bagel, mushroom;
    //为别名antelope 定义一个变量ant 并初始化
    antelope ant = 1;
    //为别名bagel定义一个变量bag 并初始化
    bagel  bag =2;
    //为别名mushroom 定义一个变量mush 并初始化
    mushroom mush=3;
    printf("ant = %d , bag =%d ,mush=%d \n",ant,bag ,mush); //ant = 1 , bag =2 ,mush=3
struct Book
{
    char  title[50];
    char  author[50];
    char  subject[100];
    int   book_id;
};

int main(){

 // 为结构体Book 起了3个别名 Book1,Book2,Book3
    typedef struct Book Book1,Book2,Book3;

    //为结构体别名Book1 声明变量book1
    Book1 book1;
    //对变量book1的某个成员进行初始化
    strcpy(book1.author,"C教程");

    //为结构体别名Book2 声明变量book2
    Book2 book2;
    //对变量book2的某个成员进行初始化
    strcpy(book2.subject,"Runoob");

    //为结构体别名Book3 声明变量book3
    Book3 book3;
    //对变量book3的某个成员进行初始化
    strcpy(book3.title,"编程语言");
    printf("book1.author=%s  book2.subject=%s book3.title=%s\n",book1.author,book2.subject,book3.title); //book1.author=C教程  book2.subject=Runoob book3.title=编程语言
    return 0;

}

  typedef  可以对结构体定义一个新的结构体别名 然后使用这个别名直接定义结构体变量

//typedef  为结构体Books定义一个别名Book
typedef struct Books
{
    char  title[50];
    char  author[50];
    char  subject[100];
    int   book_id;
} Book;   //typedef  为结构体Books定义一个别名Book

int main(){
  //对这个结构体别名Books 声明一个变量book1
 struct Books book1;

    strcpy( book1.title, "C 教程");
    strcpy( book1.author, "Runoob");
    strcpy( book1.subject, "编程语言");
    book1.book_id = 12345;
    printf( "书标题 : %s\n", book1.title);
    printf( "书作者 : %s\n", book1.author);
    printf( "书类目 : %s\n", book1.subject);
    printf( "书 ID : %d\n", book1.book_id);
/**
 * 书标题 : C 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
 */

 //对结构体别名Book  声明一个变量book2   
 Book  book2;
    strcpy( book2.title, "C 教程 2 ");
    strcpy( book2.author, "Runoob 2");
    strcpy( book2.subject, "编程语言 2");
    book2.book_id = 123452;
    printf( "书标题 : %s\n", book2.title);
    printf( "书作者 : %s\n", book2.author);
    printf( "书类目 : %s\n", book2.subject);
    printf( "书 ID : %d\n", book2.book_id);

/**
 书标题 : C 教程 2
书作者 : Runoob 2
书类目 : 编程语言 2
书 ID : 123452
 */


return 0;

}
// typedef 为 匿名结构体 定义一个结构体别名AV
typedef  struct {
    char name[10];
    int age;
    char sex;
} AV;// typedef 为 匿名结构体 定义一个结构体别名AV


int main(){
//为结构体的别名AV 声明一个变量av 并对变量进行初始化 
 AV av = {"VideoInfo", 54, 'M'};
 printf("输出 av.name=%s,av.age=%d av.sex=%c\n",av.name,av.age,av.sex); // 输出 av.name=VideoInfo,av.age=54 av.sex=M
return0;

}

typedef 可以为指针起别名。

 intptr是int*类型指针的别名
typedef int* intptr;
int a = 10;

// 为别名intptr声明一个变量x(指针类型)   并进行初始化  
intptr x = &a;


typedef为数组类型起别名。

 
typedef int five_ints[5];
five_ints x = {11, 22, 33, 44, 55};

typedef 为函数起别名 

类型别名fp是一个指针,代表函数signed char (*)(void)。

typedef signed char (*fp)(void);

取别名的主要好处

typedef为类型起别名的好处,主要有下面几点。

(1)更好的代码可读性。

// 为字符指针起别名为STRING   以后使用STRING声明变量时,就可以轻易辨别该变量是字符串。
typedef char* STRING;

STRING name;

(2)为 struct、union、enum 等命令定义的复杂数据结构创建别名,从而便于引用。

struct treenode {
  // ...
};

// Tree为struct treenode*的别名
typedef struct treenode* Tree;

使用typedef命令,为结构体struct animal起了一个别名animal。
typedef struct animal {
  char* name;
  int leg_count, speed;
} animal;

使用typedef命令,为一个匿名的数据类型起了别名animal。  
 typedef struct {
  char *name;
  int leg_count, speed;
} animal;
 
 
 
  (3)typedef 方便以后为变量改类型。
 
  typedef float app_float;
   
 // 变量f1、f2、f3的类型都是float。如果以后需要为它们改类型,只需要修改typedef语句即可。
app_float f1, f2, f3;

//  将变量f1、f2、f3的类型都改为long double类型。
typedef long double app_float;
 
 
(4)可移植性   
 
 (5)简化类型声明

  typedef char (*Func)(void);
  typedef Func Arr[5];
  Arr* x(void);
 
x是一个函数,返回一个指向 Arr 类型的指针。

  Arr是一个数组,有5个成员,每个成员是Func类型。 

Func是一个函数指针,指向一个无参数、返回字符值的函数。

typedef 与 #define 区别

typedef 仅限于为类型定义符号名称,
  typedef 是由编译器执行解释的
 
 
  #define 是 C 指令,用于为各种数据类型定义别名,
  #define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  #define 语句是由预编译器进行处理的。

#include <stdio.h>
 
#define TRUE  1
#define FALSE 0
 
int main( )
{
   printf( "TRUE 的值: %d\n", TRUE);
   printf( "FALSE 的值: %d\n", FALSE);
 
   return 0;
}

 C enum(枚举)

枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量。
  枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。
  定义一个枚举类型,需要使用 enum 关键字,后面跟着枚举类型的名称,以及用大括号 {} 括起来的一组枚举常量。
  每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。
  enum 枚举名 {枚举元素1,枚举元素2,……};
 
  enum DAY{MON=1, TUE, WED, THU, FRI, SAT, SUN};
 
 注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。
 如果我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。

可以在定义枚举类型时改变枚举元素的值:

                         //  0             3               4               5

enum season {spring, summer=3, autumn, winter};

没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

枚举变量的定义

我们可以通过以下三种方式来定义枚举变量

1、先定义枚举类型,再定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

2、定义枚举类型的同时定义枚举变量

enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

3、省略枚举名称,直接定义枚举变量

enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

 枚举案例:

#include <stdio.h>
 
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
 
int main()
{
    //声明枚举变量
    enum DAY day;
    // 初始化
    day = WED;
    printf("%d",day);//3
    return 0;
}

枚举在 switch 中的使用:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 
    enum color { red=1, green, blue };
 
    enum  color favorite_color;
 
    /* 用户输入数字来选择颜色 */
    printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
    scanf("%u", &favorite_color);
 
    /* 输出结果 */
    switch (favorite_color)
    {
    case red:
        printf("你喜欢的颜色是红色");
        break;
    case green:
        printf("你喜欢的颜色是绿色");
        break;
    case blue:
        printf("你喜欢的颜色是蓝色");
        break;
    default:
        printf("你没有选择你喜欢的颜色");
    }
 
    return 0;
}

将整数转换为枚举

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
 
    enum day
    {
        saturday,
        sunday,
        monday,
        tuesday,
        wednesday,
        thursday,
        friday
    } workday;
 
    int a = 1;
    enum day weekend;
    weekend = ( enum day ) a;  //类型转换
    //weekend = a; //错误
    printf("weekend:%d",weekend); //weekend:1
    return 0;
}

C 预处理器

C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。
 简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。
 我们将把 C 预处理器(C Preprocessor)简写为 CPP。
 所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
 下面列出了所有重要的预处理器指令:

指令描述
#define定义宏
#include包含一个源代码文件
#undef取消已定义的宏
#ifdef如果宏已经定义,则返回真
#ifndef如果宏没有定义,则返回真
#if如果给定条件为真,则编译下面代码
#else#if 的替代方案
#elif如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个 #if……#else 条件编译块
#error当遇到标准错误时,输出错误消息
#pragma使用标准化方法,向编译器发布特殊的命令到编译器中

预处理器实例

这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 定义为 20。使用 #define 定义常量来增强可读性。
#define MAX_ARRAY_LENGTH 20
 
  这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,
  并添加内容到当前的源文件中。
 #include <stdio.h>
 #include "myheader.h"

 
 
 这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
 #undef  FILE_SIZE
#define FILE_SIZE 42

 
 这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
 #ifndef MESSAGE
 #define MESSAGE "You wish!"
 #endif

 
 这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。
 在编译时,如果您向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。
 它定义了 DEBUG,您可以在编译期间随时开启或关闭调试。
 #ifdef DEBUG
   /* Your debugging statements here */
#endif

预定义宏

ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。

描述
__DATE__当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
__TIME__当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
__FILE__这会包含当前文件名,一个字符串常量。
__LINE__这会包含当前行号,一个十进制常量。
__STDC__当编译器以 ANSI 标准编译时,则定义为 1。
#include <stdio.h>
 
main()
{
   printf("File :%s\n", __FILE__ ); // File :test.c 
   printf("Date :%s\n", __DATE__ ); // Date :Jun 2 2012 
   printf("Time :%s\n", __TIME__ ); // Time :03:36:24 
   printf("Line :%d\n", __LINE__ ); // Line :8 
   printf("ANSI :%d\n", __STDC__ ); // ANSI :1
 
}

预处理器运算符

C 预处理器提供了下列的运算符来帮助您创建宏:

1.宏延续运算符(\)

 一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。

 #define  message_for(a, b)  \                //宏
    printf(#a " and " #b ": We love you!\n") //换行后的宏

2.字符串常量化运算符(#)

在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。


宏的参数 a, 转成字符串常量#a=Carole
宏的参数 b, 转成字符串常量#b=Debra

#include <stdio.h>
       //宏的参数 a, 转成字符串常量#a=Carole
       //宏的参数 b, 转成字符串常量#b=Debra
#define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n") 
 
int main(void)
{
   message_for(Carole, Debra);
   return 0;
}

/**
输出:
Carole and Debra: We love you!
*/

3.标记粘贴运算符(##)

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。

4.defined() 运算符

预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:

#include <stdio.h>
 
#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif
 
int main(void)
{
   printf("Here is the message: %s\n", MESSAGE);  // Here is the message: You wish!

   return 0;
}

参数化的宏

CPP 一个强大的功能是可以使用参数化的宏来模拟函数。

例如,下面的代码是计算一个数的平方:

int square(int x) {
   return x * x;
}

我们可以使用宏重写上面的代码,如下:

#define square(x) ((x) * (x))

在使用带有参数的宏之前,必须使用 #define 指令定义。参数列表是括在圆括号()内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格。

#define MAX(x,y) ((x) > (y) ? (x) : (y))

#include <stdio.h>
 
#define MAX(x,y) ((x) > (y) ? (x) : (y))
 
int main(void)
{
   printf("Max between 20 and 10 is %d\n", MAX(10, 20));//Max between 20 and 10 is 20

   return 0;
}

C 头文件

  头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。

有两种类型的头文件:程序员编写的头文件编译器自带的头文件。

在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是编译器自带的头文件。

引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。

引用头文件的语法

使用预处理指令 #include 可以引用用户和系统头文件。

这种形式用于引用系统头文件。 引用的是编译器的类库路径里面的头文件。

#include <file>

这种形式用于引用用户定义的头文件。

#include "file"

引用头文件的操作

#include 指令会指示 C 预处理器浏览指定的文件作为输入。预处理器的输出包含了已经生成的输出,被引用文件生成的输出以及 #include 指令之后的文本输出

头文件 header.h

char *test (void);

头文件 program.c

int x;
#include "header.h" //引用头文件header.h 的函数char *test (void);

int main (void)
{
   puts (test ());
}

只引用一次头文件

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

如果没有引用头文件HEADER_FILE  条件为true  就进行引用头文件HEADER_FILE 执行下面整个逻辑。 如果已经引用了头文件HEADER_FILE  条件为false    头文件HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。

#ifndef HEADER_FILE 
#define HEADER_FILE

the entire header file file

#endif

有条件引用

有时需要从多个不同的头文件中选择一个引用到程序中。

例如,需要指定在不同的操作系统上使用的配置参数。您可以通过一系列条件来实现这点,如下:

#if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif

在有多个 .h 文件的时候    往往我们会用一个 global.h 的头文件来包括所有的 .h 文件,然后在除 global.h 文件外的头文件中 包含 global.h 就可以实现所有头文件的包含,同时不会乱。方便在各个文件里面调用其他文件的函数或者变量。

#ifndef _GLOBAL_H
#define _GLOBAL_H
#include <fstream>
#include <iostream>
#include <math.h>
#include <Config.h>

C 内存管理

C 语言的内存管理,分成两部分。一部分是系统管理的,另一部分是用户手动管理的。

系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。这些变量存放的区域称为”栈“(stack),”栈“所在的内存是系统自动管理的。

用户手动管理的内存,主要是程序运行的整个过程中都存在的变量(全局变量),这些变量需要用户手动从内存释放。如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为”内存泄漏“(memory leak)。这些变量所在的内存称为”堆“(heap),”堆“所在的内存是用户手动管理的。

C 语言在 <stdlib.h> 头文件 里 为内存的分配和管理提供了几个函数。   

在 C 语言中,内存是通过指针变量来管理的。指针是一个变量,它存储了一个内存地址,这个内存地址可以指向任何数据类型的变量,包括整数、浮点数、字符和数组等。

序号函数和描述
1void *calloc(int num, int size);
在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。
2void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3void *malloc(int num);
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展到 newsize

注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

动态分配内存

编程时,如果您预先知道数组的大小,那么定义数组时就比较容易。例如,一个存储人名的数组,它最多容纳 100 个字符,所以您可以定义数组,如下所示:

char name[100];

但是,如果您预先不知道需要存储的文本长度,例如您想存储有关一个主题的详细描述。在这里,我们需要定义一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:

void 指针 void*

前面章节已经说过了,每一块内存都有地址,通过指针变量可以获取指定地址的内存块。指针变量必须有类型,否则编译器无法知道,如何解读内存块保存的二进制数据。但是,向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。

为了满足这种需求,C 语言提供了一种不定类型的指针,叫做 void 指针。它只有内存块的地址信息,没有类型信息,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。

另一方面,void 指针等同于无类型指针,可以指向任意类型的数据,但是不能解读数据。void 指针与其他所有类型指针之间是互相转换关系,任一类型的指针都可以转为 void 指针,而 void 指针也可以转为任一类型的指针。

// 整数指针int* 和 void*指针如何互相转换。
 int x = 10;
 // &x是一个整数指针,p是 void 指针,赋值时&x的地址会自动解释为 void 类型
void* p = &x; // 整数指针&x转为 void*指针p
// p再赋值给整数指针q时  p的地址会自动解释为整数指针。
int* q = p; // void* 指针转为整数指针int*

void* malloc(size_t size)

1.malloc()函数用于分配内存,该函数向系统要求一段内存,系统就在“堆”里面分配一段连续的内存块给它。它的原型定义在头文件stdlib.h。
  它接受一个非负整数作为参数,表示所要分配的内存字节数,返回一个 void 指针,指向分配好的内存块。
  这是非常合理的,因为malloc()函数不知道,将要存储在该块内存的数据是什么类型,所以只能返回一个无类型的 void 指针。
  可以使用malloc()为任意类型的数据分配内存,常见的做法是先使用sizeof()函数,算出某种数据类型所需的字节长度,然后再将这个长度传给malloc()。

 // 将malloc()返回的 void* 指针,强制转换成了整数指针int*。
 int* p = (int*)malloc(sizeof(int));
*p = 12;
printf("%d\n", *p); // 12

2.malloc()分配内存有可能分配失败,这时返回常量 NULL。Null 的值为0,是一个无法读写的内存地址,
可以理解成一个不指向任何地方的指针。它在包括stdlib.h等多个头文件里面都有定义,所以只要可以使用malloc(),
就可以使用NULL。由于存在分配失败的可能,所以最好在使用malloc()之后检查一下,是否分配成功。

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

// 判断返回的指针p是否为NULL,确定malloc()是否分配成功。
if (p == NULL) {
  // 内存分配失败
}

// or
if (!p) {
  //...
}

3.malloc()最常用的场合,就是为数组和自定义数据结构分配内存。

// p是一个整数指针,指向一段可以放置10个整数的内存,所以可以用作数组。
// malloc()可以创建动态数组,即根据成员数量的不同,而创建长度不同的数组。
int* p = (int*) malloc(sizeof(int) * 10);

for (int i = 0; i < 10; i++){
     p[i] = i * 5;
    }

4.malloc()可以创建动态数组,即根据成员数量的不同,而创建长度不同的数组。
malloc()可以根据变量n的不同,动态为数组分配不同的大小。
malloc()不会对所分配的内存进行初始化,里面还保存着原来的值。如果没有初始化,就使用这段内存,可能从里面读到以前的值。
程序员要自己负责初始化,比如,字符串初始化可以使用strcpy()函数。
int* p = (int*) malloc(n * sizeof(int));

// 字符指针p指向一段4个字节的内存,
char* p = malloc(4*sizeof(char));
// strcpy()将字符串“abc”拷贝放入这段内存,完成了这段内存的初始化。
strcpy(p, "abc");

// or
p = "abc";

void free(void* block)

free()用于释放malloc()函数分配的内存,将这块内存还给系统以便重新使用,
否则这个内存块会一直占用到程序运行结束。该函数的原型定义在头文件stdlib.h里面。

//free()的参数block 是malloc()返回的内存地址。
int* p = (int*) malloc(sizeof(int));
*p = 12;
free(p); //12 

注意,分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用free()对该地址释放第二次。

///一个很常见的错误是,在函数内部分配了内存,但是函数调用结束时,没有使用free()释放内存。
//函数gobble()内部分配了内存,但是没有写free(temp)。这会造成函数运行结束后,占用的内存块依然保留,如果多次调用gobble(),就会留下多个内存块。
//并且,由于指针temp已经消失了,也无法访问这些内存块,再次使用。
void gobble(double arr[], int n) {
  double* temp = (double*) malloc(n * sizeof(double));
  // ...
}

void* calloc(size_t n, size_t size);

 calloc()函数的作用与malloc()相似,也是分配内存块。该函数的原型定义在头文件stdlib.h。
calloc()分配的内存块,也要使用free()释放。

两者的区别主要有两点:

(1)calloc()接受两个参数,第一个参数是某种数据类型的值的数量,第二个是该数据类型的单位字节长度。
calloc()的返回值也是一个 void 指针。分配失败时,返回 NULL。
void* calloc(size_t n, size_t size);

(2)calloc()会将所分配的内存全部初始化为0。
malloc()不会对内存进行初始化,如果想要初始化为0,还要额外调用memset()函数。
// calloc()相当于malloc() + memset()。
int* p = calloc(10, sizeof(int));

// 等同于
int* p = malloc(sizeof(int) * 10);
memset(p, 0, sizeof(int) * 10);

void* realloc(void* block, size_t size)

realloc()函数用于修改已经分配的内存块的大小,可以放大也可以缩小,返回一个指向新的内存块的指针。
如果分配不成功,返回 NULL。该函数的原型定义在头文件stdlib.h。
block:已经分配好的内存块指针(由malloc()或calloc()或realloc()产生)。
size:该内存块的新大小,单位为字节。

realloc()可能返回一个全新的地址(数据也会自动复制过去),也可能返回跟原来一样的地址。
realloc()优先在原有内存块上进行缩减,尽量不移动数据,所以通常是返回原先的地址。
如果新内存块小于原来的大小,则丢弃超出的部分;如果大于原来的大小,则不对新增的部分进行初始化(程序员可以自动调用memset())。

// b是数组指针,realloc()动态调整它的大小。
int* b;

// 指针b使用malloc动态创建一个原来指向10个成员的整数数组,
b = malloc(sizeof(int) * 10);
//使用realloc()调整为2000个成员的数组。这就是手动分配数组内存的好处,可以在运行时随时调整数组的长度。
b = realloc(b, sizeof(int) * 2000);

//realloc()的第一个参数可以是 NULL,这时就相当于新建一个指针。
char* p = realloc(NULL, 3490);
// 等同于
char* p = malloc(3490);

//如果realloc()的第二个参数是0,就会释放掉内存块。
char* p = realloc(NULL, 0);

//调用realloc()后,有分配失败的可能, 最好检查一下它的返回值是否为 NULL。分配失败时,原有//内存块中的数据不会发生改变。
float* new_p = realloc(p, sizeof(*p * 40));

if (new_p == NULL) {
  printf("Error reallocing\n");
  return 1;
}

restrict 说明符

声明指针变量时,可以使用restrict说明符,告诉编译器,该块内存区域只有当前指针一种访问方式,
其他指针不能读写该块内存。这种指针称为“受限指针”(restrict pointer)。


//声明指针变量p时,加入了restrict说明符,使得p变成了受限指针。
// 当p指向malloc()函数返回的一块内存区域,就味着,该区域只有通过p来访问,不存在其他访问方式。

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

memcpy()

memcpy()用于将一块内存拷贝到另一块内存。该函数的原型定义在头文件string.h。

该函数会将从source开始的n个字节,拷贝到dest。
dest是目标地址,
source是源地址,
dest和source都是 void 指针,表示这里不限制指针类型,各种类型的内存数据都可以拷贝。两者都有 restrict 关键字,表示这两个内存块不应该有互相重叠的区域。
第三个参数n是要拷贝的字节数n. 。如果要拷贝10个 double 类型的数组成员,n就等于10 * sizeof(double),而不是10。
memcpy()的返回值是第一个参数,即目标地址的指针dest。
因为memcpy()只是将一段内存的值,复制到另一段内存,所以不需要知道内存里面的数据是什么类型。

void* memcpy(
  void* restrict dest,
  void* restrict source,
  size_t n
);

char* s = "hello world";
size_t len = strlen(s) + 1;
char *c = malloc(len);
if (c) {
  memcpy(c, s, len);
}

复制字符串的例子
数组的复制

#include <stdio.h>
#include <string.h>
int main(void) {
    // 字符串s所在的内存,被拷贝到字符数组t所在的内存。
     char s[] = "Goats!";
    char t[100];
    printf("输出s[]=%s,  t[100]=%s\n",s,t); //输出s[]=Goats!,  t[100]=o磜C箇b
    memcpy(t,s,sizeof(s));
    printf("输出s[]=%s,  t[100]=%s\n",s,t); //输出s[]=Goats!,  t[100]=Goats!
    return 0;
}

字符串的复制

//字符串的复制
char* s = "hello world";
size_t len = strlen(s) + 1;
char *c = malloc(len);

if (c) {
  // strcpy() 的写法
  strcpy(c, s);
 
  // memcpy() 的写法  两种写法的效果完全一样,但是memcpy()的写法要好于strcpy()
  memcpy(c, s, len);
}

memmove()

memmove()函数用于将一段内存数据复制到另一段内存。
它跟memcpy()的主要区别是,它允许目标区域与源区域有重叠。
如果发生重叠,源区域的内容会被更改;如果没有重叠,它与memcpy()行为相同。
该函数的原型定义在头文件string.h。

dest是目标地址,source是源地址,n是要移动的字节数。
dest和source都是 void 指针,表示可以移动任何类型的内存数据,
两个内存区域可以有重叠。
memmove()返回值是第一个参数,即目标地址的指针dest。
void* memmove(
  void* dest,
  void* source,
  size_t n
);

memcmp()

memcmp()函数用来比较两个内存区域。它的原型定义在string.h。

前两个参数是用来比较的指针,第三个参数指定比较的字节数。
如果两者相同,返回0;如果s1大于s2,返回大于0的整数;如果s1小于s2,返回小于0的整数。
int memcmp(
  const void* s1,
  const void* s2,
  size_t n
);

char* s1 = "abc";
char* s2 = "acd";
//比较s1和s2的前三个字节,由于s1小于s2,所以r是一个小于0的整数,一般为-1。
int r = memcmp(s1, s2, 3); // 小于 0
char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'};
char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'};

if (memcmp(s1, s2, 3) == 0) // true
if (memcmp(s1, s2, 4) == 0) // true
if (memcmp(s1, s2, 7) == 0) // false

获取数组的长度 与获取字符串的字节长度的区别

1.获取数组的长度sizeof()
sizeof()运算符会返回整个数组的字节长度。

int a[] = {22, 37, 3490};
// sizeof返回数组a的字节长度是12。
int arrLen = sizeof(a); // 12

char s[] = "Goats!";
 int arrLen= sizeof(s);

printf("数组s的字节长度=%d\n",arrLen); //数组s的字节长度=7

2.字符串的字节长度strlen()

 strlen()函数返回字符串的字节长度,不包括末尾的空字符\0。
它的参数是字符串变量s,返回的是size_t类型的无符号整数,
size_t strlen(const char* s);

char* str = "hello";
int len = strlen(str)+1; // 6

char* str = "hello world";
 size_t len = strlen(str) + 1;
 printf("字符串str的字节长度=%d\n",len);// 字符串str的字节长度=12

数组的复制 与字符串的复制  5memcpy

1.数组的复制

int main(void) {
    // 字符串s所在的内存,被拷贝到字符数组t所在的内存。
    char s[] = "Goats!";
    char t[100];
    printf("输出s[]=%s,  t[100]=%s\n",s,t); //输出s[]=Goats!,  t[100]=o磜C箇b
    // sizeof运算符会返回整个数组的字节长度。
    int arrLen= sizeof(s);
    printf("数组s的字节长度=%d\n",arrLen); //数组s的字节长度=7

    //todo 数组复制方法一:
    //复制数组最简单的方法,还是使用循环,将数组元素逐个进行复制给另一个数组的元素。
   // 将数组b的成员逐个复制给数组a,从而实现数组的赋值。
   for(int i=0;i<arrLen;i++){
     t[i]=s[i];
   }
    printf("输出s[]=%s,  t[100]=%s\n",s,t); //输出s[]=Goats!,  t[100]=Goats!

}

int main(){
 //todo 数组复制方法二:
  //  另一种方法是使用memcpy()函数(定义在头文件string.h),直接把数组所在的那一段内存,再复制一份。
  //  将数组b所在的那段内存,复制给数组a。这种方法要比循环复制数组成员要快。
  // memcpy()可以取代strcpy()进行字符串拷贝,而且是更好的方法,不仅更安全,速度也更快,它不检查字符串尾部的\0字符。
    memcpy(t,s,arrLen);
    printf("输出s[]=%s,  t[100]=%s\n",s,t); //输出s[]=Goats!,  t[100]=Goats!

}

2.字符串的复制

int main(){

  char* str = "hello world";
   /**
    获取字符串的字节长度
    size_t strlen(const char* s);
    strlen()函数返回字符串的字节长度,不包括末尾的空字符\0。
    它的参数是字符串变量s,返回的是size_t类型的无符号整数,
     */
    size_t len = strlen(str) + 1;
    printf("字符串str的字节长度=%d\n",len);// 字符串str的字节长度=12
    char *c = malloc(len);

     //todo  复制字符串的方法一
    if (c) {
        //两种写法的效果完全一样,但是memcpy()的写法要好于strcpy()。
        // strcpy() 的写法
        strcpy(c, str);
        printf("复制后字符串c=%s\n",c); //复制后字符串c=hello world
        // memcpy() 的写法
        memcpy(c, str, len);
        printf("复制后字符串c=%s\n",c);//复制后字符串c=hello world
    }

  free(c);
   c=NULL;

return 0;

}

void* my_memcpy(void *dest, void *src, int byte_count);

int main(){

//todo  复制字符串的方法二
 char* string_src="fangmingfei";
char* string_dest=(char*)malloc(sizeof(char));
   //字符串string_src的字节长度
  int  string_src_len = strlen(string_src)+1;
  printf("字符串的长度string_src_len=%d\n",string_src_len); //字符串的长度string_src_len=12
    printf("执行my_memcpy函数前,输出字符串string_dest=%s\n",string_dest); //执行my_memcpy函数前,输出字符串string_dest= "?
    //使用 void 指针,也可以自定义一个复制内存的函数。
string_dest= (char*)my_memcpy(string_dest,string_src, string_src_len);
printf("执行my_memcpy函数后,输出字符串string_dest=%s\n",string_dest); //输出字符串string_dest=fangmingfei
return 0;

}

//不管传入的dest和src是什么类型的指针,将它们重新定义成一字节的 Char 指针,这样就可以逐字节进行复制。
void* my_memcpy(void* dest, void* src, int byte_count) {
char* d=(char*)dest; //形参void* 类型  转为 char*
 char* s= (char*)src; //形参void* 类型 转为 char*
 //while(byte_count--){
     //*d++ = *s++语句相当于先执行*d = *s(源字节的值复制给目标字节),然后各自移动到下一个字节。
    // *d++=*s++;
 //}
  for(int i=0;i<byte_count;i++){
      *(d+i)=*(s+i);
  }
  return dest;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值