输出

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

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

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

字符 输出形式
d 十进制数
o 八进制数
x 十六进制数
c 单个字符
s 字符串
f 浮点数
p 指针(地址)
In [46]:
#include <stdio.h>

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文件中引入这个头文件,这样就可以单独编译任何一个文件。

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

预处理

文件包含

In [ ]:
//在当前目录查找文件
# include "文件名"
//根据查找规则查找文件
# include <文件名>

宏定义

In [ ]:
# define maxline 100
# define max(A,B) (A>B?A:B)

条件包含

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

指针

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

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

In [75]:
# include <stdio.h>

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语言的函数传参是将参数复制到函数内部的局部变量里,所以在函数中如何折腾都不会改变函数外变量的值。

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

In [76]:
# include <stdio.h>

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);
}

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

In [ ]:
void *swap(){
    ...
}

数组

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

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

In [89]:
# include <stdio.h>
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指向指向该数组第一个元素。

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

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

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

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

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

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

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

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

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

In [ ]:
int strlen(char *s)
{
    ...
}

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

单引号与双引号的区别

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

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

In [12]:
#include <stdio.h>
#include <string.h>

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语言就用到了字符串常量

In [112]:
# include <stdio.h>
int main(){
    printf("hello word!\n");

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

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

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

指针数组

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

In [2]:
# include <stdio.h>
int main(){
    char *name[] = {
        "January","Feb","Mar"
    };
    
    printf("%s,%s,%s",name[0],name[1],name[2]);
}

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

In [ ]:
char **p;
p = name;

多维数组

In [166]:
# include <stdio.h>
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}
    };
}

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

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

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

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

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

In [ ]:
int *p;
p = (int *)arr;

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

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

In [ ]:
int (*p)[5];
p = arr;

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

函数指针

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

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

In [267]:
#include <stdio.h>
 
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));
}

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

In [279]:
#include <stdio.h>
 
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);
}

结构体

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

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

In [189]:
# include <stdio.h>
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

In [197]:
# include <stdio.h>

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);
}

联合体

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

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

In [236]:
# include <stdio.h>

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>中提供了很多实用函数:

In [ ]:
#include <stdlib.h>

//产生随机数
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>

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

In [4]:
#include <stdio.h>
#include <limits.h>

int main(){
    printf("%d\n",INT_MAX);
    printf("%d\n",INT_MIN);
}
In [ ]:

posted @ 2020-05-04 22:46:42
评论加载中...

发表评论