c语言笔记

c语言笔记

一、警告与报错

出现三个警告:1. 转义字符\可以在字符串中打印双引号,但如果要打印%,则需要%%,

  1. multi-line comment:多行注释

    在C/C++语言中,在对源文件做预处理的时候,有两条基本原则:
    1、凡是以//开头的为单行注释
    2、凡是以\结尾的代表此行尚未结束

    于是预处理器在处理的时候会先按第二条规则,看每行的末尾的那个字符是不是”\”,是的
    话,就下一行接到本行。

所以,当单行注释的最后一个字符是,会将下一行也认为是注释,当最后一个字符是汉字时,也可能会因为字符集编码的问题,从而推掉下行代码

  1. 当一个变量在未被赋值时,参与运算,则或报错

  2. initialization of ‘int *’ from ‘int’ makes pointer from integer without a cast(强制转换):

    初始化是把一个指针类型的对象赋给一个整形变量,而且也没有进行强制类型转换

  3. warning: ‘d’ defined but not used :定义未使用。

  4. ‘weight’ is used uninitialized (初始化)in this function ,即主要是因为指针没有指向可用的内存地址或没有分配地址

内存泄漏
#include <stdio.h>
#include<string.h>
#define DENSITY 62.4
int main()
{
    float weight, volume;
    int size, letters;
    char name[40];

    printf("hi! what\"s your first"); //打印%用%%,打印其他的特殊字符,用
    scanf("%s",name); //使用scanf将字符串读入字符数组时不加寻址号,读取基本数据类型时用&
    scanf("%f"&weight)
    size = sizeof(name); //sizeof后面是变量时,可不加括号
    letters = strlen(name);
    volume = weight / DENSITY;
    printf("well, %s, your volume is %2.2f cubic feet.\n",
           name, volume);
    printf("also, your first name has %d letter,\n", letters);
    printf("and we have %d bytes to store it.\n", size);

    return 0;
}

二、sizeof函数

#include <stdio.h>
int main()
{
    char a = 'a';

    printf("%d\n",sizeof a );
    printf("%d\n",sizeof(char));
    printf("%d\n",sizeof 'a');

    return 0;
}

输出为:1 1 4

分析:a是一个变量,占一个字节,但’a’的本质是:C99标准的规定,'a’叫做整型字符常量(integer character constant),被看成是int型,所以在32位机器上占4字节。

三、格式化输出

#include <stdio.h>
int main(void)
{
    printf("%x %X %#x\n",31 ,31 ,31);
    printf("**%d**% d**% d**\n", 42,42,42);
    printf("**%5d**%5.3d**%05d**%05.3d**\n", 6, 6, 6, 6);

    return 0;
}

