C语言学习笔记(二)

1.指针是一种数据类型,装地址的数据类型。注意指针类型(对偏移,内存操作理解很重要),什么样的类型决定了操作空间的大小。地址加减,就是加减一个类型的大小。*代表间接访问运算符。

#include<stdio.h>
main()
{
    //int a = 12;
    //int *p = &a;//理解:首先声明一个指针类型的变量p、再取a的地址赋值给p。这里的*,起标记作用,标记p是一个指针变量
    //p = &a;
    //printf("% p %p %p %d\n", p, &a,&p,*p);//&p取p的地址,与前两个不同。p装的是a的地址.这里*是间接访问运算符或内存操作运算符
    //a = 14;
    //printf("%d\n", *p);//14 改变指向变量的值,也改变了指针的值
    //*p = 145;
    //printf("%d", a);//145 改变指针的值,也改变了指向变量的值
    
    int a = 12;
    int *p = &a;//切记:p是一块空间,有他自己的地址
    int *p3 = p;
    printf("%d\n", *p3);//输出12 ,p代表a的空间地址,*p3获取的就是a的值
    //证明不同
    //int *p1 = &p;//   报错:“p1” : “int *”与“int **”的间接寻址级别不同
    
    int **p1 = &p;//二级指针。、,装一级指针地址
    *p1 == p;//此时两者的关系 true
    *p == a;
    
    getchar();
    return 0;
}

2.指针与数组二级指针与数组无关,涉及到数组与指针,利用好数组就是地址+[偏移量]的思维。指针数组(地址数组)(例:int p[5]):用数组装入地址的数组指针(例:int (p)[5]):数组类型的地址**

#include<stdio.h>
int main(void)
{
    int a[5] = { 9,6,4,5,4 };
    int *p = &a[0];
    printf("%p %p %p %p %p\n", p,p+1,p+2,p+3,p+4);//运算完p没有改变,用多条语句++p,也可以实现改变地址,但是会引起p的改变
    printf(" %p %p\n", &*(p+2), &p[2]);
    int i ;
    for ( i = 0; i < 5; i++)
    {
        printf("%p\n", *(p + i));
        //printf("%p\n", *p++);//注释上一句结果同上9 6 4 5 4,此语句p++改变了p的指向
        printf("%d ,%d\n",a[i],p[i]);//指针的下标运算(指定首地址,且为连续的空间),两者结果相同 (地址+[偏移量])。
    }

    printf("%p %p %p %p %p %p\n", a, &a[0], &a[1], &a[2], &a[3], &a[4]);//a也是首地址,上下两者输出一样。

    //**指针数组
    int a1 = 1, b1 = 2, c1 = 3;
    int *arr[3] = { &a1,&b1,&c1 };
    printf("%d %p\n", *arr[1], arr[1]);//分别用来修改值,修改地址
    //指针数组的拉链结构
    int b11[3] = { 1,2,3 };
    int c11[2] = { 2,5 };
    int d11[4] = { 1,3,2,44 };
    int e11[5] = { 45,6,5,4,26 };
    int f11[2] = { 4,9 };
    int *a11[5] = { b11,c11,d11,e11,f11 };
    printf("a11:%d\n",a11[2][2]);//二维数组的方式访问,但是二维数组地址是连续的,一般拉链结构地址不连续,相互分开不同。大小不同但类型要同
    
    //**数组指针
    int a2[5] = { 3,4,2,6 ,8};
    int*p2 = &a2[4];
    //int *p22 = &a;//类型不兼容报错,根据报错找类型  warning C4047: “初始化”:“int *”与“int (*)[5]”的间接级别不同
    //下标不一样,类型是不同的。int [6] int [5]不同类型的,可用上句测试
    int(*p22)[5] = &a2;
    //数组指针使用
    p22[2];//为a2数组的地址,再偏移2(2个a2数组的大小,2*5*4)
    printf("第二个:%d\n", (*p22)[2]);//输出2,取值设置等操作
    printf("%p %p\n", &(*p22)[2],&a2[2]);
    getchar();
    return 0;
}

