C语言自学笔记

指针是什么

指针就是地址。说得再全面一点,指针是一个变量,且这个变量是专门用来存放地址的。

指针的在函数中的两个基本用法:

用法一:

  • 当函数需返回多个值时,某些值则需要通过指针返回

  • 传入的参数实际上是需要保存带回的结果的变量

例如:找一列数中的最大和最小值并输出

#include <stdio.h>
void minmax(int a[],int len,int *min,int *max);
int main(void)
{
    int a[]={1,2,3,4,5,6,7,8,9,12,13,17,21,23,55};
    int min,max;
    minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
    printf("max=%d,min=%d",max,min);
    return 0;
}
void minmax(int a[],int len,int *min,int *max)
{
    int i;
    *min=*max=a[0];
    for(i=0;i<len;i++)
    {
        if(a[i]<*min)
            *min=a[i];
        if(a[i]>*max)
            *max=a[i];
    }
}

用法二:

  • 函数返回运算的状态,结果通过指针返回

  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错

例如:做两个整数的除法,若除法成功则函数返回1并输出,否则返回0

#include <stdio.h>
int divide(int a,int b,int *result);
int main()
{
    int a=5,b=2;
    int c;
    if(divide(a,b,&c))
        printf("%d/%d=%d",a,b,c);
    return 0;
}
int divide(int a,int b,int *result)
{
    int ret =1;
    if(b==0)
        ret=0;
    else
        *result=a/b;
    return ret;
}

指针与数组

  • 函数参数表中的数组实际上就是指针

  • 对其做sizeof(a)其实是做sizeof(int*)

  • 但是可以用数组的运算符[]进行运算

    例如以下函数原型为等价的:

    • int sum(int *ar,int n)

    • int sum(int*,int)

    • int sum(int ar[],int n)

    • int sum(int [],int)

数组变量是特殊的指针

  • 数组变量本身表达地址,所以数组a[]的地址即为a,无需用&取地址

  • 数组的单个单元表达的为变量,需要用&取地址,不过a[0]的地址就是a

  • []可对数组也可对指针做,若一个数组对他做p[0]即相当于*p

  • *运算符可对指针做也可对数组做,例如对数组a[]做 *a=1000,即a[0]=1000

  • 数组变量为常量指针,因此不能将一个数组的值赋给另一个数组,但可以将一个数组赋给一个指针

int b[]=a错误,但是int*p=a正确,这是因为int b[]就相当于int *const b

指针运算

加减法

加减整数

有以下程序:

#include <stdio.h>
int main()
{
    char ac[]={1,2,3,4,5,6};
    char *p=ac;
    printf("p=%p\n",p);
    printf("p+1=%p\n",p+1);
    int ai[]={1,2,3,4,5,6};
    int *q=ai;
    printf("q=%p\n",q);
    printf("q+1=%p\n",q+1);
    return 0;
}

由此可看出,对指针+1是加sizeof(char)或者sizeof(int),即将指针移到下一个数组的单元格

因此*(p+n)=a[n],但如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

指针相互加减

#include <stdio.h>
int main(void)
{
    char ac[]={1,2,3,4,5,6};
    char *p=ac;
    char *p1=&ac[5];
    printf("p1-p=%d\n",p1-p);
    int ai[]={1,2,3,4,5,6};
    int *q=ai;
    int *q1=&ai[5];
    printf("q1-q=%d\n",q1-q);
    return 0;
}

由此得,指针相互加减是指两指针的距离为多少;

*p++

取出p所指的那个数据,完事后把p移到下一个位置,常用于数组类的连续空间操作

有以下函数:

#include<stdio.h>
int main()
{
    char ac[]={1,2,3,4,5,6,7,8,-1};//此处-1作为跳出循环的条件
    char *p=ac;
    for(p=ac;*p!=-1;)
        printf("%d\n",*p++);
    return 0;
}
​

指针比较

  • <,<=,==,>,>,!=都可以对指针做

  • 是比较它们在内存中的地址

  • 数组中的单元的地址是线性递增的

指针的类型

例如:用p申请了一部分空间,但是在之后的代码中对p的值进行了更改,例如p++,则free(p)会导致错误,只能用p的初始值进行free

  • 无论指向什么类型,所有指针的大小都是相同的,因为都是地址

  • 指向不同类型的指针不能直接相互赋值

  • void指不知道什么类型的指针

  • 动态内存分配

    int * a=(int*)malloc(n *sizeof(int));

    调用malloc,要求分配n个int大小的内存,malloc就会分配相应大小内存,由于malloc返回的是void*,因此,需要在前面强制类型转换为int *。用完后还要释放空间free

    动态内存分配时需调用#include <stdlib.h>函数

    例如用动态内存分配实现一个大小可变的数组:

    #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {
        int number;
        int *a;
        int i;
        printf("输入数组大小:");
        scanf("%d",&number);
        a=(int*)malloc(number*sizeof(int));//之后就可以当做数组用了
        for(i=0;i<number;i++)
        {
            scanf("%d",a[i]);
        }
        free(a);
        return 0;
    }

    malloc申请的空间是以字节为单位的

    如果没空间了,即内存申请失败则malloc返回0,或者叫做NULL;

    因此有以下代码可查看可申请多大空间:

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        void *p;
        int cnt=0;
        while(p=malloc(100*1024*1024))//1024*1024为1M,每次申请100M空间,然后将申请到的空间交给p,同时p也作为一种条件
            cnt++;
        printf("申请到了%d00Mb空间",cnt);
        free(p);
        return 0;
    }

    关于free()

  • 申请来的空间需要还给系统

  • 只能还申请来的空间的首地址