输出为:1f 1F 0x1f
** 42** 42** 42**
** 6** 006 ** 00006 ** 006**

  1. printf格式化输出

    #include<stdio.h>
    int main()
    {
        printf("%-5.2f**\n", 123.325);
        printf("%4f\n", 123.36);
        printf("%4.2f\n", 123.36);
        printf("%14f\n", 123.36);
        printf("%4d\n", 123.36);
        printf("%d\n", 123.36);
    
        return 0;
    }
    
    

    输出:

    main.c:146:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
    printf("%d\n", 123.36);
    123.33**
    123.360000(数据宽度大于格式化的宽度,按实际宽度输出)
    123.36(精度小于实际精度,按四舍五入输出
    123.360000(小数点也站一个宽度)
    1030792151
    1030792151

  2. 警告: ‘0’ flag ignored with precision and ‘%d’ gnu_printf format

四、三元运算符

三元运算符:?:

  1. 条件 ? 表达式1 : 表达式2

  2. 在计算完条件之后,有一个序列点。如果结果不等于 0(换句话说,如果条件计算结果为 true),则只有第二个操作数(也就是表达式 1)会被计算,并且表达式 1 的值就是整个表达式的结果。

    另一方面,如果结果为 0(如果条件计算结果为 false),那么只有第三个操作数(也就是表达式 2)会被计算,并且表达式 2 的值就是整个表达式的结果。以这种方式,条件运算符代表了在程序流中的条件式跳转,因此,有时候可以与 if-else 语句相互替代。

    inline int iMax(int a, int b) { return a >= b ? a : b; }
    
    1. 判断运算符可嵌套使用,?abc?abc:abc

五、分支与跳转

switch语句

switch中必须要是字符或整型 与case后的相比较看是否相等,如果所有的都不符合,就执行defalut语句,或者在没有defalut语句的情况下就直接跳出switch语句,而不输出任何值。

若defalut在程序最前面,则依顺序执行原则,若此时default后没有break,会执行case

#include <stdio.h>
char *p(char c);
char *p(char c)
{
    switch(c)
    {
        case('A') :
            return "APPLE";//返回一个指针
        case('B'):
            return "banana";
        default:
            return "None";
    }
}
int main()
{
    char input;
    scanf("%c", &input);
    printf("%s", p(input));
   // printf("%p",&p(input));
    printf("%p", &input);
    return 0;
} 1
if else语句

六、声明与定义

extern

C++程序通常由许多文件组成,为了让多个文件访问相同的变量,C++区分了声明和定义。

变量的定义(definition)用于为变量分配存储空间,还可以为变量指定初始值。在程序中,变量有且仅有一个定义。

声明(declaration)用于向程序表明变量的类型和名字。定义也是声明:当定义变量的时候我们声明了它的类型和名字。可以通过使用extern声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern。

extern声明不是定义,也不分配存储空间。事实上它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。

只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern。

任何在多文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
typedef

C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE

typedef unsigned char BYTE;

按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写

您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:

#include <stdio.h>
#include <string.h>
 
typedef struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;
 
int main( )
{
   Book book;
 
   strcpy( book.title, "C 教程");
   strcpy( book.author, "Runoob"); 
   strcpy( book.subject, "编程语言");
   book.book_id = 12345;
 
   printf( "书标题 : %s\n", book.title);
   printf( "书作者 : %s\n", book.author);
   printf( "书类目 : %s\n", book.subject);
   printf( "书 ID : %d\n", book.book_id);
 
   return 0;
}
#define

#define 是 C 指令,用于为各种数据类型定义别名

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

七、getchar语句

#include <stdio.h>
int main()
{
    int count = 0;
    
    while (getchar() != '\n')
    {
        count = count + 1;
    }
    printf("%d", count);
    return 0;
}

只能反复输入,而回车操作不能使程序跳出循环,再次运行时可以正常输出。

ps:1. 循环的基本结构:初始化计数器,循环条件,更改计数器

  1. 素数:不能被2—sqrt(i)这个区间的数整除的,即为素数

八、有关循环的一些注意事项

  1. (3) for语句循环中表达式 3:调节器
    调节器(例如计数器自增*)在每轮循环结束后且表达式 2 计算前执行*。即,在运行了调节器后,执行表达式 2,以进行判断。

  2. for循环中,表达式可以省略,但是;不可以省——for(;;)

  3. continue对于for语句与while语句具有不同的影响。

    对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 whiledo…while 循环,continue 语句重新执行条件判断语句。

    #include <stdio.h>
    int main()
    {
        for (i = 0; i < 100; i++)
        {
            if (i%2)
            {
                contiue;
            }
        }
            return 0;
    }
    

    下面的语句是一个死循环

#include <stdio.h>
int main()
{
    int i = 0;
    while (i < 100)
    {
        if (i%2)
        {
            contiue
        }
        i++//被跳过
    }
        return 0;
}

九、数组的相关知识

  1. 声明:type arrayName [ arraySize ];

  2. 初始化:***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[4] = 50.0;

  3. 数组支持动态定义数组:可以在定义长度时使用变量。

    //使用malloc动态定义数组
    int n;
    printf("请输入总人数:\n");
    scanf("%d",&n);
    int *a;
    a = (int *)malloc(sizeof(int)*n);
    //使用变量动态定义数组(报错)
    int i;
    int n;
    printf("请输入总人数:\n");
    scanf("%d",&n);
    int a[n];
    for(i = 0;i = n;i++)
    {
        a[i] = 1;
    }
    
  4. 初始化字符数组的方式

  5. 字符串的赋值可以用=吗?

    答:不可以,报错assignment to expression with array type

    翻译:数组类型匹配错误。
    给char数组赋值字符串在数组定义时可以完美运行,但是在如上情况就会报错。因为此时数组名表示的是一个指针,指向数组首元素地址,这样赋值就等于尝试修改地址。

  6. #include<stdio.h>
    int main()
    {
        char str[5] = { 'A','B','\0','D' };
        int i;
    
        for (i = 0; i < 5; i++)
            printf("%d ", str[i]);//  \0在内存中实质是0
        printf("\n%s", str);
        printf("\n%s", &str[1]);//传入一个地址
        return 0;
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWTLjBoz-1625412652077)(C:\Users\onlooker\AppData\Roaming\Typora\typora-user-images\image-20210514084755004.png)]