3.二维数组与指针。声明的指针指向哪个地址,操作的就是对应层次的地址。一个指针指向一个变量,p对应他本身(若p=&a[0][0]即*p==a[0][0])。指针的大小取决于编译环境。32位编译环境下的全为4字节,64位位8字节(vs编译器可以切换)。

#include<stdio.h>
int main(void)
{
    //int a = 12;
    //int *p = a;//警告:“int *”与“int”的间接级别不同
    //printf("%p %d\n", p, *p);//输不出值
    int a[2][3] = { {2,5,3} ,{3,5,3} };
    int *p = &a[0][0];//==a[0]
    for (int i = 0; i <= 5; i++)
    {
        printf("%d\n",*(p+i));//可以全部输出,因为存的空间是连续的
    }

    int (*p1)[3] = &a[1];
    int(*p2)[2][3] = &a;//对比a[2][3]发现数组指针与数组一种形式上的关系。但是每一种声明的方式,指向层次都是相同的。可以和上一句对比
    int i, j;
    for ( i = 0; i < 2; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf("%d\n", (*p2)[i][j]);

        }

    }
    
    getchar();
    return 0;
}

4.堆区和栈区 内存分为:栈区(由系统申请),堆区(由自己申请释放),全局区,字符常量区,代码区。malloc申请一段连续的指定大小的空间,并返回首地址。

#include<stdio.h>
#include<stdlib.h>//两个都是malloc/calloc的库文件
#include<malloc.h>
#include<memory.h>
int main(void)

{
    //void * maloc(size_t size)
    int *p = (int*)malloc(4);//参数为字节数(除4得一,代表只有一个数),将空间标记为int*类型。4默认转为无符号的了,或者写成4u
    p = (int*)malloc(4);//再申请的话会造成空间丢失,内存泄漏。但还是被占用的
     //地址的类型决定了,地址每次访问的大小。size_t==unsigned int
    //申请不到失败,返回NULL
    *p = 2;//赋值
    //空间赋值,字节设置值
    memset(p, 0, 4);//memcpy才是数组赋值。参数1为赋值变量,参数二所赋的值,参数三字节数


    if (NULL==p)
    {
        printf("申请失败!!");
    }
    int a = sizeof(size_t);//32位编译器环境是4字节,64位是8字节
    printf("%p\n",p);

    free(p);//释放空间,把空间还给操作系统
    printf("%p\n", p);//释放后p就是野指针了(访问受限)。还有就是定义指针为初始化也是野指针

    //malloc与数组
    int *p1=(int*)malloc(sizeof(int) * 5);
    int a1[5];
    //int *p2 = &a1[0];
    //p1 = p2;
    /*for (int i = 0; i <= 5; i++)
    {
        printf("%d\n",*(p+i));

    }*/
   //free(p2);

    //数组类型
    int (*p2)[5]=(int(*)[5])malloc(sizeof(int) * 5);
    (*p2)[5] = &a1;
    printf("比较:%d\n",*p2==a1);//0
    printf("比较1:%d\n", *p2 == a1);//1
    for (int i = 0; i < 5; i++)
    {
        printf("%d\n", *( p2)[i]);

    }
    free(p2);
    int(*p3)[2][5] = (int(*)[2][5])malloc(sizeof(int) * 5);

    //calloc
    int*p4=(int*)calloc(5, 4);//5个元素,每个4字节;申请数组比较方便
    for (int i = 0; i < 5; i++)
    {
        printf("%d\n",p4[i]);//区别之一,全部初始化为0

    }
    free(p4);
    //realloc重新修改字节
    printf("%d\n", _msize(p));//_msize()查看malloc出的空间字节大小
    int *p5 = (int*)realloc(p, 20);//重新分配20字节
    getchar();
    return 0;
}

5.函数基本使用

#include<stdio.h>
#include<malloc.h>
void fun1();//函数声明:先声明,函数体可以放最后
int fun2();
int fun4(int a, float b);
int fun5(int *p);
void fun6(int **p);
void fun(void)//无返回值,无参数。 整体放在最后面调用报错。使用函数声明来解决
{
    printf("这是一个函数!");
    return;//终止函数
}
//返回多个值
int * fun3(void)
{
    int *p = (int*)malloc(8);
    *p = 4;
    p[1] = 5;
    return p;//返回指针

    //int p1[2] = { 4,5 };//虽然表面输出没问题,这是c自身的bug。会有警告
    //return p1;//返回栈区,这里p1为栈区局部变量,运行完函数后,空间就被释放了,使用的是非法空间
}

