C语言学习(9)

一、二级指针(多级指针)

1. 定义: 类型名 **指针的名字

  比如: int **p; //定义了int类型的二级指针

  注意:你定义的二级指针,它的类型必须跟一级指针类型一致

  作用:用来指向另外一个一级指针在内存当中的地址(指向指针的指针)

  总结:二级指针在使用的时候无非就两种情况

  **q

  *q 二级指针解引用一次和两次分别代表什么??这个搞清楚代码就不会写错

#include <stdio.h>

int main()
{
    int a = 888;
    int *p = &a;  //定义一级指针p
    int **q = &p; //定义二级指针q,指向一级指针p的地址

    //通过二级指针去访问a
    //把二级指针解引用两次 **q--》**&p --》*p --》*&a--》a
    //把二级指针解引用一次 *q--》*&p--》p
    printf("通过二级指针q访问a: %d\n", **q); //q解引用两次即可
    printf("通过二级指针q访问a: %d\n", **&p);
    printf("二级指针q存放的是指针p的首地址:%p\n", &p);
    printf("二级指针q存放的是指针p的首地址:%p\n", q);
}

 

 

#include <stdio.h>
#include <string.h>
int main()
{
    int i;
    char buf[10] = "hello";
    char *p = buf;
    char **q = &p; // 定义二级指针q

    //通过二级指针访问修改buf中的内容
    //**q --》**&p--》*p --》*&buf[0] --》buf[0]
    //*q --》p --》buf--》&buf[0]
    printf("通过二级指针q访问buf完整的字符串 %s\n", *q);
    for (i = 0; i < strlen(buf); i++)
        printf("通过二级指针q访问buf中的单个字符 %c\n", *(*q + i));
}

 

练习:

  1. char buf[3][10]={"hello","hehe","haha"};

  char *p=buf[0];

  char **q=&p;

  //通过q访问二维数组中的三个字符串 %s

  //通过q访问二维数组中的单个字符%c

#include <stdio.h>
#include <string.h>
/*
    二级指针使用的套路:
        通过等价替换--》把二级指针解引用替换成你熟悉的一级指针
*/
int main()
{
    int i, j;
    char buf[3][10] = {"hello", "hehe", "haha"};
    /*    char *p=buf[0];
    char **q=&p;
     //访问二维数组中的字符
    for(i=0; i<30; i++)
        printf("二级指针q访问数组:%c\n",*(*q+i)); 
    
    //访问二维数组中的字符串--》找到字符串的起始地址即可
    for(i=0; i<30; i+=10)
        printf("三个字符串分别是:%s\n",*q+i); //等价于p+i */

    //扩展思考:
    char(*p)[10] = &buf[0]; //数组指针
    //二级指针的类型要跟一级指针类型一致
    char(**q)[10] = &p; //p的类型是数组指针
    //通过二级指针q去遍历访问二维数组的字符
    // *q相当于p
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 10; j++)
            printf("(*q)[%d][%d] is:%c\n",i,j, (*q)[i][j]);
    }

    //通过二级指针q去遍历字符串
    for (i = 0; i < 3; i++)
        printf("%s\n", *(*q + i));
}

二、C语言中的函数

1. 函数的概念和作用:

  数学当中:函数就是别人提供给你公式方法 sin() cos()

  C语言中:为了提高代码的复用性,把一些功能封装成函数供开发人员使用

  printf() --》函数 strcmp() strcpy()

2. 函数的定义

  函数的定义都是写在main函数的外面

  返回值类型 函数名(形参列表) //函数头

  {

    函数体;

  }

  返回值: 告诉函数的调用者,我这个函数执行完毕之后的结果

  函数名: 变量名规则一样,通俗易懂,不能跟C语言的关键字以及C语言的函数名字冲突

  形参列表:告诉程序员,在使用这个函数的使用需要传递多少个实参,每个实参分别是什么类型

  实参(实际参数):函数调用的使用,传递的参数就是实参

  形参(形式参数):函数定义的时候出现在圆括号里面的都是形参

  函数的调用

  语法格式: 函数名(实参列表) //实参的个数,类型必须跟形参保存一致

#include <stdio.h>
//把比较两个整数大小的代码--》封装成函数
//函数如何封装--》程序员说了算,封装的函数用起来很方便--》成功
//目的:为了提高代码的重复使用效率,任何其他代码将来都能使用

int numcmp(int n1, int n2) //形式参数
{
    if (n1 > n2)
        return 1;
    else if (n1 < n2)
        return -1;
    else
        return 0; //通过返回值判断大小
}