十、相关板块函数的总结

函数

函数声明扩大作用域。

c语言中的形参与实参传递机制和java一样,采用复制传递。

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数

形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。

当调用函数时,有两种向函数传递参数的方式:

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

默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数

i/o篇
  1. printf

  2. scanf:scanf的转换说明的意义:例如%d,你输入一个数,我把他当做10进制

    int a;
    
    scanf("%o",&a);
    printf("%d",a);
    //输入108,结果为8
    //原理:在缓冲区存在的108,将尽量去匹配能够用8进制表达的数,所以匹配到10,解读为8
    
  3. putchar

  4. getchar

  5. puts:属于stdio.h,与printf不同的是:它只显示字符串,且自动在末尾加上换行符(puts(“i always be there for you ”))

字符数组篇<string.h>
  1. strlen
  2. strcpy(str1,str2):str2的结束符也会被拷贝过去;且str1的容量大小必须大于str2,否则即使没有报错(c语言不会检查数组的边界),但是会造成在拷贝过程中数据的丢失。
  3. strncpy(str2,str2,n):n可以控制粘贴过去的长度,但不包括结束符(字符数组中结束符很重要吗?,如果没有,字符串读取时就会继续向后读取,读取到未初始化的内存,可危险了。)
  4. strcat
  5. strncat
  6. strcmp
  7. strncmp
  8. 菜鸟教程上全都有

十一、关于指针

#include<stdio.h>
#include<stdlib.h>
void Fun1(int x,int y);
int main()
{
	int n = 1,m = 2;
	Fun1(n, m);
	printf("%d   %d", m, n);
}
void Fun1(int x,int y)
{
	int temp;
	temp = x;
	x = y;
	y = temp;
}

结果:2 1,交换失败。因为传入的地址在函数结束后消失。

形参与实参是不一样的内存。

其实还真的是,函数调用中的形参在调用时分配内存,调用结束后又被回收。也就是当执行earn(money);的时候,重新开辟了一块跟money变量一样大小的内存,即形参的内存,当函数调用结束后该内存又被回收,相当于过年时候你姑姑给了你压岁钱,然后又被你爸妈拿回去给你姑姑的孩子。earn函数中修改的只是形参的值,而不是实参的值。所以实际上实参的值并没有发生改变,仍然为0

解决办法:传入指针

#include <stdio.h>
void earn(int *money)
{
    *money += 100;
}
int main()
{
    int money=0;

    earn(&money);
    printf("I have %d money", money);
    return 0;
}

赋值给实参

基础知识
  1. 指针类型的作用,指明读取的范围。
指向数组的指针

地址常量与地址变量的区别:数据存储的空间中的数据可以被修改,这个空间称为变量,如果空间中的数据不能被修改,这个空间称为常量。地址常量就是地址不能被修改,就像一维数组中的数组名,是一个指针常量,不可被运算和不可被改变。地址变量就是地址能修改,就像一级指针,是一个指针变量,可以通过移动下标或移动指针来改变。

#include <stdio.h>
int main()
{
    char *pn = "i lovefishc";
    int a[3][4] = {0};
    //a[0] + 1跨度4个字节,a + 1跨度16个字节

    printf("%p\n",pn);
    printf("%s", pn);
    int b;
    b = &b + 4;//此时b是一个指针
    printf("%p\n%p", b,&b);
    return 0;

}

输出:

0000000000409000
i lovefishc(使用指针初始化数组,数组名等于指针名)

指向指针的指针
#include<stdio.h>
int main()
{
    char *pps[] = {
        "c的陷阱与缺陷",
        "c专家编程",
        "c与指针"};
    char **p1;
    char **ps[2];

    p1 = &pps[0];
    ps[0] = &pps[1];
    ps[1] = &pps[2];
    printf("%s\n", pps[0]);
    printf("%s\n", * p1);
    printf("%d\n",**p1);
    printf("%p\n", *ps[0]);
    printf("%d\n", **ps[0]);

    return 0;
}

