ok
编程不过是把你所想转为代码,用堆栈的角度理解指针
第一章 数组
在C语言中一共有两种聚合类型:数组(array)和结构(structure)。本章介绍一维数组(1.1节)和多维数组(1.2节)的声明与使用。1.3节讨论了C99中的变长数组。本章主要讨论一维数组,因为与多维数组相比,一维数组在C语言中占有更加重要的角色.
1.1声明数组需要指明类型和数量
//数组惯用法逆序输出
#include <stdio.h>
#define N 10
int main(){
int n, a[N];
for (int i = 0; i < n; i ++)
scanf("%d", &a[i]);
for (int i = N - 1; i >= n; i --)
printf("%d", a[i]);
printf("\n");
return 0;
}
下面这个程序用来检查数中是否有出现多于一次的数字。用户输入数后,程序显示信息Repeated digit或No Repeated digit:数28212有一个重复的数字(即2),而9357这样的数则没有。
/*
程序采用布尔值的数组跟踪数中出现的数字。名为digit_seen的数组元素的下标索引从0到9,对应于10个可能的数字。最初的时候,每个数组元素的值都为假。(digit_seen的初始化式为{false},这实际上只初始化了数组的第一个元素。编译器会自动把其他元素初始化为0,0跟false是相等的。)
当给定数n时,程序一次一个地检查n的数字,并且把每次的数字存储在变量digit中,然后用这个数字作为数组digit_seen的下标索引。如果digit_seen[digit]为真,那么表示digit至少在n中出现了两次。另一方面,如果digit_seen[digit]为假,那么表示digit之前未出现过,因此程序会把digit_seen[digit]设置为真并且继续执行。
*/
#include <stdio.h>
#include <stdbool.h>
int main(){
bool digitSeen[10] = {false};
int digit;
long n;
printf("请输入一个数:");
scanf("%ld", &n);
while (n > 0){
digit = n % 10;
if (digitSeen[digit]) break;
digitSeen[digit] = true;
n / 10;
}
if (n > 0) printf("有重复数字");
else printf("没有重复数字");
}
1.2多维数组与常量数组
数组可以有任意维数。可以声明产生一个二维数组(或者按数学上的术语称为矩阵):
就像for循环和一维数组紧密结合一样,嵌套的for循环是处理多维数组的理想选择。例如,思考用作单位矩阵的数组的初始化问题。(数学中,单位矩阵在主对角线上的值为1,而其他地方的值为0,其中主对角线上行、列的索引值是完全相同的。)
#define N 10
double a[N][N];
for (int i = 0; i < N; i ++)
for (int j = 0; j < N; j ++)
if (i == j) a[i][j] = 1.0;
else a[i][j] = 0.0;
无论一维数组还是多维数组,都可以通过在声明的最开始处加上单词const而成为“常量”:程序不应该对声明为const的数组进行修改,编译器能够检测到直接修改某个元素的意图。把数组声明为const有两个主要的好处。它表明程序不会改变数组,这对于以后阅读程序的人可能是有价值的信息。它还有助于编译器发现错误——const会告诉编译器我们不打算修改数组。const类型限定符(➤18.3节)不限于数组,后面将看到,它可以和任何变量一起使用。但是,const在数组声明中特别有用,因为数组经常含有一些在程序执行过程中不会发生改变的参考信息。
const char a[] = {
'a', 'b', 'c', 'd', 'e', 'f', 'g'
};
下面这个程序说明了二维数组和常量数组的用法。程序负责发一副标准纸牌。每张标准纸牌都有一个花色(梅花、方块、红桃或黑桃)和一个等级(2、3、4、5、6、7、8、9、10、J、Q、K或A)。程序需要用户指明手里应该握有几张牌:
Enter number of cards in hand: 5 Your hand: 7c 2s 5d as 2h
不能明显地看出如何编写这样一个程序。如何从一副牌中随机抽取纸牌呢?如何避免两次抽到同一张牌呢?下面将分别处理这两个问题。
为了随机抽取纸牌,可以采用一些C语言的库函数。time函数(➤26.3节,来自于<time.h>)返回当前的时间,用一个数表示。srand函数(➤26.2节,来自于<stdlib.h>)初始化C语言的随机数生成器。通过把time函数的返回值传递给函数srand可以避免程序在每次运行时发同样的牌。rand函数(➤26.2节,来自于< stdlib.h>)在每次调用时会产生一个看似随机的数。通过采用运算符%,可以缩放rand函数的返回值,使其落在0~3(用于表示牌的花色)的范围内,或者是落在0~12(用于表示纸牌的等级)的范围内。
为了避免两次都拿到同一张牌,需要记录已经选择过的牌。为了这个目的,程序将采用一个名为in_hand的二维数组,数组有4行(每行表示一种花色)和13列(每列表示一种等级)。换句话说,数组中的每个元素对应着52张纸牌中的一张。在程序开始时,所有数组元素都为假。每次随机抽取一张纸牌时,将检查数组in_hand中的对应元素为真还是为假。如果为真,那么就需要抽取其他纸牌;如果为假,则把true存储到与这张纸牌相对应的数组元素中,以提醒我们这张纸牌已经抽取过了。
一旦证实纸牌是“新”的(还没有选取过),就需要把牌的等级和花色数值翻译成字符,然后显示出来。为了把纸牌的等级和花色翻译成字符格式,程序将设置两个字符数组(一个用于纸牌的等级,另一个用于纸牌的花色),然后用等级和花色对数组取下标。这两个字符数组在程序执行期间不会发生改变,所以也可以把它们声明为const。
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <stdlib.h>
#define NUM_SUIT 4
#define NUM_RANKS 13
int main(){
bool inHand[NUM_SUIT][NUM_RANKS] = {false};
int numCards, rank, suit;
const char rankCode[] = {'2', '3', '4','5', '6', '7','8', '9',
't', 'j', 'q','k', 'a'};
const char suitCode[] = {'c', 'd', 'h','s'};
srand((unsigned)time(NULL));
printf("输入你手中卡牌的数量");
scanf("%d", &numCards):
printf("你手中的卡牌");
while (numCards > 0){
suit = rand() % NUM_SUIT;
rand = rand() % NUM_RANKS;
if (!inHand[suit][rank]){
inHand[suit][rank] = true;
numCards --;
printf("%c%c", rankCode[rank], suitCode[suit]);
}
}
printf("\n");
return 0;
}
第二章 函数
函数是C程序的构建块。每个函数本质上是一个自带声明和语句的小程序。可以利用函数把程序划分成小块,这样便于人们理解和修改程序,本章讲解函数的调用声明与定义,以及数组参数的传递和递归
下面这个程序判断素数给定数n后,is_prime函数把n除以从2到n的平方根之间的每一个数;只要有一个余数为0,n就不是素数。
#include <stdio.h>
#include <stdbool.h>
bool isPrime(int n){
int divisor;
if (n <= 1) return false;
for (divisor = 2; divisor*divisor <= n; divisor ++){
if (n % divisor == 0) return false;
}
return true;
}
int main(){
int n;
scanf("%d", &n);
if (isPrime(n)) printf("是素数"):
else printf("不是素数");
return 0;
}
递归
#include <stdio.h>
const int N = 1e6 + 10;
int n;
int q[N];
void quick_sort(int q[], int, int);
void swap(int*, int*);
int main(){
scanf("%d", &n);
for (int i = 0; i < n; i ++) scanf("%d", &q[i]);
quick_sort(q, 0, n - 1);
for (int i = 0; i < n; i ++) printf("%d", q[i]);
}
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j){
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j)
quick_sort(q, j + 1, r);
}
void swap(int*a, int*b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
第三章 指针,数组,字符串,指针的高级应用
C语言允许使用赋值运算符进行指针的复制,前提是两个指针具有相同的类型。
指针作为参数
#include <stdio.h>
#define N 10
void max_min(int a[], int n, int*max, int*min);
int main(){
int b[N], big, small;
for (int i = 0; i < n; i ++)
scanf("%d", &a[i]);
max_min(b, N, &big, &small);
printf("%d", big);
printf("%d", small);
return 0;
}
void max_min(int a[], int n, int*max, int*min){
*max = *min = a[0];
for (int i = 0; i < n; i ++){
if (a[i] > *max) *max = a[i];
else if (a[i] < *min) *min = a[i];
}
}
指针作为返回值
#include <stdio.h>
int* max(int* a, int* b){
if (*a > *b) return a;
else return b;
}
int main(){
int a, b;
max(&a, &b);
}
C程序员经常在处理数组元素的语句中组合*(间接寻址)运算符和++运算符。思考一个简单的例子:把值存入一个数组元素中,然后前进到下一个元素。利用数组下标可以这样写:
a[i++] = j;
如果p指向数组元素,那么相应的语句将会是
*p++ = j;
通常情况下,a + i等同于&a[i](两者都表示指向数组a中元素i的指针),并且*(a+i)等价于a[i](两者都表示元素i本身)。换句话说,可以把数组的取下标操作看成是指针算术运算的一种形式。
//惯用法
#include <stdio.h>
#define N 10
int main(){
int a[N], *p, sum = 0;
printf("输入%d个数", N);
//a == &a[0]数组的第一个元素
for (p = a; p < a + N; p ++)
sum += *p;
scanf("%d", &N);
for (p = N - 1; p >= a; p --)
printf("%d", *p);
printf("\n");
return 0;
}
用指针作为数组名
#define N 10
int a[N], sum = 0, *p = a;
for (int i = 0; i < N; i++)
sum += p[i];
//编译器把p[i]看作*(p+i)
字符串字面量是作为数组来存储的,那么编译器会把它看作是char *类型的指针,也是指向第一个元素的地址printf函数和scanf函数都接收char *类型的值作为它们的第一个参数。
只要保证字符串是以空字符结尾的,任何一维的字符数组都可以用来存储字符串。这种方法很简单,但使用起来有很大难度。有时很难辨别是否把字符数组作为字符串来使用。如果编写自己的字符串处理函数,请千万注意要正确地处理空字符。而且,要确定字符串长度没有比逐个字符地搜索空字符更快捷的方法了。
假设需要用一个变量来存储最多有80个字符的字符串。由于字符串在末尾处需要有空字符,我们把变量声明为含有81个字符的数组:
字符串的长度取决于空字符的位置,而不是取决于用于存放字符串的字符数组的长度
字符串变量的声明中可以省略它的长度。这种情况下,编译器会自动计算长度:
#define STR_LEN 11
char str[STR_LEN + 1];
char str2[] = "hello world";
字符数组与字符指针
char s1[] = "hello world";
char* s2 = "hello world";
在声明为数组时,就像任意数组元素一样,可以修改存储在date中的字符。在声明为指针时,date指向字符串字面量,在13.1节我们已经看到字符串字面量是不可以修改的。·在声明为数组时,date是数组名。在声明为指针时,date是变量,这个变量可以在程序执行期间指向其他字符串。如果希望可以修改字符串,那么就要建立字符数组来存储字符串,声明指针变量就不够的
在使用p作为字符串之前,必须把p指向字符数组。一种可能是把p指向已经存在的字符串变量:现在p指向了str的第一个字符,所以可以把p作为字符串使用了。另一种可能是让p指向一个动态分配的字符串(➤17.2节)。
char str[STR_LEN + 1], *p;
p = str;
字符串的读和写
使用printf函数或puts函数来写字符串是很容易的。读字符串却有点麻烦,主要是因为输入的字符串可能比用来存储它的字符串变量长。为了一次性读入字符串,可以使用scanf函数或gets函数,也可以每次读入一个字符。
用printf函数和puts函数写字符串
/*
printf函数会逐个写字符串中的字符,直到遇到空字符才停止。(如果空字符丢失,printf函数会越过字符串 的末尾继续写,直到最终在内存的某个地方找到空字符为止。).
puts函数总会添加一个额外的换行符,从而前进到下一个输出行的开始处。
*/
#include <stdio.h>
char str[] = "nothing to say";
printf("%s\n", str);
puts(str);
用scanf函数和gets函数读字符串
gets函数把读入的字符放到数组中,然后存储一个空字符。然而,在其他方面gets函数有些不同于scanf函数。·gets函数不会在开始读字符串之前跳过空白字符(scanf函数会跳过)。gets函数会持续读入直到找到换行符才停止(scanf函数会在任意空白字符处停止)。此外,gets函数会忽略掉换行符,不会把它存储到数组中,用空字符代替换行符。在把字符读入数组时,scanf函数和gets函数都无法检测数组何时被填满。因此,它们存储字符时可能越过数组的边界,这会导致未定义的行为。通过用转换说明[插图]s代替%s可以使scanf函数更安全。这里的数字[插图]指出可以存储的最多字符数。可惜的是,gets函数天生就是不安全的,fgets函数(➤22.5节)则是一种好得多的选择。
逐个字符读字符串
因为对许多程序而言,scanf函数和gets函数都有风险且不够灵活,C程序员经常会自己编写输入函数。通过每次一个字符的方式来读入字符串,这类函数可以提供比标准输入函数更大程度的控制。如果决定设计自己的输入函数,那么就需要考虑下面这些问题。·在开始存储字符串之前,函数应该跳过空白字符吗?·什么字符会导致函数停止读取:换行符、任意空白字符还是其他某种字符?需要存储这类字符还是忽略掉?·如果输入的字符串太长以致无法存储,那么函数应该做些什么:忽略额外的字符还是把它们留给下一次输入操作?
假定我们所需要的函数不会跳过空白字符,在第一个换行符(不存储到字符串中)处停止读取,并且忽略额外的字符。函数将有如下原型:
int readLine(char str[], int n);
read_line函数主要由一个循环构成。只要str中还有空间,此循环就会调用getchar函数(➤7.3节)逐个读入字符并把它们存储到str中。在读入换行符时循环终止注意,ch的类型为int而不是char,这是因为getchar把它读取的字符作为int类型的值返回,
返回之前,read_line函数在字符串的末尾放置一个空字符。scanf和gets等标准函数会自动在输入字符串的末尾放置一个空字符;然而,如果要自己写输入函数,必须手工加上空字符。
int readLine(char str[], int n){
int ch, i;
while (ch = getchar() != '\n')
if (i < n)
str[i++] = ch;
str[i] = '\0';
return i;
}
访问字符串中的字符
字符串是以数组的方式存储的,因此可以使用下标来访问字符串中的字符。例如,为了对字符串s中的每个字符进行处理,可以设定一个循环来对计数器i进行自增操作,并通过表达式s[i]来选择字符。假定需要一个函数来统计字符串中空格的数量。利用数组取下标操作可以写出如下函数:
在s的声明中加上const表明count_spaces函数不会改变数组。如果s不是字符串,count_spaces将需要第2个参数来指明数组的长度。然而,因为s是字符串,所以count_spaces可以通过测试空字符来定位s的末尾。许多C程序员不会像例子中那样编写count_spaces函数,他们更愿意使用指针来跟踪字符串中的当前位置。就像在12.2节中见到的那样,这种方法对于处理数组来说一直有效,但在处理字符串方面尤其方便。下面用指针算术运算代替数组取下标来重新编写count_spaces函数。这次不再需要变量i,而是利用s自身来跟踪字符串中的位置。通过对s反复进行自增操作,count_spaces函数可以逐个访问字符串中的字符。下面是count_spaces函数的新版本:
注意,const没有阻止count_spaces函数对s的修改,它的作用是阻止函数改变s所指向的字符。而且,因为s是传递给count_spaces函数的指针的副本,所以对s进行自增操作不会影响原始的指针。
int count_spaces(const char *s)
{
int count = 0;
for (; *s != '\0'; s++)
if (*s == ' ')
count++;
return count;
}
使用C语言的字符串库
字符串惯用法
字符串数组(二维)
现在来看一个在使用字符串时经常遇到的问题:存储字符串数组的最佳方式是什么?最明显的解决方案是创建二维的字符数组,然后按照每行一个字符串的方式把字符串存储到数组中。考虑下面的例子:
算法(解决问题的步骤)练习题
问题步骤写清楚,搞清楚每个变量数据结构的含义,解决实际问题
链接语语法
树就是特殊的图,算法(思想)相似
经典算法《算法第四版》

