C总结-part_3-函数&指针

C总结-part_3-函数&指针

// 1、声明,此时定义了一个函数变量,但是没有给其分配内存空间
extern int add(int a,int b);

// 2、函数定义,如果在头部有声明,则放在main前后都可以
// 此时,开辟了内存空间存放函数内容
int add(int a ,int b)

// 3、调用,函数应当先声明、定义,之后才能调用。
extern int dive(int a,int b);
int dive(int a ,int b)
{
    if(a)
    {
        exit(0);
    }
    return a/b;
}
// Tips:如果不写声明,定义就要写在main的上面,
// 如果有声明,就要将声明卸载main上面;

———————————————————————————————————————————————

多文件联合编程

  • 主文件 .c

在主文件中需要写#include "头文件.h",
在主文件中可以调用子文件中的东西;

  • 子文件 .c

在子文件中写各个函数的定义,定义的变量或函数在全局都可使用,因此命名不可以重复
同时也需要#include "头文件.h"

  • 头文件 .h

头文件中写 #include <stdio.h>

头文件的作用:

  • 函数、变量的声明
  • 系统库的调用
gcc -o .exe a.c b.c head.h
// 注意,头文件只能include一次,重复的include会导致出错
// 此时可以使用 #pragma once

#pragma once // 意味着仅进行一次include

 // 注意__HEAD_FILE_H__命名也不可以重复;
 // 如果没有头文件,则定义
#ifndef __HEAD_FILE_H__    
#define __HEAD_FILE_H__

// 在中间写自己引入的头文件