输出:

c的陷阱与缺陷
c的陷阱与缺陷
99
0000000000409016
99

数组指针

当**p = array(一个数组名),编译器只把她当作一个一维数组。p+1上移4个字节。

#include<stdio.h>
int main()
{
    char array[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}};
    char **pp = array;//会发出警告:initialization of 'char **' from incompatible pointer type 'char (*)[4]
    char(*p)[4] = array;//p依旧是指向一维数组,但他的跨度改变了。

    printf("%p\n", pp);
    printf("%p\n", pp + 1);
    printf("%p\n", &array[0]);
    printf("%p\n", p);
    printf("%d\n", **(p));
    printf("%d\n", **(p+1));
    printf("%d\n", *(*(p + 1) + 1));


    return 0;
}

输出:

000000000061fe14
000000000061fe1c
000000000061fe14s
000000000061fe14
1
5
6

常量与指针
  1. 指向常量的指针:可以修改为指向不同的变量,可以修改为指向不同的常量,可以读取指针指向的数据,但不可以通过解引用修改指针指向的数据。

  2. 常量指针:本身不可以改变,但*p可以改变。

  3. 指向常量的常量指针:*p与p都不可以修改。

    #include <stdio.h>
    int main ()
    {
        int num = 1,cnum = 2;
        const int *p = &cnum;
        const int *const pp = &num;
    
        num = 2;
        cnum = 3;
        printf("%d",*pp);
        printf("%d", *p);
    
        return 0;
    }
    

    输出:2 3

    函数指针与指针函数

    函数指针:

    函数指针是指向函数的指针变量。

    通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。

    函数指针可以像一般函数一样,用于调用函数、传递参数。

    函数指针变量的声明:

    typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型
    #include <stdio.h>
     
    int max(int x, int y)
    {
        return x > y ? x : y;
    }
     
    int main(void)
    {
        /* p 是函数指针 */
        int (* p)(int, int) = & max; // &可以省略
        int a, b, c, d;
     
        printf("请输入三个数字:");
        scanf("%d %d %d", & a, & b, & c);
     
        /* 与直接调用函数等价,d = max(max(a, b), c) */
        d = p(p(a, b), c); 
     
        printf("最大的数字是: %d\n", d);
     
        return 0;
    

    指针函数:以指针为返回值的函数。

    返回的指针如果是函数中的内部局部变量的地址,则返回值是一个无效的地址。

十二、逻辑表达式的短路特性

#include <stdio.h>
int main()
{
    int a = 1, b = 0, c = -2;
    int z;
    z = a++ || ++b && --c;
    printf("a = %d,b = %d,c = %d,z = %d\n", a, b, c, z);
    a = 0;
    z = a++||++b&&--c;
    printf("a = %d,b = %d,c = %d,z = %d\n", a, b, c, z);
    b = -1;
    z = a++ || ++b && --c;
    printf("a = %d,b = %d,c = %d,z = %d\n", a, b, c, z);
    return 0;
}

运算结果:

a = 2,b = 0,c = -2,z = 1
a = 1,b = 1,c = -3,z = 1
a = 2,b = -1,c = -3,z = 1

结果分析:

  1. a++为2,||为或,在a = 2后,就不对++并进行计算,直接得到结果1;非0的数都是真。

十三、存储类别、链接与内存管理

作用域、链接与存储期
#include<stdio.h>
int func();//全局作用域
int count;//全局
extern int c;
static int d;

int func(int b)//b是局部
{
    printf("%d\n", count);
    int count = b;
    printf("%d", count);

    return 0;
}
int main ()
{
    int a;
    func(1);
    printf("\n");
    a = count; 
    printf("%d", a);
    return 0;
}

输出:0 1 0

  1. 在块中定义一个static变量的结果:得到一个静态局部变量,作用域不变,生存期变为静态存储。静态存储:内存空间分配以后,变量一直存在,在块调用完成后,不清零。再调用的时候,等于进行了两次操作。
  2. extern:全局变量,作用范围覆盖整个文件,但是当同名时,局部变量优先
动态内存管理
malloc
  1. 函数原型与其具体说明:void *malloc(size_t size),向系统申请size个字节的内存空间,并返回一个指向这个空间的void型指针。

  2. void型指针可转化为任意类型的指针

  3. 调用失败或size设置为0,返回null

#include <stdio.h>
#include<stdlib.h>
int main ()
{
    int *p;

    p =(int*) malloc(num * sizeof(int));//不同的系统int的字节数不一样
    if (p == NULL)
    {
        printf("调用失败");
    }
    else
    {
        printf("请输入一个整数");
        scanf("%d\n", p);
        printf("%d\n", *p);//为甚么要输入两次才能成功读取呢(scanf后面不能用于读取\n)
        //&p[i + 1],    
    }
    return 0;
}
free
  1. void free(void *ptr)
  2. 用于释放由malloc、calloc、realloc申请的空间。释放其他的空间将导致未定义行为。空间为null,则不作操作。
  3. 函数不修改参数本身的值,调用后*ptr被清空,但指针指向原来的地方。
#include <stdio.h>
#include<stdlib.h>
int main ()
{
    int *p;

    p =(int*) malloc(sizeof(int));
    if (p == NULL)
    {
        printf("调用失败");
    }
    else
    {
        printf("请输入一个整数");
        scanf("%d\n", p);
        printf("%d\n", *p);
        free(p);                  //此处释放了空间
        printf("%d\n", *p);      //此时访问为非法访问
    }
    return 0;
}

输出:

2
2(scanf读取两个2,后,正确输出)
9986864

十四、字符串

  1. puts函数只显示字符串

    #include<stdio.h>
    int main()
    {
        puts("you")
        return 0;
    }
    
    定义字符串的方法

    1、字符串常量

      1. 用“”括起来的叫做字符常量
    
      2. 如果字符串常量之间没有间隔,视为一个常量
         3. \“可以打印双引号
      3. 字符串常量属于静态存储类别,若在函数中使用,该字符串只被存储一次,并存在于整个函数的生命周期。即使多次调用,用双括号括起来的内容会被视为指向该数组的指针。
         4. 定义时,a[]的长度包含了\0的内存,如果填满,输出时将不会停止。
         5. puts(“abc\0777\0”),strlen("abc\0777\0"),输出为abc7,5。
         6. 字符串中出现\,可能是由1-3位8进制数,2位16进制数构成的转义,此时只构成一个字符。若两种都不是,\将被视为不存在。
      4. 
    

十五、结构体

struct tag {    //标签的作用
    member-list
    member-list 
    member-list  
    ...
} variable-list ;//声明结构体变量

tag 是结构体标签。

member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。

variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。下面是声明 Book 结构的方式:

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;
 
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
 
//也可以用typedef创建新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

关于在结构体变量在存储时的内存对齐问题

结构体嵌套:结构体中有另外一个结构体作为成员

#include<stdio.h>
struct Data {
    int year;
    int month;
    int day;
};
struct Book {
    int number;
    struct Data data;
}book = {
        1,
    {2020,3,12}
         };
int main()
{
    printf("%d ", book.data.month);
    return 0;
}
结构体数组与结构体指针
#include<stdio.h>
struct Data {
    int year;
    int month;
    int day;
};
struct Book {
    int number;
    struct Data data;
}book = {
        1,
    {2020,3,12}
         };
int main()
{
    struct Book* p;
    p = &book;//结构体的名字不是指向首地址的指针,所以要加&
    printf("%d  ", p->data.month);//->意为对象选择,适用对象是指针,(*p).data = p->data;
    printf("%  ", (*p).data.month);//.号运算符的优先级大于*,所以要加括号
    printf("%d ", book.data.month);
    return 0;
传递结构体变量与结构体指针

因为传递结构体变量名,会拉慢程序效率(因为结构体的数据量可能会很庞大,传入结构体变量会将所有的数据全部传入,浪费时间与空间),所以传入指针会使更好的办法。

访问结构体有两种方式:下标或者是点号取值

#include<stdio.h>
struct Data {
    int year;
    int month;
    int day;
}data;
struct Book {
    int number;
    struct Data data;
}book1;
void  get_month(struct Book *book)
{
    book->data.month = 3;
}
int main()
{
    struct Book *book3 = &book1;
    get_month(book3);
    printf("%d ", book1.data.month);
    return 0;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值