int main(void)
{
//一般函数的执行略慢一丢丢于未封装的代码。因为涉及到跳转
    fun();//调用:函数地址+参数列表
    printf("%p %d %d\n", fun,&fun,fun==&fun);
    (&fun)();//结果同上,不要忘记前面的小括号。
    void(*p)(void) = fun;//函数指针
    fun1();
    printf("返回值:%d\n",fun2());

    //返回多个值函数测试
    int*a = fun3();
    printf("%d %d\n",a[0],a[1]);
    free(a);//记得释放堆空间

    //通过函数修改外部值,思路修改地址,把地址传进函数
    int b = 2;
    int *p1 = &b;//p1代表(装载的)b的地址,p1自己还有一个自己空间的地址
    fun5(p1);

    //通过函数修改指针变量
    fun6(&p1);

    getchar();
    return 0;   
}

void fun1(void)//函数定义 
{
    printf("这是一个fun1!");
}
int fun2(void) 
{
    printf("无参有返回值函数!");
    return 4;
}
int fun4(int a,float b)
{
    printf("两参数 %d %d\n",a,b);
    return 4;
}
int fun5(int *p) {
    *p = 24;
    printf("xiugao的%d\n",*p);
}
void fun6(int **p) {
    *p = NULL;
}

5.较为特殊的函数和用法(数组参数,递归函数),递归注意递归控制变量

#include<stdio.h>
#include<stdarg.h>
//一维数组
void fun(int *p,int length)//int *p改为 int p[]([]可以写任何整数字),数组做形参会被解析成指针
{
    for (int i = 0; i < length; i++)
    {
        printf("%d\n",p[i]);
    }
}
//二维数组
void fun1(int (*p)[3],int hang,int lie)//同一维的改写,参数int p[任意][3]
{
    for (int i = 0; i < hang; i++)
    {
        for (int j = 0; i < lie; i++)
        {
            printf("%d\n",p[i][j]);
        }
    }
}
//函数类型
int fun2() {}
int fun3(int a) {}
void fun4(int a) {};

//递归函数
int fun5(int n)
{
    if (n==1 || n==2 || n==0)
    {
        return 1;

    }
    else {
        return fun5(n - 1) + fun5(n - 2);
    }


}
//指定未知参数个数a
void fun5(int a,...)
{
    va_list ap;//定义参数数组
    va_start(ap,a);//将参数装进数组
    printf("%d\n", va_arg(ap, int));//将数取出来,就没了。再取就是 顺序往下取第二个
    printf("%lf\n", va_arg(ap, double));
    printf("%d\n", va_arg(ap, int));
}
int main()
{
    int a[5] = { 1,2,3,4,5 };
    fun(a, 5);

    int a1[2][3] = { {1,2,3},{4,5,6} };
    int(*p)[3] = a1;
    fun(a1,2,3);

    //函数地址 地址类型:有返回值类型,参数类型个数决定
    //int ();对应的类型去掉名字,保留参数
    //int (int a);
    int(*p1)(int a) = fun3;//int a对应fun3的参数列表
    //int (*p1)(int a) = fun4;//也是可以的

    //递归斐波拉契数列,通项公式型
    printf("递归:%d\n", fun5(6));
    //多参数

    fun5(3, 12, 34.34 , 78);//3是指参数个数

    getchar();
    return 0;
}

6.字符