void othernumcmp(int n1, int n2) //没有返回值
{
    if (n1 > n2)
        printf("n1>n2");
    else if (n1 < n2)
        printf("n1<n2");
    else
        printf("n1==n2");
}
int main()
{
    int a;
    int b;
    printf("请输入两个整数!\n");
    scanf("%d%d", &a, &b);

    //调用函数
    //实参赋值给形参
    int ret = numcmp(a, b); //编译器翻译这句话 n1=a  n2=b
    if (ret > 0)
        printf("a>b\n");
    else if (ret < 0)
        printf("a<b\n");
    else
        printf("a==b\n");
}

 

3. 函数定义需要注意的问题

  第一个: 函数的定义要么写在main函数的前面,要么写在main函数的后面(注意提前声明)

  第二个:实参和形参的关系一定要搞清楚

#include <stdio.h>
//numcmp定义在main的后面
int numcmp(int a, int b); //声明了函数头,形式参数

//fun定义在main的前面
int fun(int c, int d) //故意迷惑你,把形参的名字取得跟实参一模一样
{
    c += 18;
    d += 10;
    return 0;
}
int main() //C语言代码都是从main函数开始往下执行
{
    int a;
    int b;
    int c = 6;
    int d = 12;
    printf("请输入两个整数!\n");
    scanf("%d%d", &a, &b);

    //调用函数fun
    fun(c, d);
    printf("c is:%d\n", c);
    printf("d is:%d\n", d);

    //调用函数numcmp
    //实参赋值给形参
    int ret = numcmp(a, b); //编译器翻译这句话 n1=a  n2=b
    if (ret > 0)
        printf("a>b\n");
    else if (ret < 0)
        printf("a<b\n");
    else
        printf("a==b\n");
}

int numcmp(int a, int b)
{
    if (a > b)
        return 1;
    else if (a < b)
        return -1;
    else
        return 0; //通过返回值判断大小
}

 

  C语言中函数调用传递参数只有两种写法: 传值和传地址

  区分传值和传地址: 函数的形参是普通变量 --》传值

  函数的形参是指针--》传地址

  传值:实参把自己的值拷贝一份给形参

  传地址:实参把自己的地址赋值给形参指针

#include <stdio.h>
//经典例子--》定义一个函数交换两个整数的值
void swapnum(int n1, int n2) //实参传值给形参
//实参a,b和形参n1,n2是四个独立的变量,a,b只是把自己的值拷贝它们
{
    printf("形参n1 n2地址是:%p\n", &n1);
    printf("形参n1 n2地址是:%p\n", &n2);
    int temp;
    temp = n1;
    n1 = n2;
    n2 = temp; //跟实参a,b没有任何关系
    printf("函数中n1  n2: %d  %d\n", n1, n2);
}

void otherswap(int *p1, int *p2) //实参传地址给形参
{
    printf("形参p1存放的地址:%p\n", p1);
    printf("形参p2存放的地址:%p\n", p2);
    int temp;
    temp = *p1;
    *p1 = *p2;
    *p2 = temp; //把p1和p2两个指针中的内容交换
}

int main()
{
    int a = 89;
    int b = 63;
    printf("实参a的地址:%p\n", &a);
    printf("实参b的地址:%p\n", &b);
    printf("交换之前: %d   %d \n", a, b);
    //调用函数swapnum
    swapnum(a, b); //编译器翻译:  n1=a  n2=b

    //调用otherswap
    // otherswap(&a,&b); //编译器翻译:p1=&a  p2=&b
    printf("交换之后: %d   %d \n", a, b);
}

 

  第三个:数组作为实参和形参

  数组作为实参: 传递的地址(指针)

  数组作为形参: 要么写成指针的形式,或者写成 数组+n(n表示元素个数)

  第四个:局部变量和全局变量--》作用域

  全局变量: 定义在main函数外面以及其他函数的外面

  全局变量在定义的.c文件中任何函数都能使用,并且其他.c文件也能使用

  全局变量是所有函数共享的,作用域属于整个文件

  局部变量: 定义在某个函数的花括号里面的,作用域仅限于函数内部

  作用域(作用区域):指的是这个变量的使用范围

  需要注意的问题:

  第一个: 全局变量跟局部变量同名,优先使用自己定义的局部变量(全局

  变量不起作用了)

#include <stdio.h>
int num = 100; //全局变量

