标签(空格分隔): C 编程
"根据我们的经验,C语言是一种令人愉快的,具有很强表达能力的通用的语言,适合于编写各种程序。它容易学习,并且随着使用经验的增加,使用者会起来越感到得心应手。"
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岁。
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在函数外声明.
指针(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