#include<stdio.h>
#include<stdlib.h>
#include<string.h> 
int main(void)
{
    printf("%c\n",'A');//要用'',不然编译器会吧字母当做变量
    printf("%c\n", 64);//输出字符@
    printf("%d\n", 'A');//65
    putchar('a');//输出
    putchar('\n');
    char a = 66;//1字节
    printf("%c\n", a);//B
    //scanf我们在控制台的输入,系统会开辟一块空间(输入缓冲区)存储我们的所有输入,按下回车结束
    int d, f;
    scanf("%d", &d);//从缓冲区拿数据,输入时2 3连续输,中间带空格可正常输出。回车后,这时scanf就去缓冲区取相应类型的数据
    scanf("%d", &f);//去缓冲区读取数据
    printf("%d %d\n", d, f);
    //清空缓冲区 setbuf(stdin,NULL)(c标准)和fflush(stdin)(不标准,有些编译器不支持)还有while((c=getchar())!='\n'&&c!=EOF);
    setbuf(stdin,NULL);
    char e, g;
    scanf("%c", &e);//字符类型的输入,回车和空格对scanf的读取有影响,输入一个字母回车。此时相当于存储了字母1和\n(回车)
    scanf("%c", &g);//一般直接输入两个相连例如ab
    printf("%c %c\n", e, g);//d存储为输入的字母,f存储的是\n
    
    //scanf_s读取格式
    scanf_s("%c \n", &e, 1);//1表示字节数,多个输入加多个指定的字节数
    //char h =_getch();//随输入随读取,不需要回车确认。头文件conio.h
    
    char c1 = 0;//转换大小写
    while (1)
    {
        scanf("%c", &c1);
        if(c1>=65&&c1<=90)
        {
            printf("%c",c1+32);
        }
        else if (c1 >= 97 && c1 <= 122)
        {
            printf("%c", c1 - 32);
        }
        else if (c1 == '\n')
            break;
    }
    //字符数组
    char arr[5] = { 'r','g','g','r','r' };
    
    getchar();
    return 0;
}

7.字符串(以\0 (0)结尾的字符数组)

#include<stdio.h>
#include<string.h>
int main(void)
{
    char str[3] = { 'a','b','e' };//这是纯字符数组
    char str1[3] = { 'a','b','\0' };//这就是字符串了
    printf("%s\n", str);//输出乱码,因为结束不了
    printf("%s\n", str1);//输出ab(遇到\0(0)结束,\0转义为0),不输出\0。用%c可以输出\0
    
    printf("%s\n", "dfaf 522!!");//常量字符串,自带\0(dfaf 522!!\0)
    char *p = "aaa555 55";//双引号返回字符串的首地址
    char *pp = 'a';//
    printf("指针输出字符%c\n", *p);
    printf("指针输出字符地址%p \n", p);
    //*p = 'w';//崩了,内存地址指向非法
    //p [0]= 'w';//报错:写入位置 0x00966B44 时发生访问冲突。p指向的是一个常量区字符串地址,无法再进行修改。
    //p = "wfwfgwg";//ok
    //printf("指针输出字符串%s\n", *p);//直接炸掉
    printf("指针输出字符串%s\n", p);
    printf("查看*p %c\n", *p);//输出w
    printf("我也可以输出!!");//定义中第一个参数为char* 而""刚好返回地址

    char str11[15] = "1546 eege";//常量字符串不能修改。字符串在栈区数组和常量区都有
    str11[1] = 'w';//改变的是栈区的
    char str2[] = "vg1546 eege";//不用考虑越界
    //str1[10]="wdqwfq";错误的不能一次转入。通过循环可以
    char *p1 = "de";
    int i = 0;
    for (; *p!='\0'; p++)
    {
        str2[i] = *p;
        i++;
    }
    str2[i] = '\0';//给结尾加个\0,不然就是替换前两个而已。
    strcpy(str2, str11);//将str11赋值给str2(str2空间要足够大)
    printf("%s\n", strcpy(str2, str11));
    //strcpy()//类比于scanf_s(str,10,str1)多了个指定(参数1的)字节数
    
    strncpy(str2, "fwfwgwg", 3);//3表示自从字符串中取三字节到str2中
    strncpy_s(str2,10, "fwfwgwg", 3);//多了个指定参数1的字节数

    char str22[20];
    scanf("%s", str22);//输入字符串后面默认加了\0,输入的空格相当于一个分割符,不读空格。
    //scanf("%s", str22,19);//保留一个字节给系统输入\0
    //gets(str22);//读入全部,包括空格
    //gets_s(str22,19);//总结_s系列的函数,主要优化了越界处理和报错,方便查找。在本句就报错,不再程序执行完在报错
    printf("%s",str22);
    
    char str3[3] = "谭";//字符串装入汉字两个字节,另外后面有个\0;或者百度汉字国标码
    //求字符串长度
    size_t a = strlen("156466");//只读取字符数,不读取\0. size_t无符号整形重命名
    //字符串比较,切记与字符串长短无关,按顺序比较asc2码大小
    int result = strcmp("abcdff", "abef");//前面的字符串大于后面的返回>0(两字符的差值),小于的返回小于0((两字符的差值),带符号),相等返回0;
    //字符串拼接
    strcat(str3, "f");
    strcat(str3, "ferbe",1);//选一字节拼到str3
    //将数字字符串转为整数
    int aa = atoi("255");//头文件stdlib.h
    int aa1 = atoi("gwg255");//返回0
    int aa2 = atoi("255gree");//返回255
    //整数转字符串
    char stra[20] = { 0 };
    itoa(235, stra, 2);//将235以二进制存于stra
    //sprintf将后面的值全转为字符串存到stra里
    sprintf(stra, "%d,%c,%f", 12, 'v', 12.3f);//不会输出到控制台,要用printf
    //测试一些转义字符的字节数
    printf("%u\n", strlen("\n"));
    //  \123 \0123 数字都为八进制的、\xf45为16进制的

    //字符串数组
    char *sta1[3] = {"fwef","regfwef","tjyy"};

    getchar();
    return;
}

