C语言基础知识回顾

注意:本文最初使用jupyter notebook编写,后经程序转换为markdown,所以格式可能有多处错误,懒得修改了。

输出

输出字符功能需要与环境进行交互,很复杂,这不是C语言所包含的功能。 练习的过程中需要用到输出显示结果,所以我们有必要先学习一下输出功能。标准库IO库提<stdio.h>提供了输出函数:

int printf(char *format,arg1,arg2,...)

第一个参数是输出的格式,以后的参数都是要输出的变量

字符 输出形式
d 十进制数
o 八进制数
x 十六进制数
c 单个字符
s 字符串
f 浮点数
p 指针(地址)

#include &lt;stdio.h&gt;

int main(){
    int a = 1;
    char b = 's';
    char c\[10\] = "abcdefg";
    float d = 1.2;
    
    printf("%i,%c\\n%s\\n%f",a,b,c,d);
    return 0;
}

数据类型

c语言中只有四种基本的数据类型

符号 数据类型 16位机器 32位机器 64位机器
char 字符型 8位 8位 8位
int 整型 16位 32位 32位
float 单精度浮点型 16位 32位 32位
double 双精度浮点型 64位 64位 64位

另外提供short,long,signed,unsigned等限定符。

理论上int的长度应该是cpu一次所能处理的位数,但实际上现在64位cpu中的整型也是32位,因为32位的int足够用,改的话涉及兼容性问题,况且想存储更大的数可以加long修饰符。

变量及函数的声明

c语言与脚步语言的一大不同是变量和函数都需要先声明后使用。

变量声明是为了给变量分配空间。

函数需要先声明是因为函数通常在多个文件中定义,而这些文件还可以单独进行编译,那么单独编译某一个文件的时候如何找到其他文件中的函数?c语言的做法是把所有函数的声明放到一个后缀位.h的文件中,在所有的.c文件中引入这个头文件,这样就可以单独编译任何一个文件。

当然大型项目有很多个头文件,对于中小型程序只需要一个头文件就够了。

预处理

文件包含


//在当前目录查找文件
\# include "文件名"
//根据查找规则查找文件
\# include &lt;文件名&gt;

宏定义


\# define maxline 100
\# define max(A,B) (A>B?A:B)

条件包含


//为了让hdr.h只被包含一次,可以这样写
\# if !define(HDR)
\# define HDR
    /\* 头文件内容 */
\# endif

指针

指针是一种保存变量地址的变量。

需要注意的是,指针只能指向某种特定的数据类型,整型指针只能指向整型。


\# include &lt;stdio.h&gt;

int main(){
    int a =1,b=2;
    int *ip;  //指向整型的指针

    ip = &a;  //现在ip存储着变量a的地址
    
    b = *ip;  //*ip可以当a用
    *ip = 3;  //*ip可以当a用
    
    printf("%x,%d,%d",ip,a,b);
}

c语言的函数传参是将参数复制到函数内部的局部变量里,所以在函数中如何折腾都不会改变函数外变量的值。

但是如果想在函数内部修改函数外部的变量怎么办?可以将指针传进函数,然后函数内部用指针修改。


\# include &lt;stdio.h&gt;

void swap(int *px, int *py){
    int temp;
    
    temp = *px;
    *px = *py;
    *py = temp;
}

int main(){
    int a =1,b=2;
    swap(&a,&b);
    
    printf("%d,%d",a,b);
}

如果一个函数返回指针可以这样定义函数


void *swap(){
    ...
}

数组

数组是基础数据结构的集合。4种基础数据结构int,char,float,double都可以组成数组。

定义的时候必须声明大小,以便分配空间。


\# include &lt;stdio.h&gt;
int main(){
    int *ip;
    
    int a\[10\];
    char b\[11\];
    float c\[12\];
    double d\[13\];
    
    a\[2\] = 123;
    b\[10\] = 'c';
    c\[6\] = 1.732;
    d\[7\] = 1.414;
    
    ip = &a\[2\];
    
    printf("%d",*ip);
}