结构类型

  • 在函数内部申明的结构类型只能在函数内部使用

  • 尽量在函数外部申明结构类型,这样可被多个函数使用

#include <stdio.h>
struct date
{
    int day;
    int month;
    int year;
};
int main()
{
    struct date today;
    today.day=23;
    today.month=5;
    today.year=2022;
    printf("Today's days is %d-%d-%d",today.year,today.month,today.day);
    return 0;
}

申明结构的形式

第一种:

struct point{
    int x;
    int y;
};
struct point p1,p2;

p1和p2都是point里面有x和y的值

第二种

struct{
    int x;
    int y;
}p1,p2;

这个结构无名,p1和p2都是这个无名结构,其中有x和y

第三种

struct point{
    int x;
    int y;
}p1,p2;

p1和p2都是point里面有x和y的值

结构运算

  • 强制类型转换:p1=(struct point){5,10}//相当于p1.x=5;p1.y=10;

  • p1=p2;//相当于p1.x=p2.x;p1.y=p2.y;

结构指针

  • 和数组不同,结构变量的名字并不是结构变量的地址,需使用&运算符

  • struct date *pdate=&today;

结构与函数

  • 整个结构可以作为函数的参数的值传入函数

  • 这时候是在函数内新建一个结构变量,并复制调用者的结构的值

  • 函数也可以返回一个结构

    以下函数为输出今天的第二天的年月日:

#include <stdio.h>
#include <stdbool.h>
int numberofdays(struct date d);
bool isleap(struct date d);
struct date{
    int month;
    int day;
    int year;
};
int main(void)
{
    struct date today,tomorrow;
    printf("This date is:");
    scanf("%d %d %d",&today.month,&today.day,&today.year);
    if(today.day!=numberofdays(today))
    {
        tomorrow.month=today.month;
        tomorrow.day=today.day+1;
        tomorrow.year=today.year;
    }
    else if(today.month==12)
    {
        tomorrow.month=1;
        tomorrow.day=1;
        tomorrow.year=today.year+1;
    }
    else
    {
        tomorrow.month=today.month+1;
        tomorrow.day=1;
        tomorrow.year=today.year;
    }
     printf("Tomorrow's date is %d-%d-%d",tomorrow.month,tomorrow.day,tomorrow.year);
    return 0;
}
int numberofdays(struct date d)
{
    int day;
    const int dayspermonth[12]={31,28,31,30,31,30,31,31,30,31,30,31};
    if(d.month==2&&isleap(d))
    {
        day=29;
    }
    else
        day=dayspermonth[d.month-1];
    return day;
}
bool isleap(struct date d)
{
    bool leap=false;
    if((d.year %4==0&&d.year %100!=0)||d.year%400==0)
        leap=true;
    return leap;
}

结构与指针

  • 用->表示指针所指的结构变量中的成员

struct date{
    int month;
    int day;
    int year;
}myday;
struct date *p=&myday;
p->month=12;\\这个与(*p).month=12意思相同

指针函数

int *fun(int x,int y);

这个函数就是一个指针函数,其返回值是一个 int 类型的指针,是一个地址。和普通函数对比不过就是其返回了一个指针(即地址值)而已。

以下有一段指针函数的代码:

#include <stdio.h>
struct point* getstruct(struct point *p);
void output(struct point p);
void print(const struct point *p);
struct point{
    int x;
    int y;
};
int main(void)
{
    struct point y={0,0};
    getstruct(&y);
    output(*getstruct(&y));
    print(getstruct(&y));
    getstruct(&y)->x=0;//将y的x值更改为0
    *getstruct(&y)=(struct point){1,2};//将结构y的x改为1,y改为2
    return 0;
}
struct point* getstruct(struct point *p)//传进一个指针,对指针所指的东西进行一定的处理后再将这个指针返回出去
{
    printf("%d %d",p->x,p->y);
    scanf("%d",&p->x);
    scanf("%d",&p->y);
    printf("%d %d",p->x,p->y);
    return p;
}
void output(struct point p)
{
    printf("%d %d",p.x,p.y);
}
void print(const struct point *p)//不用修改p的内容,因此有const,从而防止p被修改
{
    printf("%d %d",p->x,p->y);
}

结构数组

输出这一秒的下一秒的代码:

struct date dates[100];
struct date dates[]={
    {4,5,2005},{2,4,2005};
}

自定义数据类型

typedef:

typedef int length;

使得Length成为int类型的别名。这样Length 就可以代替Int出现在变量定义和参数声明的地方了:

Length a,b,len;

Length numbers[10];