8.结构体及数据存储。32位和4位cpu一次处理的数据分别为4字节,8字节。数据存储规则:字节对齐/内存对齐。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//手动设置字节对齐,这里表示每次存四字节
#pragma pack(4)

//在全局区定义则全部区域可用
struct Stu
{
    void(*p)(void);//结构体不能定义函数,若要传入函数,就用指针
    char name[10];
    int age;

}stu1,stu2;//声明变量方式1

struct 
{
    struct Stu stuu;//结构体嵌套
    char name[10];
    int age;

}stu3={ "蔡徐坤",21 };//没名字的要在下面声明办理,不然其他地方难声明。这里初始化是全局的

void fun(void){}

struct Stu1
{
    char name[10];
    int age;
};

int main(void)
{
    struct Stu student = {"蔡徐坤",21};//声明变量,初始化(按顺序)。外面的变量也可这样初始化,初始化就是全局的,这里的是局部的
    //初始化指定元素struct Stu student = {.age=23 };
    struct Stu *p = &student;
    p->age;//结构体指针调用方式
    (&student)->name;//用点会报错
    strcpy((*p).name,"ysl");//设定定义字符串的值,不能直接用p->name="ysl"
    strcpy(p->name, "ysl2");
    student = (struct Stu) { "蔡徐坤2", 21 };//复合文字结构

    struct Stu student1 = {fun};//传入函数的地址
    //结构体数组
    struct Stu student3[3] = { { "蔡徐坤3", 21 },{ "蔡徐坤4", 21 },{ "蔡徐5", 21 } };
    //结构体大小也用sizeof(),计算规则依据于内存对齐
    struct Stu1 student11 = { "蔡徐坤啊",21 };//
    printf("%u %u\n",sizeof(struct Stu1), sizeof(student11));
    //(顺序存储,读取顺序)计算规则:1.以最大类型为字节对齐宽度2.一次填补各个成员字节3.结尾补齐。另外数组的话,就看初始定义的大小char a[10],再根据基本类型中最大的(不是a[10])读取字节数取算

    system("pause");
    return 0;
}

9.联合体和枚举

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

//联合类型,所有成员共用一块内存。修改一个变量其他的也会被改变
union Un
{
    char c;
    short s;
    int i;

}un1;

//枚举 一组有名字的 int常数
enum Color {red ,black,white,blue=20};


int main(void)
{
    printf("%p %p %p",&un1.c, &un1.s, &un1.i);//都一样
    union Un un2 = {.i=34};//只那能初始化一个

    enum Color co = 202;//大小就是4字节
    printf("%d %d %d %d %d",red,black,white,blue,co);// 0 1 2
    system("pause");
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容