当我们声明一个数组时,例如int a[4];,系统做了什么?

系统给我们分配了5个int型变量的空间,再产生一个指针变量static int *a指向指向该数组第一个元素。

所以,数组名就是一个指针,我们可以像使用指针一样使用数组名。


//取数组第一个元素的地址
pa = &a\[0\];
pa = a;

//取第i个元素的地址
pa = &a\[i\];
pa = a+i;

//取第一个值
b = a\[0\]
b = *a

//数组下标取值 等价于 指针取值
b = a\[i\]
b = *(a+i)

唯一与指针不同的地方是,数组名是一个静态的指针,它不能改变


//指针可以自增指向下一个位置
pa++
    
//数组不可以自增
a++ //❌

既然数组名是一个指针,那么函数在传参过程中,传递的就是指针。

如果一个函数需要接收一个数组,需要用指针来接收。


int strlen(char *s)
{
    ...
}

也可以使用char s[],与char *s等价,但后者更直观

单引号与双引号的区别

单引号代表一个字符,需要用char来存储。

双引号代表一个字符串,需要用char[]来存储。字符串的末尾会多存储一个二进制为0的字符\0,用来标识字符串结束。

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

int main(){
    char str1\[5\] = "12345";
    char str2\[6\] = "12345";
    printf("strlen(str1)=%d\\n", strlen(str1));
    printf("strlen(str2)=%d\\n", strlen(str2));
}

str1没给'\0'留位置,编译器不会报错,但是strlen()会一直找下去,直到遇到'\0'停止,所以这里的strlen(str1)的值是错的。str2多分配了一个字节给'\0',所以strlen(str2)的值是对的。

so,我们为字符串分配空间时可以多分配一个字节,这样有有助于判断字符串的结束。

字符常量

每个人几乎是刚接触到c语言就用到了字符串常量


\# include &lt;stdio.h&gt;
int main(){
    printf("hello word!\\n");

    //相当于
    char *p = "hello word!\\n";
    printf(p);
}

字符串常量是不可以修改的,如果想要修改可以定义成字符数组。


\# include &lt;stdio.h&gt;
int main(){
    char message\[\] = "hello word!\\n";
    
    message\[0\] = 'o';
    message\[1\] = 'o';
    message\[2\] = 'o';
    message\[3\] = 'o'; 
    
    printf("%s",message);
}

指针数组

c语言支持将指针存储在一个数组中,构成指针数组,它的定义如下:


\# include &lt;stdio.h&gt;
int main(){
    char *name\[\] = {
        "January","Feb","Mar"
    };
    
    printf("%s,%s,%s",name\[0\],name\[1\],name\[2\]);
}

数组名是指针,那么指针数组名当然是指针的指针


char **p;
p = name;

多维数组


\# include &lt;stdio.h&gt;
int main(){
    int arr\[5\]\[5\] = {
        {0,0,1,0,0},
        {0,0,1,0,0},
        {0,0,0,0,1},
        {0,1,1,0,0},
        {0,1,0,1,0}
    };
}

多维数组与指针数组在使用的语法上很类似,但是原理完全不同:


num\[2\]\[3\]   //取指针数组中秩为2的指针,再取该指针所指向的数组中秩为3的值
arr\[2\]\[3\]   //取多维数组行秩为2,列秩为3的值

指针数组在内存中是一个数组,存了很多指针。通过指针找下一维度的数组。

多维数组是在内存中存了m*n个整数。它在内存中的存储形式和一维数组一样,只不过可以自动计算偏移量而已。

多维数组名与一维数组名一样,都是指向首元素的指针,我们也可以手动计算偏移位置取值:

int *p;
p = (int *)arr;

printf("%d",*(p+j*3+2)); //取第4行秩为2的元素

