C语言入门

标签(空格分隔): C 编程


"根据我们的经验,C语言是一种令人愉快的,具有很强表达能力的通用的语言,适合于编写各种程序。它容易学习,并且随着使用经验的增加,使用者会起来越感到得心应手。"

UNIX与C语言之父 -- Ken Thompson与Dennis Ritchie

Ken Thompson(上图左)

  • 1943 出生于美国新奥尔良。
  • 1966 加入贝尔实验室。
  • 1969 用汇编语言写出了UNIX的第1个版本,运行在PDP-7上,随后UNIX被移植到了PDP-11/20上。
  • 1971 由于B(BCPL,Basic Combined Programming Language 1967年由剑桥大学的Matin Richards开发)语言在进行系统编程时不够强大,所以Thompson和Ritchie(另一位C语言之父)对其进行了改造,并与1971年共同发明了C语言(当时还叫做new B),在B语言的基础上增加了数据类型和结构。

Dennis Ritchie(上图右)

  • 1941年9月9日出生于美国纽约,哈佛大学数学博士。
  • 1967 进入贝尔实验室。
  • 1969 加入了Multics项目,那是由贝尔实验室、麻省理工学院和通用电器三家的合作项目。Ritchie负责多道处理机的BCPL语言和GE650的编译器。
  • 1973 Ritchie和Thompson用C语言重写了UNIX。
  • 1978 与Brian W. Kernighan一起出版了著名的《C程序设计语言(The C Programming Language)》
  • 1983 与Ken Thompson一起获得了图灵奖。
  • 2011年10月9日,去世,享年70岁。
PDP-7
PDP-11(站着的为Ritchie)

BCPL是由同样由剑桥大学开的的CPL(1963年)语言上改进(简化,所以加了Basic)而来.
而CPL是由ALGOL60的基础上简化而来。后者标志着程序设计语言成为一门独立的科学学科。它是ALGOrithmic language的缩写,是计算机发展史上首批产生的高级程式语言家族。

****

编译过程

gcc --save-temps C程序文件可查看各过程生成的文件
1.预处理: .c --> .i
2.编译: .i --> .s
3.汇编: .s --> .o 目标文件
4.链接: .o --> .elf(.exe)

#include <stdio.h>           // 预处理段.  头文件地址 /usr/include/*.h,自定义的头文件用""号包含

int main(void)
{
    printf("Hello World!\n");
    return 0;
}

gcc编译器用法

格式: gcc [options] *.c ...
    -o OUTFILE  : 指定生成的可执行程序名称
    -c          : 只编译
    --save-temps: 可生成编译过程生成的各种中间文件
    

C/C++程序中的数据类型

数据类型 占用字节数
int 4个byte
short,short int 2个byte
long,long int 4个byte
C语言对于各种数据所占用内存字节数比较宽泛,只要求long型数据长度不短于int型,short型不长于int型.
float 4个字节,7位精度(-3.4e38~3.4e38)
double 8个字节,15位精度(-1.7e308~1.7e308)
long double 8个字节,15位精度(-1.7e308~1.7e308)

输入输出函数

  • int printf(): 返回值输出的字符数
  • int scanf() : 返回值为读入的以','分隔的项目数
  • int putchar(int c): 向标准输出打印一个字符
  • int getchar(void):向标准输入输入一个字符Ctrl+D会得到EOF

格式化字符串

格式:%type
%a       浮点数、十六进制数字和p-计数法(C99)
%A       浮点数、十六进制数字和p-计数法(C99)
%c       输出单个字符
%d       以十进制形式输出带符号整数(正数不输出符号)
%i       有符号十进制整数(与%d相同),用在scanf中会自动转换进制
%e或%E   以指数形式输出单、双精度实数
%f或%F   以小数形式输出单、双精度实数
%g       以%f%e中较短的输出宽度输出单、双精度实数,%e格式在指数小于-4或者大 于等于精度时使用
%G       以%f%e中较短的输出宽度输出单、双精度实数,%e格式在指数小于-4或者大于等于精度时使用
%o       以八进制形式输出无符号整数(不输出前缀O)
%p       打印地址(有前缀0X)
%s       输出字符串
%x       以十六进制形式输出无符号整数(不输出前缀0X)
%X       以十六进制形式输出无符号整数(不输出前缀0X)
%u       以十进制形式输出无符号整数
%n       读入/写出的个数

