C 和指针 1 至 4 章笔记
第 1 章 快速上手
主要通过讲解一个例子,来让读者对 C
有一个大概印象。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20
#define MAX_INPUT 1000
int read_column_numbers(int columns[], int max);
void rearrange(char *output, char const *input, int n_columns, int const columns[]);
int main(void)
{
int n_columns;
int columns[MAX_COLS];
char input[MAX_INPUT];
char output[MAX_INPUT];
n_columns = read_column_numbers(columns, MAX_COLS);
//while (gets(input) != NULL) {
while (fgets(input, MAX_INPUT, stdin) != NULL) {
printf("Original input : %s\n", input);
rearrange(output, input, n_columns, columns);
printf("Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
int read_column_numbers(int columns[], int max)
{
int num=0;
int ch;
while (num < max && scanf("%d", &columns[num]) == 1 && columns[num] >= 0) {
if (num > 0 && columns[num] >= 0 && columns[num]<columns[num-1]) {
puts("invalid input. columns[num] must gather than columns[num-1]");
exit(EXIT_FAILURE);
}
num ++;
}
printf("columns[%d] = %d\n",num, columns[num]);
if (num % 2 != 0) {
puts("Last column number is not paired.");
exit(EXIT_FAILURE);
}
while ((ch = getchar()) != EOF && ch != '\n')
;
return num;
}
void rearrange(char *output, char const *input, int n_columns, int const columns[])
{
int col;
int output_col;
int len;
len = strlen(input);
output_col = 0;
for (col=0; col<n_columns; col+=2) {
int nchars = columns[col+1] - columns[col] + 1;
if (columns[col] >= len || output_col == MAX_INPUT - 1)
break;
if (output_col + nchars > MAX_INPUT - 1)
nchars = MAX_INPUT - output_col - 1;
strncpy(output + output_col, input + columns[col], nchars);
output_col += nchars;
}
output[output_col] = '\0';
}
这个程序主要作用是循环从标准输入流中读取成对的标号,并以负数结尾,紧接着再循环读取标准输入流中的字符并按照之前成对的标号输出到屏幕上。
这里编译时,如果是按照书中的 gets(input) != NULL
那么会报
/tmp/cc7GVA4V.o: In function `main':
input.c:(.text+0x85): warning: the `gets' function is dangerous and should not be used.
因为 gets
函数对输入的字符没有长度限制,很有可能造成溢出,这里换成 fgets(input, MAX_INPUT, stdin) != NULL
就很好的规避了溢出问题。同时
while ((ch = getchar()) != EOF && ch != '\n')
;
主要是消除非法输入。因为 scanf
函数对输入值进行转换时,它只读取需要的字符,输入行包含的最后一个值的剩余部分仍会留在那里,等待被读取。比如输入 1 3 5 7 -1
然后换行,实际上 scanf
只读取 1 3 5 7 -1
,并把换行留在 stdin
里,这时就需要清除掉。因为不清除掉,会出现以下情况
1 3 5 7 -1
Original input :
Rearranged line:
也就是换行符被下面的 fgets(input, MAX_INPUT, stdin)
接收并输出到屏幕了。这里也写成 fflush(stdin)
清空输入流。这个程序主要逻辑在 rearrange
方法里。
columns[0] = 1;
columns[1] = 3;
columns[2] = 5;
columns[3] = 7;
columns[4] = -1;
for (col=0; col<n_columns; col+=2)
成对循环成对复制 input
的字符串到 output
,
int nchars = columns[col+1] - columns[col] + 1;
/*
columns[0+1] - columns[0] + 1;
columns[1] - columns[0] + 1;
3 -1 + 1
3
*/
并判断是否已到了是否超出了 input
数组,没有的话那么就使用库函数 strncpy
复制 input
的 nchars
长度到 output。
这个程序还有个问题,read_column_numbers
里输入的下标除了最后一个应该是依次递增的,而不是乱序的
a < b < c < d
因此需要在 read_column_numbers
函数 while
里添加判断
if (num > 0 && columns[num] >= 0 && columns[num]<columns[num-1]) {
puts("invalid input. columns[num] must gather than columns[num-1]");
exit(EXIT_FAILURE);
}
第 2 章 基本概念
主要讲述了环境、此法规则、程序风格。个人感觉就下面这个图重要,其他的就是理解并且编程时遵循其规则就行。
另外一个就是注意下三字母词序列
如何输出以下字符串
"Blunder??!??"
#include <stdio.h>
int main(void)
{
puts("\"Blunder\?\?!\?\?\"");
return 0;
}
第 3 章 数据
这章首先讲述了基本的数据类型,整型、浮点型、指针和聚合类型(如数组和结构);然后介绍了基本声明
int i,j;
int values[20];
char *msg="hello world";
这里需要关注的是,编译器并不检查程序对数组下标的引用是否越界,同时对于指针,需要理解的是
// 把 `msg` 声明为一个指向字符的指针,并用字符串常量中第 `1` 个字符的地址对该指针进行初始化
char *msg="hello world";
相当于
char *msg;
msg="hello world";
而对于函数,如果没有返回值,那么编译器默认返回 整型
f(int x)
{
return x * x;
}
接着说了下 typedef
,同时对比了下宏的区别,
#define ptr_to_char char *
ptr_to_char a,b,c;
预处理器执行后,变成
char *a,b,c;
可以看到 b
和 c
并没有如期的声明为字符型指针
typedef char *ptr_to_char;
ptr_to_char a,b,c;
上面的用法就说明了 typedef
意义,允许为各种数据类型定义新名字;再着就是常量了,使用 const
关键字来声明常量
// pi是一个普通的指向整型变量的指针
int *pi;
// pi是一个普通的指向整型变量的指针,可以修改指针的值,但不能修改它所指向的值
int const *pi;
// pi是一个普通的指向整型变量的指针,可以修改它所指向的值,但不能修改指针的值
int * const pi;
// pi是一个普通的指向整型变量的指针,无论是指针本身还是其指向的值都不能修改
int const * const pi;
这章的重点基本在最后几节,分别介绍了作用域、链接属性、存储类型和 static
- 作用域:代码块作用域、文件作用域、原型作用域、函数作用域
- 链接属性,这个可以参照上面
第 2 章 基本概念
编译过程的那个图
external(外部文件可见)、internal(此文件可见)、none(默认)
extern -> external、static -> internal,只对缺省的链接属性为 external 的声明有效
-
存储类型:普通内存、运行时堆栈、硬件寄存器(register)
- 静态变量内存:存储凡是在任何代码块之外声明的变量,在程序运行之前创建,在程序的整个执行中始终存在。
-
static
在不同的上下文中有不同的意义。- 用于函数定义时,或用于代码块之外时,用于改变标识符的链接属性,从
external
改为internal
- 用于代码块内部声明时,用于改变变量的存储类型,从自动变量修改为静态变量
- 用于函数定义时,或用于代码块之外时,用于改变标识符的链接属性,从
一幅图说明四者的关系
第 4 章 语句
这章很轻松,和其他语言差不多,依次介绍了 空语句、表达式语句、代码块、if
语句、 while
语句、for
语句、 do
语句、switch
语句、goto
语句。