当然你也可以这样定义指针,它可以自动计算偏移:


int (*p)\[5\];
p = arr;

printf("%d",p\[3\]\[2\]); //取第4行秩为2的元素

函数指针

就像数组名是指向数组的指针一样,函数名是指向函数的指针。

唯一不同的是,通过函数指针调用函数可以不加*


#include &lt;stdio.h&gt;
 
int max(int x, int y)
{
    return x > y ? x : y;
}
 
int main()
{
    printf("%p\\n",max);
    printf("%d\\n", (*max)(1,6));
    printf("%d\\n", max(1,6));
}

通过函数指针可以实现回掉函数的功能


#include &lt;stdio.h&gt;
 
int max(int x, int y)
{
    return x > y ? x : y;
}

void printmax(int x,int y,int max (int x, int y))
{
    int a = x + y;
    int b = x + 10;
    
    printf("%d\\n", max(a,b));
}
 
int main()
{
    printmax(1,6,max);
    printmax(10,6,max);
}

结构体

结构体可以理解为脚本语言中类的简化版。

类需要先定义,然后再使用,结构体同样需要先定义后使用。通过结构体.成员来访问成员。


\# include &lt;stdio.h&gt;
int main(){
    //定义结构体
    struct point{
        int x;
        int y;
    };

    //生成结构体变量
    struct point a = {200,200},b = {200,200},c = {200,200};
    
    //定义并生成
    struct {
        int x;
        int y;
    } d,e,f;

    printf("%d,%d",c.x,a.y);
}

我们知道数组名其实是一个指针,那么结构体名是指针吗?不是的。

结构体相当于一个普通的变量,函数传参会复制整个结构体,但是通常结构体很大,所以常常传递指针给函数。

r->x等效于(*r).x


\# include &lt;stdio.h&gt;

struct rect{
    int x;
    int y;
};

int canonrect(struct rect *r)
{
    //使用指针和. 访问成员
    printf("%d",(*r).x);
    
    //与上述等效
    printf("%d",r->x);
}

int main(){
    struct rect a = {200,200};

    canonrect(&a);
}

联合体

联合体是一种非常特殊的结构体。其定义和取值的方法都与结构体相同。他们的不同点是:结构体会为每一个成员变量分配内存空间。联合体只按照最大的成员变量分配一块内存空间,所有的成员变量公用这一块内存空间。很明显,联合体不能保存所有的成员变量,每一时刻只能保存其中一个成员变量。

我们可以将联合体视作一个变量,这个变量可以合法保存多种数据结构的类型。


\# include &lt;stdio.h&gt;

union data{
    int a;
    char b;
    float c;
};

int main(){
    union data u;
    
    u.a = 1;
    printf("%d, %c, %f\\n", u.a, u.b, u.c);
    u.b = 'a';
    printf("%d, %c, %f\\n", u.a, u.b, u.c);
    u.c = 1.732;
    printf("%d, %c, %f\\n", u.a, u.b, u.c);

}

标准库<stdlib.h>

标准库<stdlib.h>中提供了很多实用函数:


#include &lt;stdlib.h&gt;

//产生随机数
int rand(void)
    
//分配nobj个大小为size的空间
void *calloc(size_t nobj, size_t size)
    
//分配1个大小为size的空间
void *malloc(size_t size)
    
//改变已分配内存的大小
void *realloc(void *p, size_t size)
    
//释放内存
void free(void *p)
    
//使程序非正常终止
void abort(void)
    
//使程序正常终止
void exit(int status)

标准库<limits.h>

有的时候我们需要在计算机中表示无穷大和无穷小,这时可以用计算机能表示的最大值和最小值代替。


#include &lt;stdio.h&gt;
#include &lt;limits.h&gt;

int main(){
    printf("%d\\n",INT_MAX);
    printf("%d\\n",INT_MIN);
}
posted @ 2020-05-04 22:46:42
评论加载中...
发表评论