printf更细致的用法:
%[flags][width][.prec][hlL]type
    flags: 如 printf("%-9d\n", 123);
        -: 左对齐
        +: 在前面放+或-
    space: 正数留空
        0: 0填充
    width: 整个输出占据的位数    
    
    hIL:
        hh: 单个字节
        h : short
        l : long
        ll: long long
        L :
        
scanf更细致的用法:
%[flags]type
    flags:
        *  : 跳过  如 scanf("%*d%d", &num);只会读取第2个数 
        NUM: 最大字符数
        hh : char
        h  : short
        l  : long, double
        ll : long long
        L  : long double 

各数据类型的极限值

#incluce <limits.h>
char c = CHAR_MIN | CHAR_MAX
short s = SHRT_MIN | SHRT_MAX
int i = INT_MIN | INT_MAX
long l = LONG_MIN | LONG_MAX
long long ll = LLONG_MIN | LLONG_MAX
float f = FLT_MIN | FLT_MAX
double d = DBL_MIN | DBL_MAX
long double ld = 

文件输入输出

打开文件:
FILE* fp = fopen("file", "r");   # FILE是结构,没有打开则返回NULL
if (fp){
    fscanf(fp, ...);
    fclose(fp);
}else{
    //文件无法打开
    ...
}

fopen的参数:
    r : 只读打开
    r+: 从文件头开始,以读写方式
    w : 以写打开,如果文件不存在则新建,如果存在则清空
    w+: 以读写打开,如果不文件不存在则新建,如果存在则清空
    a : 打开追加,如果不存在则新建,如果存在则从文件尾开始
   [w|a]x: 只新建,如果文件已存在则不能打开

#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp = fopen("haha.txt", "r");
    if (fp){
        int num;
        fscanf(fp, "%d", &num);
        printf("%d\n", num);
        fclose(fp);
    }else{
        printf("无法打开文件\n");
    }
    return 0;
}

二进制读写


size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);  

size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

ptr: 读写内存的指针
size: 读写内存的大小
niterms: 读写内存的数量(即有几个这样的内存)
stream: 文件指针
// 返回的是成功读写的字节数

内存结构

每当声明一个变量,计算机都会在内存的某个地方为它创建空间.如果在函数中声明变量,则会将其保存在一个叫栈(stack)的区段中,否则计算机会把它保存在全局量段(globals)中.如下图变量int x在函数中声明,变量int y在函数外声明.

内存结构
屏幕截图 2017-06-22 22.36.46.png

指针(pointer)

指针是能够存放一个地址的一组存储单元(通常是2个或4个byte).我在64位CentOS 7.2上得到的为8个byte.指针只能指向某种特定类型的对象.
指针做了2件事:
❶ 避免副本
❷ 共享数据

& : 取地址符可以找到变量的地址 # 这2个运算符的优先级都高于算术运算符 (*p)++ 得这样写

  • : 可以设置与读取存储器地址中的内容
int x = 5;                        // 声明一个整数变量
int* a = &x;                      // a 即表示变量x在内存中的地址
printf("x变量所在的地址:%p", a);  // *a 即表示a这个地址中的值,即5
*a = 100;                         // 将变量x所在地址的值设置为100


const来修饰指针变量时:
㈠(const在*号前,const int *p与int const *p表示相同意思)
int i = 35;
int j = 77;
const int *p = &i;                // 这里的const表示不能再通过这个p指针去修改i变量,不是说用const修饰i变量,也不是说const使p这个指针变量也成为常量
*p = 36;                          // ERROR (*p是const)
i = 36;                           // OK
p = &j                            // OK

㈡int *const p = &i;              // 表示const了指针p,即p的值(内存地址)不能被修改,但可以通过指针修改其指向的变量的值

sizeof(): 特殊运算符,返回某条数据占用空间大小.

数组变量

char quote[] = "Cookies make you fat";  // C中的字符串用字符数组来表示.数组名quote代表字符数组第一个字符的地址(即指针).
int cont[] = {1, 2, 3};

字符串

  • 以0(整数0)结尾一的串字符,0标志字符串的结束,但它不是字符串的一部分,字符串的长度不包括这个0
  • 相邻的字符串会自动连接在一起
几种写法:
char *str = "Hello";      // 不知道字符串在哪. 如果你要处理一个字符串
char word[] = "Hello";    // 表示字符串在栈中,空间会被自动回收. 如果你要构造一个字符串,应该用这种数组的方式
char line[10] = "Hello";