#endif
#include <stdio.h>
int main (int argc, char *argv[])
{
    // argc 代表传递参数的个数
    // argv 传递参数的内容,里面放有所有传入的参数
    if(argc)
    {
        printf("缺少参数
");
        return -1;
    }
    char arr[1000];
    char temp[256];

    strcpy(arr,"gcc -o");
    strcpy(temp,argv[1]);
    char *p = strtok(temp,".");
    
    strcat(arr,p);
    strcat(arr," ");
    strcat(arr,argv[1]);

    // Tips:使用命令mygcc a.c,就可以执行gcc -o a.c a
}
// 实现调用命令执行gcc
// a.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argvp[])
{
    if(argc <= 1)
    {
        printf("缺少参数
");
        return -1;
    }
    char arr[1000];
    char temp[256];
    
    strcpy(arr,"gcc -o");
    strcpy(temp, argv[1]);
    char *p = strtok(temp, ".");
    strcat(arr, p);
    strcat(arr, " ");

    strcat(arr, argv[1]);

    system(arr);    // 调用命令,命令就是arr中的内容;

}
// 编译当前程序 gcc -o a a.c
// 然后就可以用本程序来编译其他的c程序了
// eg: ./a b.c
// 即可编译 b.c 为可执行文件 b

———————————————————————————————————————————————

7_指针

int my_strcmp(char s1[],char s2[])
{
    int i = 0;
    while(s1[i] !="" && s2[i] != "")
    {
        if(s1[i] != s2[i])
        {
            return s1[i] - s2[i];
        }
        i++;
    }
    if(s1[i] !="" || s2[i] != "")
    {
        return s1[i] - s2[i];
    }
    return 0;
}

指针,指向内存地址

注意,内存地址都是无符号整形

int a = 10;
int *p = &a; // p代表了一个int变量的地址

printf("%p
",&a);
printf("%d
",&p); // 这两种打印等价

// *p 指向a的值
// p  指向a的地址
// &a 指向a的地址
// 以下三种定义方式是等价的
int * p
int* p
int *p

sizeof(int *)
32位OS,int * 占4B,
64位OS,int * 占8B。
int a = 10;
int *p = &a; // 代表a的地址
*p = 20;     // 此时,a的值会被改为20

———————————————————————————————————————————————

野指针(野指针在编程当中应该避免,野指针容易使程序出现重大问题)

外挂就是利用的野指针

int a = 10;
int *p = &a;
p = 100;    // 擅自给指针p赋了一个值,此时,p变成了一个野指针,指向了未知的地址
*p = 200;   // 此时,操作了野指针,更改了野指针指向的未知的地址的值,程序会出错

// Tips:地址0-255都是系统保留不可读也不可写的区域;

———————————————————————————————————————————————

空指针(空指针是允许的,可以用来进行条件判断)

int *p;     // 定义了一个指针p,没有指向任何地址,这是一个空指针;
p = NULL;
*p = 100;   // 给NULL的地址中赋值,不会起效果;

// Tips:空指针可以用来判断空间是否有

———————————————————————————————————————————————

万能指针

void *p = &a;   // 用void没有指定指针的类型,此时可以指向任何类型
*(int *)p = 100;    // 先转为int *,再对*赋值

// Tips:void指针,要用的时候都必须要转换类型才能用;
int a = 10;
void *p = &a;
printf("%d
",*p); // 报错,void类型直接用printf是无法输出的
printf("%d
",*(int *)p); // 正确
int arr[10] = {0};
void *p = arr;
*(int *)p = 100;    // 给arr设置值100;

// p作为int指针,+4就会向前移动4个int大小的长度,所以这里会把arr[4]设置为200
*((int *)p + 4) = 200;  

———————————————————————————————————————————————

const 修饰符

有四个例子,来说明const定义的变量是否可以更改

const int a = 10;
int *p = &a;
*p = 100; // 在这里用指针修改了const定义的a的值

// 本例中,p可以更改,而*p不可更改;
int a = 10;
const int *p; // 用const修饰指针,指针变量一旦赋值就不可更改;
p = &a;     // 从而使得指针指向的值变得不可更改,但是指针的地址还是可以更改的;


// 在这里,*p可以更改,p不可以更改
int * const p = &a;
*p = 100;   
// 此时完全锁定,p不可以更改,*p也不可以更改
const int * const p = &a;

———————————————————————————————————————————————

指针和数组

int arr[10] = {0};
// arr → 是一个常量,里面存的是指针
// &arr[0] → 是一个值,arr[0]的值

int *p = arr;
// p[0]、p[1]等价于arr[0]、arr[1]
// *(p+0),*(p+1),*(p+2)
// 这里的+1 +2 乘以int的大小,就是新指向的地址,p的值始终未变
int *p = arr; 
p++; // 这里会把p的值更改,p++会向前移动一个int大小的地址


// Tips:数组下标越界,分为上越界,下越界

———————————————————————————————————————————————

指针运算

注意,指针的加法并非简单的数据加法

int * p = &a;
p= p + 2;   // 注意,此时,p移动了2个int大小的地址
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
p = &arr[9];
p-arr;  // 返回9,即9个int大小的地址

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int * p;

    // arr[9]为最大的元素,而arr[10]数组会越界,但是不会报错
    for(p = arr;p < &arr[10];p++)
    {   
        // p[0] 可以输出,p[0]代表了p向前走0个int的大小;
        printf("%d
",p[0]);
    }
}

// 数组名和指针的区别
sizeof(arr) → return 40 → 是整个数组的大小
sizeof(p) → return 4 → 求出的是int的大小
// 例、指针实现冒泡
int bubble(int *p,int len)
// 技巧:*(p + i) 就指向了目标的数据arr[i],二者等价
// 例、实现strchr
char arr1[] = "hello world";
char ch = "";
char *p = strchr(arr1 , ch);

// p打印出值为 0 world
// Tips:strchr,在字符串中查找字符首次出现的位置,并返回该位置的地址;
char *mystrchr(char * arr1,char ch)
{
    int i =0;
    while(arr1[i] != "")
    {
        if(arr1[i] == ch)
        {
            return &arr[i];
        }
        i++;
    }
    return NULL;
}
// 例、字符串的逆置 → 数组逆置,用指针来完成

———————————————————————————————————————————————

指针数组

把数组元素拿来存指针,就变成了指针数组,其元素全都是地址;

int a = 10,b = 20, c = 30;
int *arr[] = {&a,&b,&c};
*arr[0] = 100;
char * arr[] = {"hello","world","abc"};
*arr[0] → 会打印一个h
arr[0]、arr[1] 是"hello" 、"world"两个字符串的首地址;
*arr[0] + 1     找到的是world
*(arr[0] + 1)   找到的是e
strlen(arr[1])  计算的是world的长度
// 例、字符串数组,按字母排序
char *arr[] // 存的都是地址

用一个指针指向一个一维数组,指针+1的步进就是数组元素所占空间的大小。
用一个指针指向一个二维数组,情况就不一样了。 ———————————————————————————————————————————————

多级指针

int a = 10;
int *p = &a;    // 一级指针p
int **pp = &p;  // 二级指针pc

*pp = &a
**pp = 20;  // 等价于*(*p)  先寻址到p的值,p的值是一个地址,再访问这个地址,设置为20

int *** ppp = &pp;  // 三级指针

Tips:关系式
***ppp == **pp == *00 == p == &a

char的取值范围是0-256
如果函数定义为void tab(){},在tab内return 100,不会有什么意义;也就是定义void返回值,而返回的不是void;

int tab(int * a ,int b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
int main()
{
    int a = 100;
    int b = 20;
    tab(&a, &b); // 向tab函数内,传入a、b的地址
    return 0;
}
// Tips:函数参数中如果有数组,则会转为指针;
sizeof(arr) → 40
sizeof(p)   → 4 → 数组传入函数中,其实是地址传入,函数内操作的是指针,长度是4

Tips:int型的数组是这样的规律;但是char型的数组不是这个的规律;

void print(char *arr)
{
    int len = strlen(arr);
    int i = 0;
    while(arr[i] !="")
        i++;
}
int main(void)
{
    char arr[] = "hello world";
    char arr2[] = {"h","e","l","l","o"};
    print(arr);
}
/*
1、当数组传入到函数中后,数组变量会退化为指针,
2、在传递数组到函数中时,需要将数组元素个数传递进去,否则你无法知道数组有多长
*/

/*
函数调用,如果是在函数内部定义的变量,return到函数外部,地址可以访问到,
但是其值并不可用,
因为函数调用结束后,在函数内定义的变量的空间会被回收,
这就return出了一个野指针;
*/
char *test()
{
    char arr[] = "hello world";
    return arr; // 这个arr必须保证其里面有内容
}
int main()
{
    char *p = test();
    printf("%p     %s
",p,p); // 地址%p可以打印值,会乱码,因为arr已经被回收了
}
// 但是,下面这种情况特例
char *test2()
{   
    // 定义了一个字符串指针,这个指针指向的hello world字符串,是一个字符串常量
    char *arr = "hello world"; 

    // 这里return出去的arr,字符串常量不会被回收,在外部函数中依然能访问到hello world
    return arr;
}

/*
以下变量在函数执行完之后不会被回收
1、全局变量
2、函数内的字符串常量
3、static修饰的变量
*/
/*
例、strstr的实现 → 比较两个字符串是否一致
可以借助指针来实现,
1、硬解算法
2、KMP算法
*/

———————————————————————————————————————————————

指针和字符串

char arr[] = "hello world";
char *p;
p = arr;
*p = "A";   // 相当于arr[0] = "A"
p++;        // p是arr[0],p++之后就指向了arr[1]
*p = "B";   // 相当于arr[1] = "B"
printf("%s   %s",p,arr);    // p会打印Bllo world,arr会打印ABllo world

// Tips:在printf时,字符串会从当前地址打印直到
// 这是一个字符串常量,可读不可写,存放在内存的常量区
char *arr = "hello world";

// 这是一个字符串数组,可读可写,存放在内存的栈区
char arr[] = "hello world";
// 例、strcat函数 → 将一个字符串追加到另一个字符串数组中
void mystrcat(char *arr,char *s1)
{
    while(*arr)
        arr++;
    while(*arr++ = *s1++);
}   

// 例、字符串排序

———————————————————————————————————————————————