int fun(int a) //形参a也是局部变量
{
    //由于fun函数中没有单独定义num,使用的是全局变量num
    num += 5; //使用的全局变量
    printf("num在fun函数中被修改了%d\n", num);
    printf("a is:%d\n", a);
}

int main()
{
    printf("main函数中使用num %d\n", num); //全局变量num
    int num = 888;                         //局部变量如果跟全局变量同名,会覆盖掉同名的全局变量
    fun(num);                              //num用的是哪个num
    printf("调用fun之后,main函数中使用num %d\n", num);
}

 

 

练习:

  封装一个函数,把用户从键盘输入的任意一个字符串中的非英文字符去除

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

/*
    函数名的不同风格:
        getnum()   
        get_num()  //ubuntu系统中采用这种风格
        getNum() //C++  JAVA  驼峰命名
*/
//封装函数实现去除字符串中的非英文字符
void rmchar(char *buf) //char *buf=buf实参传递地址给形参
{
    int i, j;
    for (i = 0; i < strlen(buf); i++)
    {
        if (buf[i] < 'a' || buf[i] > 'z')
        {
            for (j = i; j < strlen(buf); j++)
                buf[j] = buf[j + 1];
            i--;
        }
    }
}
//这种封装通用性比较差
//void otherchar(char otherbuf[20])  //要求你传递char [20]类型的数组
//通用性好一些,n表示数组元素个数
void otherchar(char otherbuf[], int n)
{
    int i, j;
    for (i = 0; i < strlen(otherbuf); i++)
    {
        if (otherbuf[i] < 'a' || otherbuf[i] > 'z')
        {
            for (j = i; j < strlen(otherbuf); j++)
                otherbuf[j] = otherbuf[j + 1];
            i--;
        }
    }
}

int main()
{
    char buf[16];
    scanf("%s", buf);
    //rmchar(buf); //编译器翻译:char *buf=buf
    otherchar(buf, 16);
    printf("去除非英文字符之后:%s\n", buf);
}

 

 

作业:

  第一题: 明确指出每个num是全局还是局部,值是多少,不要编译运行

#include <stdio.h>

int num = 100; //全局变量

int fun(int num) //任何函数的形参都是局部变量,跟全局变量同名

{

    num += 5; //fun自己的形参num 87

    printf("num在fun函数中被修改了%d\n", num); //fun自己的形参num 87

    return num; //fun自己的形参num 87
}

int main()

{

    //到目前为止main没有定义自己的num

    num -= 18; //全局变量num==82

    fun(num); //函数调用

    printf("main函数中使用num %d\n", num); // 全局变量num==82

    int num = 888; //main自己局部变量

        fun(num); //main自己的num

    printf("调用fun之后,main函数中使用num %d\n", num); //main自己的num
}

  第二题:请问程序执行结果是多少??

void foo(int b[][3]);

void main()
{
    int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
    foo(a);
    printf("%d",a[2][1]);
}
void foo(int b[][3])
{
    ++b;
    b[1][1]=9;
}

 

  难点:知识点 数组作为形参--》理解为指针,传地址

  指针的运算,写法 ++b很好理解 b[1][1]的理解

结果为:9
因为foo(a)//a为数组名,int(*)[3]的指针;
将首地址赋值给b,进入函数后,立马执行++;
此时b的指引的地址+1了,指向了a的第2位数组的首地址
即{4,5,6}这个数组
然后b[1][1]=9;则表示数组{7,8,9}中的8替换成了9;
回到主函数,打印的是a[2][1]即是被替换后的那个值。
最后结果为:9
View Code

 

  第三题: 封装一个函数 int fun(int n) 该函数可以把1到n之间(1<= 数字 <=n)所有能被6整除的数,或者数字中包含6的全部找到并打印出来

#include <stdio.h>
/*
*封装一个函数  int  fun(int  n)   
*该函数可以把1到n之间(1<= 数字 <=n)所有能被6整除的数,
*或者数字中包含6的全部找到并打印出来  
*/
int fun(int n) //能被6整除,或数字包含6的数打印出来;
{
    int i;
    for (i = 1; i <= n; i++)
    {
        if (i % 6 == 0 || i % 10 == 6 || i / 10 == 6 || i / 100 == 6 || i / 1000 == 6)
        {
            printf("%d  ", i);
        }
    }
}

int main()
{
    int input = 0;
    printf("Please enter a number\n");
    scanf("%d", &input);
    fun(input);
    return 0;
}
View Code

 

posted @ 2020-03-12 18:02  Geek_Jian  阅读(208)  评论(0编辑  收藏  举报