typedef long int64_t;
typedef struct ADate{
    int month;
    int year;
    int day;
}Date;
int64_t i=100;
Date d={9,1,2005};

typedef *char[10] Strings;//Strings是10个字符指针的数组

程序结构

全局变量

  • 定义在函数外面的为全局变量

  • 全局变量具有全局的生存期和作用域

  • 它们与任何函数都无关

  • 任何函数内部都可以使用它们

例如:

#include <stdio.h>
int f(void);
int gall=12;
int main(void)
{
    printf("in %s gall =%d\n",__func__,gall);
    f();
    printf(" agn in %s gall =%d\n",__func__,gall);
}
int f(void)
{
     printf("in %s gall =%d\n",__func__,gall);
    gall+=2;
     printf(" agn in %s gall =%d\n",__func__,gall);
    return gall;
}

全局变量初始化

  • 没有做初始化的全局变量会得到0值,而指针会得到NULL

  • 只能用编译时刻已知的

若是全局变量,实行以下操作:
int gall;
int g2=gall;
这是不合法的。但若是:
const int gall=12;
int g2=gall;
则是合法操作,但是不建议此操作。
  • 若函数内部存在与全局变量同名的变量(本地变量),则该全局变量会被暂时覆盖(仅存在于函数内部),出函数后全局变量不会被改变

静态本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量

  • 当离开函数时,静态本地变量会继续存在并保持其值

  • 静态本地变量只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值

  • 静态本地变量具有全局生存期和函数内的局部作用域

  • 静态本地变量实际上是特殊的全局变量,它们位于相同的内存区域

#include <stdio.h>
int f(void);
int main(void)
{
    f();
    f();
    f();
    return 0;
}
int f(void)
{
    static int all=1;
     printf("in %s gall =%d\n",__func__,all);
    all+=2;
     printf(" agn in %s gall =%d\n",__func__,all);
    return all;
}

  • #define 用来定义一个宏

  • #define <名字><值>

  • 名字必须为一个单词,值可以为各种东西

  • 编译器替换是做的是完全的文本替换,define定义的名字后面有什么就把程序中相应名字替换为后面的值

  • 如果宏的值超过一行,最后一个行之前的行末需要加\

  • 宏也可以只有名字没有值,这类宏用来进行条件编译

带参数的宏:

#define cube(x) ((x)* (x)*(x)):

这与C语言的函数相似,不同的是这其中的x没有类型

错误用法:

#define RATG1(x) (x*57.298)

#define RATG2(x) (x)*57.298

这两个错误的原因都是括号的用法错误

因此:

  • 宏的参数出现的每个地方都要有括号

  • 整个值要括号

因此改为:#define RATG(x) ((x)*57.298)

宏可以带多个参数:

#define MIN(a,b) ((a)>(b)?(b):(a))

宏中也可以嵌套多个宏

多个源代码文件:

  • 一个.c文件是一个编译单元

  • 把函数原型声明封装到一个头文件(就是以.h结尾的文件)中,在需要调用这个函数的源代码文件(.c文件)中#include“ ”这个头文件,就能让编译器知道这个函数原型

  • #include“ ”把那个文件的全部文本内容原封不动地插入到它所在的地方

  • 在使用和定义这个函数的地方都应该#include“ ”这个头文件(.h)

  • 在函数前面加上static就使得它成为只能在所在编译单元中被使用的函数

  • 在全局变量前面加上static就使得它成为只能在所在编译单元中被使用的全局变量

  • 在头文件中,对全局变量声明为:extern int gAll;从而可以在其他文件中直接使用gAll

标准的头文件结构:

#ifndef _LIST_HEAD_
#define _LIST_HEAD_ //头两行,注意:该头文件文件名要在此处为大写,比如头文件叫max.h,则在代码中为_MAX_H_
​
​
#endif//在最后一行
前两行和最后一行是这个头文件的标准结构,用来判断这个头文件是否被调用过,如果被调用,则不在重复调用,从而防止对其中一些内容进行重复调用

函数指针:

  • 一个函数的函数名就是一个地址

  • 例如一个函数为void f(int),则指针为void (*pf)(int)=f,pf就是这个函数的指针,类型为void()

函数指针的使用:

  • 调用函数也可以:(*pf)(10),这与f(10)相同,意为调用f函数并传入参数10

第一种用法:

#include <stdio.h>
void f(int i)
{
    printf("in f(),%d",i);
}
void g(int i)
{
    printf("in h(),%d",i);
}
void h(int i)
{
    printf("in h(),%d",i);
}
int main(){
    int i=0;
    void (*fa[])(int)={f,g,h};//建立一个函数指针的数组fa,fa[0]指向为f的地址,依次类推
    scanf("%d",&i);
    if(i>=0&&i<sizeof(fa)/sizeof(fa[0])){
        (*fa[i])(0);
    }
       return 0;
}

第二种用法:

#include <stdio.h>
int plus(int a,int b){
    return a+b;
}
int minus(int a, int b){
    return a-b;
}
 void cal(int (*f)(int,int))//参数为一个函数指针
{
    print("%d",(*f)(2,3));
}
int main(void){
    cal(plus);
    cal(minus);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值