char*是字符串吗?

  • 字符串可以表达为char*的形式
  • char*不一定是字符串(只有它所指的字符数组有结尾的0,才能说它所指的是字符串)

安全的输入

char string[8];
scanf("%7s", string);  // 表示最多允许读入的字符数量,因为数组最后有一个'\0',所以这个数字应该比数组的大小小1.

char **a

a是一个指针,指向另一个指针,那个指针指向一个字符(串).

char a[][NUM]

二维数组的第二维必须指定

char *a[]

表示数组a中的每一个元素都是一个指针变量

字符串相关的标准库函数(#include <string.h>后)

strlen: `size_t strlen(const char *s)`
strcmp: `int strcmp(const char *s1, const char *s2)` 0表示相等 1表示要s1>s2 -1表示s1<s2
strncmp: int strcmp(const char *s1, const char *s2, size_t n),只判断n位
strcpy(不安全): `char * strcpy(char *restrict dst, const char *restrict src)` 返回目标字符串
    常用
    char *dst = (char*)malloc(strlen(src)+1);
    strcpy(dst, src);
strncpy(安全版本): char * strcpy(char *restrict dst, const char *restrict src, size_t n)
strcat(不安全): char * strcat(char *restrict s1, const char *restrict s2),把s2拷贝到s1的后面,接成一个长的字符串,返回s1
strncat(安全版本):char * strcat(char *restrict s1, const char *restrict, size_t n)

strchr: char * strchr(const char *s, int c),在字符串中找字符,返回NULL表示没有找到
strstr: chsr

真正的main函数

int main(int argc, char const *argv[])

char const *argv[]这个字符串数组内容依次为:程序名 参数1 参数2

输入输出函数

  • int putchar(int c): 向标准输出打印一个字符
  • int getchar(void):向标准输入输入一个字符Ctrl+D会得到EOF

枚举(enum)

enum 枚举类型名{name1, name2, ...};值为int,从0依次开始
enum COLOR{RED, YELLOW, GREEN};

#include <stdio.h>
enum color {red, yellow, green};

void f(enum color c);

int main()
{
    enum color t = red;
    scanf("%d", &t);
    f(t);
    
    return 0;
}

void f(enum color c)
{
    printf("%d\n", c);
}

结构体(struct)

#include <stdio.h>
int main(int argc, char const *argv[])
{
    struct date{
        int month;
        int day;
        int year;
    }; //这里的分号不能少

    struct date today; 
    today.month = 07;
    today.day = 31;
    today.year = 2014;

    //以上4行也可以用下面这2种写法
    struct date today = {07, 31, 2014};
    //没有指定day的值,day则为0
    struct date thismonth = {.month=7, .year=2014}; 
    
    
    printf("Today's date is %i-%i-%i.\n",today.year, today.month, today.day);
    return 0;
}
结构体的另外2种写法
struct point{
    int x;
    int y;
}p1,p2;
 // p1 p2都是point里面有x和y的值

匿名的结构
struct{
    int x;
    int y;
}p1, p2;             // p1 p2都是一种无名结构,里面有x和y

结构运算

p1 = (struct point){5,10}; //强制转换为point的结构体并赋值给p1,此时p1.x = 5, p1.y = 10

p1 = p2;  // 相当于p1.x = p2.x; p1.y = p2.y;p1 p2之间的关系不是引用而只是值传递

结构作为函数参数时,它是值传递的,也就是说这时候在函数内新建了一个结构变量,并复制调用者的结构的值.函数也可以返回一个结构.

指向结构的指针

struct date{
    int month;
    int day;
    int year;
}myday;

struct date *p = &myday;

(*p).month = 12; //或者可写成 p->month = 12;

结构数组

struct date dates[100];

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

typedef(自定义类型)

相当于Linux中的alias命令(个人理解),声明新的类型的名字,用于改善程序的可读性

typedef long int64_t;    // 此时int64_t就相当于我们给long取的一个别名
int64_t i = 10000000000000;

typedef struct ADate{    // 这里的ADate其实是可有可无的
    int month;
    int day;
    int year;
} Date;                  // 此时Date就相当于我们给 struct ADate类型取的一个别名
Date d = {9, 1, 2005};

联合(union)

union很像struct,但与后者不同的是union它的所有成员共享一个空间,同一时间只有一个成员是有效的,union的大小是其最大的成员

union AnElt{
    int i;
    char c;
}elt1, elt2;

elt1.i = 4;
elt2.c = 'a';
elt2.i = 0xDEADBEEF;
#include <stdio.h>

typedef union{
    int i;
    char ch[sizeof(int)];
}CHI;

int main(int argc, char const *argv[])
{
    CHI chi;
    int i;
    chi.i = 1234;
    for(i=0; i<sizeof(int); i++){
        printf("%02hhX", chi.ch[i]);
    }
    printf("\n");
    return 0;
}

全局变量

即定义在函数体外的变量.全局变量有默认初始值0,指针会得到NULL.各全局变量最好不要相互关联,都独立赋值初始化.如果在函数内有跟全局变量同名的变量但又需要引用全局变量时可用

注意:

  • 不要使用全局变量在函数间传递参数和结果
  • 尽量避免使用全局变量
  • 使用全局变量和静态本地变量的函数是线程不安全的
#include <stdio.h>

int f(void);

int gAll = 12;

int main(int argc, char const *argv[])
{
    printf("in %s gAll=%d\n", __func__, gAll);
    f();
    printf("agn in %s gAll=%d\n", __func__, gAll);
    return 0;
}

int f(void)
{
    printf("in %s gAll=%d\n", __func__, gAll);
    gAll += 2;
    printf("ang in %s gAll=%d\n", __func__, gAll);
    
    //return gAll;
}

运行结果:
[root@W530 C]# ./test 
in main gAll=12
in f gAll=12
ang in f gAll=14
agn in main gAll=14

静态本地(局部)变量

有static修饰符修饰的变量.函数结束时,它会继续存在并保持其值.其初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开时的值.实际上是全局变量(它们的保存地址在一块).
特殊的地方:生存期为全局,作用域为本地

#include <stdio.h>

int f(void);

int main(int argc, char const *argv[])
{
    f();
    f();
    f();
    return 0;
}

int f(void)
{
    static int all = 1;
    printf("in %s all=%d\n", __func__, all);
    all += 2;
    printf("ang in %s all=%d\n", __func__, all);
    
    return all;
}

输出结果:
[root@W530 C]# ./test 
in f all=1
ang in f all=3
in f all=3
ang in f all=5
in f all=5
ang in f all=7

宏定义

在C99之前因为没有const关键字可以用以下方式定义常量.在C语言的编译器开始编译之前,编译预处理程序会把程序中的名字换成值.查看gcc --save-temps 选项查看

#define PI 3.14159               // 结尾没有';'号,因为不是C语句
#define PI2 2*PI                 // 宏调用别的宏
#define FORMAT "%f\n"
#define PRT printf("%f", PI); \  // 宏定义多个值
            printf("%f\n", PI2)
#define _DEBUG                   // 用于后面判断这个宏是否已经被定义过了

C编译器中预告定义的宏(变量):
__LINE__: 在源代码中出现的地方的行号
__FILE__: 源代码文件的文件名
__DATE__: 编译时的日期
__TIME__:         时间
__STDC__: 

带参数的宏

很像函数,只是参数不带类型

#include <stdio.h>

#define cube(x) ((x)*(x)*(x))   
#define MIN(a,b) ((a)>(b)?(b):(a))

int main(int argc, char const *argv[])
{
    printf("%d\n", cube(5));
    printf("min num is %d\n", MIN(10, 19));
    return 0;
}

多个C文件如何组织?

main.c:
#include <stdio.h>
int max(int a, int b);

int main(void)
{
    int a = 5;
    int b = 6;
    printf("%d\n", max(a,b));
    return 0;
}


max.c:
int max(int a, int b)
{
    return a>b?a:b;
}


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

推荐阅读更多精彩内容

  • 1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧...
    金巴多阅读 1,712评论 0 9
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,419评论 3 44
  • 目的: 通过以下学习,希望能理解指针的概念,理解指针和数组的关系,理解指针的定义,掌握指针的用法。 1. 简述 用...
    读书郞阅读 1,337评论 5 22
  • 15年从武汉迁移到了上海,环境发生了很大的变化,还肝过一段时间的舰娘,所以用「改」字做题。 首先还是用数字来说话:...
    john1king阅读 319评论 0 0
  • 1. 前三十年我和吴先生都不会想到会在一起。这几乎是不可能的事。我们两家祖辈为邻,爷爷那辈相交甚好,话说那个年代人...
    阿夏丽阅读 225评论 3 3