0%

definition

Affine Transformation

Linear Transformation + Translate
线性变换 (Linear Transformation): 你可以对这个网格进行 旋转 (Rotate)、缩放 (Scale)、切变 (Shear)。

  1. 如果可以邀请世界上任何一个人,你希望谁作为晚餐的客人?

  2. 你想出名吗?以什么方式?

  3. 在打电话之前,你会排练要说的内容吗?为什么?

  4. 对你来说,怎样的一天才算“完美”?

  5. 你上次唱歌给自己听是什么时候?唱歌给别人听呢?

  6. 如果你能活到90岁,并在生命的最后60年里保持30岁的头脑或身体,你会选择哪一个?

  7. 你是否有预感自己会怎么死?

  8. 说出你和你的配对者似乎有的三个共同点。

  9. 你对生活中最感激的事情是什么?

  10. 如果你可以改变你成长过程中的任何一点,你会改变什么?

  11. 用四分钟时间尽可能详细地告诉你的配对者你的生活故事。

  12. 如果你明天醒来说可以拥有任何一种特质或能力,那会是什么?

  13. 如果水晶球可以告诉你关于你自己、你的生活、未来或是其他任何事情的真相,你最想知道什么?

  14. 是否有什么事情你梦想已久,一直没有去做?为什么没有去做?

  15. 你一生中最大的成就是什么?

  16. 你在友谊中最看重什么?

  17. 你最珍贵的记忆是什么?

  18. 你最糟糕的记忆是什么?

  19. 如果你知道一年后你将会突然死去,你会改变现在的生活方式吗?为什么?

  20. 友谊对你意味着什么?

  21. 爱和感情在你的生活中扮演什么角色?

  22. 交替分享你认为对方的正面特质。总共分享五项。

  23. 你的家庭有多亲密和温暖?你觉得自己童年比大多数人快乐吗?

  24. 你对与母亲的关系感觉如何?

  25. 各自说出三个真实的“我们”。例如:“我们都在这个房间里感觉……”。

  26. 完成句子:“我希望我有一个可以分享……的人”

  27. 如果你要给你的室友或者朋友写信,分享他们需要知道的重要事情。

  28. 告诉你的室友或者朋友你感激他们什么;这次要非常诚实,说出你可能不会对刚认识的人说的话。

  29. 鼓励你的室友分享生活中一个尴尬的时刻。

  30. 你上次在别人面前哭是什么时候?自己一个人哭呢?

  31. 告诉你的室友或者朋友你已经喜欢他们什么。

  32. 有什么事是你认为不能开玩笑的?

  33. 如果你今晚就要去世,没有机会与任何人沟通,你最遗憾没有告诉某人的事情是什么?为什么还没有告诉他们?

  34. 你的房子着火了,里面所有你拥有的东西都在里面。在救出你所爱的人和宠物之后,你有时间安全地冲回去再救一样东西。那会是什么?为什么?

  35. 在你的家人中,谁的去世会让你感到最痛苦?为什么?

  36. 分别问另一个人问题,询问你希望他们告诉你一些事情,看看他们会如何回答。另外,请你的室友反馈出你对所选问题的感觉。

site:可以限制你搜索范围的域名.
site:.gov

inurl:用于搜索网页上包含的URL,这个语法对寻找网页上的搜索,帮助之类的很有用.
inurl:qq.com

intext: 只搜索网页部分中包含的文字(也就是忽略了标题、URL等的文字)
intext:色色

intitle: 查包含关键词的页面,一般用于社工别人的webshell密码
intitle:edu

filetype:搜索文件的后缀或者扩展名
filetype:txt
filetype:pdf

link: 可以得到一个所有包含了某个指定URL的页面列表.
link:weixin.com

related: 搜索相关网站
related:qq.com

define: 搜索词语的解释, 可以搜索名词及名人
define:锻炼
天天:define
刘翔 define
吴亦凡 define

翻译
I don’t know the means of the word 翻译
翻译 apple

allinurl: 搜索网址中包含以下所有词
allinurl:渗透 安全

allintext: 指定范围搜索(正文出现关键词)
allintext:渗透

精确搜索: 给关键词加引号
“渗透”
‘渗透’

- (减号): 从搜索结果中排除特定字词,
从要排除的字词前加上’-‘
linux常用命令

#会显示所有搜索结果
linux常用命令 -CSDN
#会屏蔽掉搜索结果中的CSDN相关内容

+ (加号): 只显示+后面的内容
linux常用命令 +CSDN

#只显示搜索结果中CSDN的相关内容
linux常用命令 +博客园
#只显示搜索结果中博客园的相关内容

cache: 查看网站的Google缓存版本,
在相对应网址前加上”cache:”
cache:qq.com

搜索#标签: 在字词前加上’#’

#body
#安全

搜索特定价格: 在数字前加上$
$400

搜索社交媒体: 在用于搜索社交媒体的字词前加上 @
@qq
@twitter

camera: 在某个数字范围内执行搜索
在两个数字之间加上…
camera $50…$100
camera 9999…100000

inanchor or allinanchor: 搜索范围限制在页面的链接锚点描述文本进行搜索

AROUND: 查找两个字或词在不超过指定的距离
渗透 AROUND(5) 安全

OR: 组合搜索.默认搜索,中间空格是与(AND),而使用OR,可以达到或的效果。
在各个搜索查询之间加上“OR”
marathon OR race
渗透 OR 安全

|代表或: login|admin|manget

不常用语法:

Phonebook: 搜索电话列表

Rphonebook: 搜索住宅电话列表

Bphonebook: 商业电话列表

Author: 搜索Google中新闻组帖子的作者

Group: 搜索Google标题

Inanchor: 在链接文本中查找文本

Masgid: 通过消息id来查找谷歌的帖子

Daterange: 查找某个特定日期范围内发布的网页

Insubject: 搜索Googlegroup的主题行

Stocks: 搜索股票信息

Info: 显示Google的摘要信息

Define: 显示某术语的定义

Numrang: 搜索数字需要两个参数一个最小数,一个最大数,用破折号隔开

~ 同意词即类似的词

. 单一的通配符

* 通配符,可代表多个字母

“ ” 精确查询匹配

布尔操作:
and 与
or 或
not 不

叠加使用: 组合使用上述所有方法,自行测试

注意事项:
1、所有的冒号都是半角,也就是英文的冒号,而不是中文的冒号
2、空格很重要,关键词之间一定要加空格

Google Hacking数据库: 汇集了非常多的有价值的搜索语句

https://www.exploit-db.com/google-hacking-database

查找后台地址:
site:xxx.com intext:管理|后台|登录|登陆|用户名|密码|系统|账号|login|system|admin
site:xxx.com inurl:login|admin|manage|member|admin_login|login_admin|system|login|user|main|cms
inurl:edu.cn intitle:管理
site:xxx.com inurl:login|inurl:admin|inurl:admin_login|inurl:system
site:xxx.com intitle:管理|后台|后台管理|登录|登陆
inurl:login|admin|admin_login|login_admin|system|user
site:xxx.com

查找文本内容:

site:xxx.com intext:管理|后台|登陆|用户名|密码|验证码|系统|帐号|admin|login|sys|managetem|password|username

查找可注入点:site:xxx.com inurl:aspx|jsp|php|asp
site:xxx.com inurl:php?id=

查找上传漏洞:site:xxx.com inurl:file|load|editor|files|

找eweb编辑器:site:xxx.com inurl:ewebeditor|editor|uploadfile|eweb|edit

存在的数据库:site:域名 filetype:mdb|asp|#
site:xxx.com filetype:mdb
site:xxx.com filetype:数据库格式

查看脚本类型:site:xxx.com filetype:asp/aspx/php/jsp
site:xxx.com filetype:php

查找目录遍历漏洞: site:xxx.com intitle:index of

社工信息: site:xxx.com intitle:账号|密码|工号|学号|身份证

搜索各类开源的网站上面的信息: site:github.com intext:xiaodi8.com

迂回策略入侵:inurl:cms/data/templates/images/index/

        实战演示

01 首先用google搜索这个站点的基本情况

site:xxx.com
从搜索结果中找到了几处该站点的域名

1
2
3
http://aaa.xxx.com
http://bbb.xxx.com
http://ccc.xxx.com

然后查看这几个域名的ip,并确认是否存在CDN
发现不存在CDN服务,并且子域名ip有的也不同

02 搜索该站点的后台地址

site:xxx.com intext:管理
site:xxx.com inurl:login
site:xxx.com inurl:admin
site:xxx.com intitle:管理
等其他方式也可以,自行组合即可

最终获取到了多个后台地址

03 查看服务器脚本语言

site:aaa.xxx.com filetype:asp
site:aaa.xxx.com filetype:php
site:aaa.xxx.com filetype:aspx
site:aaa.xxx.com filetype:jsp
等其他方式也可判断

最终获取到了基本的搭建组合后

04 尝试获取漏洞

site:aaa.xxx.com intext:ftp://.
site:bbb.xxx.com inurl:file
site:ccc.xxx.com inurl:load
等其他漏洞的关键字自行搜索

得到地址后就可以进行下一步渗透了

05 获取人员类相关信息

获取二级域名
site:xxx.com
获取邮箱地址
site:xxx.com intext:*@xxx.com
获取电话信息
site:xxx.com intext:电话

在搜集到信息后,可以生成社工字典,使用工具进行跑一遍

迭代

for 适合在预先知道迭代次数时使用

1
2
3
for (int i = 1; i <= n; i++) {
res += i;
}

此求和函数的操作数量与输入数据大小 成正比,或者说成“线性关系”

while 循环比 for 循环的自由度更高。在 while 循环中,我们可以自由地设计条件变量的初始化和更新步骤。

for(for( ))
每一次嵌套都是一次“升维”,将会使时间复杂度提高至“立方关系”“四次方关系”,以此类推。

递归

递归(recursion)是一种算法策略,通过函数调用自身来解决问题。它主要包含两个阶段。

  1. 递:程序不断深入地调用自身,通常传入更小或更简化的参数,直到达到“终止条件”。
  2. 归:触发“终止条件”后,程序从最深层的递归函数开始逐层返回,汇聚每一层的结果。

虽然从计算角度看,迭代与递归可以得到相同的结果,但它们代表了两种完全不同的思考和解决问题的范式。

  • 迭代:“自下而上”地解决问题。从最基础的步骤开始,然后不断重复或累加这些步骤,直到任务完成。
  • 递归:“自上而下”地解决问题。将原问题分解为更小的子问题,这些子问题和原问题具有相同的形式。接下来将子问题继续分解为更小的子问题,直到基本情况时停止 (基本情况的解是已知的)

尾递归

有趣的是,如果函数在返回前的最后一步才进行递归调用,则该函数可以被编译器或解释器优化,使其在空间效率上与迭代相当。这种情况被称为尾递归(tail recursion)。

  • 普通递归:当函数返回到上一层级的函数后,需要继续执行代码,因此系统需要保存上一层调用的上下文。求和操作是在“归”的过程中执行的,每层返回后都要再执行一次求和操作。
  • 尾递归:递归调用是函数返回前的最后一个操作,这意味着函数返回到上一层级后,无须继续执行其他操作,因此系统无须保存上一层函数的上下文。求和操作是在“递”的过程中执行的,“归”的过程只需层层返回。
迭代 递归
实现方式 循环结构 函数调用自身
时间效率 效率通常较高,无函数调用开销 每次函数调用都会产生开销
内存使用 通常使用固定大小的内存空间 累积函数调用可能使用大量的栈帧空间
适用问题 适用于简单循环任务,代码直观、可读性好 适用于子问题分解,如树、图、分治、回溯等,代码结构简洁、清晰

经典习题

  1. 编写一个程序,要求用户输入一个华氏温度。程序应读取double类型的值作为温度值,并把该值作为参数传递给一个用户自定义的函数Temperatures()。该函数计算摄氏温度和开氏温度,并以小数点后面两位数字的精度显示3种温度。要使用不同的温标来表示这3个温度值。下面是华氏温度转摄氏温度的公式:
    摄氏温度 = 5.0 / 9.0 * (华氏温度 - 32.0)
    开氏温标常用于科学研究,0表示绝对零,代表最低的温度。下面是摄氏温度转开氏温度的公式:
    开氏温度 = 摄氏温度 + 273.16
    Temperatures()函数中用const创建温度转换中使用的变量。在main()函数中使用一个循环让用户重复输入温度,当用户输入 q 或其他非数字时,循环结束。scanf()函数返回读取数据的数量,所以如果读取数字则返回1,如果读取q则不返回1。可以使用==运算符将scanf()的返回值和1作比较,测试两值是否相等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

void Temperatures(double fahrenheit);

int main() {
double fahrenheit;
printf("This program converts fahrenheit to celsius and kelvin.\n");
printf("Enter a temperature in dgrees fahrenheit (q to quit):");
while(scanf("%lf", &fahrenheit) == 1) // continue executing loop if user enters valid number
{
Temperatures(fahrenheit); // convert fahrenheit to celsius and kelvin

//prompt for new input
printf("Enter a temperatures in degrees fahrenheit (q to quit):");
}
printf("Bye!\n");
}

void Temperatures(double fahrenheit){
const double FAHR_TO_CEL_SCALE = 5.0 / 9.0;
const double FAHR_TO_OFFSET = -32.0;
const double CEL_TO_KEL_OFFSET = 273.16;

double celsius = (fahrenheit + FAHR_TO_OFFSET) * FAHR_TO_CEL_SCALE;
double kelvin = celsius + CEL_TO_KEL_OFFSET;

printf("%.2f degrees fahrenheit is %.2f degrees celsius or %.2f degrees kelvin.\n",
fahrenheit, celsius, kelvin);

}
  1. 编写一个程序,创建一个包含26个元素的数组,并在其中储存26个小写字母。然后打印数组的所有内容。
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#define SIZE 26
int main(void){
char alphabet[SIZE];
for (char index='a';index<'a' + SIZE;index++)
alphabet[index-'a']= index;
for (int index = 0; index < SIZE; index++)
printf("%c ", alphabet[index]);
return 0;
};

  1. 编写一个程序,提示用户输入大写字母。使用嵌套循环以下面金字塔型的格式打印字母:
    A
    ABA
    ABCBA
    ABCDCBA
    ABCDEDCBA
    打印这样的图形,要根据用户输入的字母来决定。例如,上面的图形是在用户输入E后的打印结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    #include <stdio.h>

    void print_spaces(unsigned int n);

    int main() {
    char uppercase_letter;
    char c1, c2;

    do // get uppercase letter from user
    {
    printf("Enter an uppercases letter:");
    scanf("%c", &uppercase_letter);
    }while (uppercase_letter < 'A'|| 'Z' < uppercase_letter);

    for(c1 = 'A'; c1 <= uppercase_letter; c1++)
    {
    //print opening spaces
    print_spaces(uppercase_letter -c1);

    //print letter
    //ascending
    for (c2 = 'A'; c2 < c1; c2++){
    printf("%c", c2);
    }
    // descending
    for (; 'A' <= c2; c2--)
    {
    printf("%c", c2);
    }

    //print closing spaces
    print_spaces(uppercase_letter - c1);
    printf("\n");
    }
    return 0;
    }

    void print_spaces(unsigned int n){
    for (int i = 0; i < n; i++)
    {
    printf(" ");
    }
    }
  2. 考虑下面两个无限序列:
    1.0 + 1.0/2.0 + 1.0/3.0 + 1.0/4.0 + …
    1.0 - 1.0/2.0 + 1.0/3.0 - 1.0/4.0 + …
    编写一个程序计算这两个无限序列的总和,直到到达某次数。提示:奇数个-1 相乘得-1,偶数个-1相乘得1。让用户交互地输入指定的次数,当用户输入0或负值时结束输入。查看运行100项、1000项、10000项后的总和,是否发现每个序列都收敛于某值?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

int main() {
long int limit;
float sign = 1.0f;
float series1 = 0, series2 = 0;

printf("Enter a number of terms to sum:");
scanf("%ld", &limit);

for (double i = 1; i <= limit; i++)
{
series1 += 1.0f/i;
series2 += (1.0f/i) * sign;
sign = -sign; // toggle sign
}

printf("The %ldth partial sum for series 1 is: %.5f\n", limit, series1);
printf("The %ldth partial sum for series 2 is: %.5f\n", limit, series2);

//Answer: Series 1 has no limit. Series 2 appears to be bouned above
return 0;
}
  1. 编写一个程序,创建两个包含8个元素的double类型数组,使用循环提示用户为第一个数组输入8 个值。第二个数组元素的值设置为第一个数组对应元素的累积之和。例如,第二个数组的第 4个元素的值是第一个数组前4个元素之和,第二个数组的第5个元素的值是第一个数组前5个元素之和(用嵌套循环可以完成,但是利用第二个数组的第5个元素是第二个数组的第4个元素与第一个数组的第5个元素之和,只用一个循环就能完成任务,不需要使用嵌套循环)。最后,使用循环显示两个数组的内容,第一个数组显示成一行,第二个数组显示在第一个数组的下一行,而且每个元素都与第一个数组各元素相对应。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

int main() {
int array[8], cumulative_sum[8];
int sum = 0;

printf("Enter 8 integers:\n");
for (int i = 0; i < 8; i++)
{
scanf("%d",&array[i]);
sum += array[i];
cumulative_sum[i] = sum;
}
//display loops
printf("Integers: ");
for (int i = 0; i < 8; i++)
{
printf("%6d ", array[i]);
}
printf("\n");
printf("Cumulative sum:");
for (int i = 0; i < 8; i++)
{
printf("%6d ", cumulative_sum[i]);
}
printf("\n");
return 0;
}
  1. 编写一个程序,读取一行输入,然后把输入的内容倒序打印出来。可以把输入储存在char类型的数组中,假设每行字符不超过255。回忆一下,根据%c转换说明,scanf()函数一次只能从输入中读取一个字符,而且在用户按下Enter键时scanf()函数会生成一个换行字符(\n)。
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
char line[255];
int i = 0; // array
printf("Enter a line to reerse:\n");
while (scanf("%c", &line[i]), line[i] != '\n')
i++;
for (; 0 <= i; i--) //previous loop leaves i in right postion
printf("%c", line[i]);
return 0;
}
  • line[i] != ‘\n’ 的条件判断使得循环在用户输入的行中包含换行符之前继续执行。当用户按下 Enter 键时,输入的换行符会被检测到,并导致循环结束。
  1. 编写一个统计单词数量的程序(即,该程序读取并报告单词的数量)。该程序还可以计算字符数和行数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// wordcnt.c -- 统计字符数、单词数、行数
#include <stdio.h>
#include <ctype.h> //为isspace()函数提供原型
#include <stdbool.h> //为bool、true、false提供定义
#define STOP '|'
int main() {
char c; //读入字符
char prev; //读入的前一个字符
long n_chars = 0L; //字符数
int n_lines = 0; //行数
int n_words = 0; //单词数
int p_lines = 0; //不完整的行数
bool inword = false; //如果c在单词中,inword等于true
printf("Enter text to be analyzed (| to terminate):\n");
prev = '\n'; //用于识别完整的行
while ((c = getchar()) != STOP)
{
n_chars++; //统计字符
if(c == '\n')
n_lines++; //统计行
if (!isspace(c) && !inword)
{
inword = true; //开始一个新的单词
n_words++; //统计单词
}
if(isspace(c) && inword)
inword = false; //打到单词的末尾
prev = c; //保存字符的值
}
if(prev != '\n')
p_lines = 1;
printf("characters = %ld, words = %d, lines = %d,",
n_chars, n_words, n_lines);
printf("partial lines = %d\n", p_lines);
return 0;
}
  1. 编写程序读取输入,读到#停止,报告ei出现的次数。
  • 注意
    该程序要记录前一个字符和当前字符。用“Receive your eieio award”这样的输入来测试。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdbool.h>
#include <ctype.h>

#define STOP '#'

int main(void)
{
char ch;
unsigned int ei_count = 0;
bool e_flag = false;

printf("This program reads input and counts the number of times the\n"
"sequence 'ei' occurs (case insensitive).\n");
printf("Enter input (%c to stop):\n", STOP);

while ((ch = getchar()) != STOP)
{
ch = tolower(ch);
if (ch == 'e')
e_flag = true;
else if (ch == 'i')
{
if (e_flag)
ei_count++;
e_flag = false;
}
else
e_flag = false;

}

printf("The sequence 'ei' occurs %u times.\n", ei_count);

return 0;
}
  1. 编写一个程序,提示用户输入一周工作的小时数,然后打印工资总额、税金和净收入。做如下假设:
    a.加班(超过40小时) = 1.5倍的时间
    b.税率: 前300美元为15%
      续150美元为20%
      余下的为25%
    

让程序可以给出一个供选择的工资等级菜单。使用switch完成工资等级选择。运行程序后,显示的菜单应该类似这样:

1
2
3
4
5
6
*****************************************************************
Enter the number corresponding to the desired pay rate or action:
1) $8.75/hr 2) $9.33/hr
2) $10.00/hr 4) $11.20/hr
3) quit
*****************************************************************

如果选择 1~4 其中的一个数字,程序应该询问用户工作的小时数。程序要通过循环运行,除非用户输入 5。如果输入 1~5 以外的数字,程序应提醒用户输入正确的选项,然后再重复显示菜单提示用户输入。使用#define创建符号常量表示各工资等级和税率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include <stdio.h>
#include <stdbool.h>

#define RATE_1 8.75
#define RATE_2 9.33
#define RATE_3 10.00
#define RATE_4 11.20

#define OVERTIME_HOURS 40.0
#define OVERTIME_MULTIPLIER 1.5
#define TAX_RATE_1 0.15
#define TAX_BRACKET_1 300.0
#define TAX_RATE_2 0.20
#define TAX_BRACKET_2 450.0
#define TAX_RATE_3 0.25

void flush_input_buffer(void);
float calculate_gross_pay(float hours, float rate);
float calulate_taxes(float gross_pay);

int main() {
bool exit_flag = false;
int rate_option;
double rate, hours, gross_pay, taxes,net_pay;

while (1) {
printf("*****************************************************************\n");
printf("Enter the number corresponding to the desired pay rate or action:\n");
printf("1) $8.75/hr 2) $9.33/hr\n");
printf("3) $10.00/hr 4) $11.20/hr\n");
printf("5) quit\n");
printf("*****************************************************************\n");

scanf("%d", &rate_option);
switch (rate_option)
{
case 1:
rate = RATE_1;
break;
case 2:
rate = RATE_2;
break;
case 3:
rate = RATE_3;
break;
case 4:
rate = RATE_4;
break;
case 5:
exit_flag = true;
break;
default: //invalid input
flush_input_buffer();
printf("please enter an integer between 1 and 5.\n");
continue; // repeat main program loop
}

if (exit_flag)
break; //exit program

printf("Enter the number of hours worked in a week:");
while (scanf("%lf", &hours) != 1 || hours <= 0)
{
flush_input_buffer();
printf("please enter a positive number.\n");
printf("Enter number of hours worked in a week:");
}

gross_pay = calculate_gross_pay(hours, rate);
taxes = calulate_taxes(gross_pay);
net_pay = gross_pay - taxes;

printf("Gross pay: $%.2f\n", gross_pay);
printf("Taxes pay: $%.2f\n", taxes);
printf("Net pay: $%.2f\n", net_pay);

}
printf("Bye.\n");
return 0;
}

void flush_input_buffer(void)
{
while (getchar() != '\n')
;
}

float calculate_gross_pay(float hours, float rate)
{
if (hours > OVERTIME_HOURS)
return OVERTIME_HOURS * rate + (hours - OVERTIME_HOURS) * rate * OVERTIME_MULTIPLIER;
else
return hours * rate;
}

float calulate_taxes(float gross_pay)
{
if (gross_pay > TAX_BRACKET_2)
return TAX_RATE_3 * (gross_pay - TAX_BRACKET_2) + TAX_RATE_2 * (TAX_BRACKET_2 - TAX_BRACKET_1) + TAX_RATE_1 * TAX_BRACKET_1;
else if (gross_pay > TAX_BRACKET_1)
return TAX_RATE_2 * (gross_pay - TAX_BRACKET_1) + TAX_RATE_1 * TAX_BRACKET_1;
else
return TAX_RATE_1 * gross_pay;
}
  • switch语句中使用的表达式必须具有整数类型或枚举类型,或者是具有单个转换函数到整数或枚举类型的类类型,无法进行bool等逻辑运算。
  1. ABC 邮购杂货店出售的洋蓟售价为 2.05美元/磅,甜菜售价为 115 美元/磅,胡萝卜售价为1.09美元/磅。在添加运费之前,100美元的订单有 5%的打折优惠。少于或等于5磅的订单收取 6.5 美元的运费和包装费,5磅~ 20 磅的订单收取14 美元的运费和包装费,超过 20 磅的订单在 14 美元的基础上每续重1磅增加 0.5 美元。

编写一个程序,在循环中用 switch 语句实现用户输入不同的字母时有不同的响应,即输入a的响应是让用户输入洋蓟的磅数,b 是甜菜的磅数,c 是胡萝人的磅数,q 是退出订购。程序要记录累计的重量。即,如果用户输入 4磅的甜菜,然后输入 5磅的甜菜,程序应报告9 磅的甜菜。然后,该程序要计算货物总价、折扣(如果有的话) 、运费和包装费。随后,程序应显示所有的购买信息: 物品售价、订购的重量(单位:磅) 、订购的蔬菜费用、订单的总费用、折扣(如果有的话)、运费和包装费,以及所有的费用总额。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include <stdio.h>
#include <stdbool.h>

#define ARTICHOKE_PRICE_PER_LB 2.05
#define BEET_PRICE_PER_LB 1.15
#define CARROT_PRICE_PER_LB 1.09

#define TRANSPORT_5LB 6.50
#define TRANSPORT_20LB 14.00
#define TRANSPORT_OVER_20_RATE 0.5

#define DISCOUNT_RATE 0.05

void flush_input_buffer(void);
float calculate_transport(float weight);

int main() {
float artichokeWeight = 0, beetWeight = 0, carrotWeight = 0, totalWeight;
float artichokePrice, beetPrice, carrotPrice, subtotal, discount, transport, total;
bool discount_flag;
float weight;
char option;

while (1){
printf("What would you like to order?\n");
printf("a) artichoke b) beet c) carrot q) quit\n");
option = getchar();
switch(option)
{
case ('q'):
printf("Bye.\n");
return 0; //exit program

case ('a'): //artichokes
printf("How many pound of artichokes would you like to add?");
if (scanf("%f", &weight) == 1)
artichokeWeight += weight;
else
{
flush_input_buffer();
printf("Invalid input. Try again.\n");
continue; //repeat main program loop
}
break;

case ('b'): //beets
printf("How many pounds of beets would you like to add?");
if (scanf("%f", &weight) == 1)
beetWeight += weight;
else
{
flush_input_buffer();
printf("Invalid input. Try again.\n");
continue; //repeat main program loop
}
break;

case ('c')://carrots
printf("How many pounds of carrots would you like to add?");
if (scanf("%f", &weight) == 1)
carrotWeight += weight;
else
{
flush_input_buffer();
printf("Invalid input. Try again.\n");
continue; //repeat main program loop
}
break;
default:
printf("Invalid input. Try again.\n");
continue; //repeat main program loop
}
//calculate subtotal
artichokePrice = artichokeWeight * ARTICHOKE_PRICE_PER_LB;
beetPrice = beetWeight * BEET_PRICE_PER_LB;
carrotWeight = carrotWeight * CARROT_PRICE_PER_LB;
subtotal = artichokePrice + beetPrice + carrotPrice;

//calculate discount
if (subtotal >= 100)
{
discount_flag = true;
discount = DISCOUNT_RATE * subtotal;
}
else
discount_flag = false;

//calulate transport
totalWeight = artichokeWeight + beetWeight + carrotWeight;
transport = calculate_transport(totalWeight);

//grand total
total = subtotal + transport - (discount_flag ? discount : 0.0);

printf("\n");
printf("Your order summary:\n\n");
printf("Artichokes: %.2flbs @ $.2f/lb: $%.2f\n",
artichokeWeight, ARTICHOKE_PRICE_PER_LB, artichokePrice);
printf("Beets: %.2flbs @ $.2f/lb: $%.2f\n",
beetWeight, BEET_PRICE_PER_LB, beetPrice);
printf("Carrots: %.2flbs @ $%.2f/lb: $%.2f\n",
carrotWeight, CARROT_PRICE_PER_LB, carrotPrice);
printf("\n");
printf("Subtotal: $%.2f\n", subtotal);
if (discount_flag)
printf("%.0f%% discount: $%.2f\n", DISCOUNT_RATE * 100, discount);
printf("Transport charges: $%.2f\n", transport);
printf("Grand total: $%.2f\n", total);
printf("\n");

flush_input_buffer();
}
return 0;
}

void flush_input_buffer(void)
{
while (getchar() != '\n');
}

float calculate_transport(float weight)
{
if (weight < 5.0)
return TRANSPORT_5LB;
else if (weight < 20.0)
return TRANSPORT_20LB;
else
return TRANSPORT_20LB + TRANSPORT_OVER_20_RATE * (weight - 20.0);
}
  1. 设计一个名为alter()的函数,接受两个int类型的变量x和y,把它们的值分别改成两个变量之和以及两变量之差。

使用指针

1
2
3
4
5
6
7
void alter(int * pa, int * pb)
{
int temp;
temp = *pa + *pb;
*pb = *pa - *pb;
*pa = temp;
}

or
1
2
3
4
5
void alter(int * pa, int * pb)
{
*pa += *pb;
*pb = *pa - 2 * *pb;
}

  1. power()函数返回一个double类型数的正整数次幂。改进该函数,使其能正确计算负幂。另外,函数要处理0的任何次幂
    都为0,任何数的0次幂都为1(函数应报告0的0次幂未定义,因此把该值处理为1)。使用递归函数并在程序中测试该函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h> //prototype for abs()

double power(double base, int exponent);

int main() {
double base, output;
int exponent;

printf("Test power() function:\n");
printf("Enter a :double: base and :int: exponent: ");
while (scanf("%lf %d", &base, &exponent) == 2)
{
output = power(base, exponent);

printf("%f ^ %d = %f \n", base, exponent, output);

printf("Enter a :double: base and :int: exponent: ");
}
return 0;
}

double power(double base, int exponent)
{
double dbl_power;

//handle powers of zero
if (base == 0)
{
if (exponent == 0)
{
printf("Warning: 0 ^ 0 is undefined. Using 1.\n");
return 1.0;
}
else
return 0;
}
if (exponent == 0) return 1; //stop recursion

dbl_power = base * power(base, abs(exponent) - 1); // recursion step

//if exponent is negative, take reciprocal
if (exponent < 0) dbl_power = 1 / dbl_power;

return dbl_power;
}
  1. 编写一个函数,返回储存在double类型数组中最大值的下标,并在一个简单的程序中测试该函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    #define SIZE 10

    int index_of_max(double *arr, int arr_size);

    int main(void)
    {
    // test index_of_max

    printf("Driver for index_of_max: returns index of the largest value stored "
    "in an array of doubles.\n");
    putchar('\n');

    double test[SIZE];

    srand(time(NULL)); // seed random number generator

    // initialize test array with random doubles
    for (int i = 0; i < SIZE; i++)
    test[i] = rand() / (double) RAND_MAX;

    // print test array

    printf("%5s ", "Index");
    for (int i = 0; i < SIZE; i++)
    printf("| %6d ", i);
    printf("\n");
    printf("%5s ", "Value");
    for (int i = 0; i < SIZE; i++)
    printf("| %6.4f ", test[i]);
    printf("\n");
    printf("\n");

    // print results
    printf("The maximum value occurs at index %d\n", index_of_max(test, SIZE));

    return 0;
    }

    int index_of_max(double *arr, int arr_size)
    {
    // return index of max value in array of doubles

    int index_of_max = 0;
    for (int i = 1; i < arr_size; i++)
    if (*(arr + i) > *(arr + index_of_max))
    index_of_max = i;

    return index_of_max;
    }
  2. 编写一个函数,把double类型数组中的数据倒序排列,并在一个简单的程序中测试该函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>


    void reverse_array(double *arr, int arr_size);

    int main(void)
    {
    // test reverse_array()

    printf("Testing reverse_array()\n");

    double test1[9];
    double test2[10];
    double test3[2];

    srand(time(NULL));

    // initialize test array 1 with 9 random doubles
    for (int i = 0; i < 9; i++)
    test1[i] = rand() / (double) RAND_MAX;

    // initialize test array 2 with 10 random doubles
    for (int i = 0; i < 10; i++)
    test2[i] = rand() / (double) RAND_MAX;

    // initialize test array 1 with 2 random doubles
    for (int i = 0; i < 2; i++)
    test3[i] = rand() / (double) RAND_MAX;

    // test array 1

    printf("First Test\n");
    // print original array
    printf("%10s: ", "Original");
    for (int i = 0; i < 9; i++)
    printf("%5.2f ", test1[i]);
    putchar('\n');

    //print reversed array
    reverse_array(test1, 9);
    printf("%10s: ", "Reversed");
    for (int i = 0; i < 9; i++)
    printf("%5.2f ", test1[i]);
    putchar('\n');

    // test array 2

    printf("Second Test\n");
    // print original array
    printf("%10s: ", "Original");
    for (int i = 0; i < 10; i++)
    printf("%5.2f ", test2[i]);
    putchar('\n');

    //print reversed array
    reverse_array(test2, 10);
    printf("%10s: ", "Reversed");
    for (int i = 0; i < 10; i++)
    printf("%5.2f ", test2[i]);
    putchar('\n');

    // test array 3

    printf("Third Test\n");
    // print original array
    printf("%10s: ", "Original");
    for (int i = 0; i < 2; i++)
    printf("%5.2f ", test3[i]);
    putchar('\n');

    //print reversed array
    reverse_array(test3, 2);
    printf("%10s: ", "Reversed");
    for (int i = 0; i < 2; i++)
    printf("%5.2f ", test3[i]);
    putchar('\n');

    return 0;
    }

    void reverse_array(double *arr, int arr_size)
    {
    // reverse an array of double

    double tmp;

    for (int i = 0; i < arr_size / 2; i++)
    {
    // swap values between indexes i and (arr_size - 1 - i)
    tmp = arr[i];
    arr[i] = arr[arr_size - 1 - i];
    arr[arr_size - 1 - i] = tmp;
    }
    }
  3. 使用编程练习2中的拷贝函数,把一个内含7个元素的数组中第3~第5个元素拷贝至内含3个元素的数组中。该函数本身不需要修改,只需要选择合适的实际参数(实际参数不需要是数组名和数组大小,只需要是数组元素的地址和待处理元素的个数)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #include <stdio.h>

    void copy_ptrs(double *target, double *source_start, double *source_end);

    int main(void)
    {
    double source[7] = {2.4, 5.9, 7.8, 1.5, 3.3, 5.3, 6.8};
    double target[3];

    copy_ptrs(target, source + 2, source + 5);

    // print arrays
    for (int i = 0; i < 7; i++)
    printf("%.1f ", source[i]);
    putchar('\n');

    for (int i = 0; i < 3; i++)
    printf("%.1f ", target[i]);
    putchar('\n');

    return 0;
    }

    void copy_ptrs(double *target, double *source_start, double *source_end)
    {
    // copy arr using pointer notation and pointer endpoint

    for (double *ptr = source_start; ptr < source_end; ptr++, target++)
    *target = *ptr;
    }
  4. 编写一个程序,初始化一个double类型的3×5二维数组,使用一个处理变长数组的函数将其拷贝至另一个二维数组中。还要编写一个以变长数组为形参的函数以显示两个数组的内容。这两个函数应该能处理任意N×M数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    #include <stdio.h>
    #define ROWS 3
    #define COLUMNS 5

    void copy_2dim_arr(int rows, int cols, double source[rows][cols], double target[rows][cols]);
    void print_2dim_arr(int rows, int cols, double arr[rows][cols]);

    int main() {
    double array1[ROWS][COLUMNS] = {
    {4.3, 5.7, 2.1, 6.6, .8},
    {5.6, 23.5, 73.2, 12.3, 123},
    {22.1, 35.3, 6.35, 0.132, 11.1}
    };
    double array2[ROWS][COLUMNS];

    // copy array1 to array2
    copy_2dim_arr(ROWS, COLUMNS, array1, array2);

    // print contents of arrays
    printf("Array 1:\n");
    print_2dim_arr(ROWS, COLUMNS, array1);
    putchar('\n');

    printf("Array2:\n");
    print_2dim_arr(ROWS, COLUMNS, array2);

    return 0;
    }

    void copy_2dim_arr(int rows, int cols, double source[rows][cols], double target[rows][cols])
    {
    // copy one two-dimensional array to another
    for (int i = 0; i < rows; i++){
    for (int j = 0; j < cols; j++)
    target[i][j] = source[i][j];
    }
    }

    void print_2dim_arr(int rows, int cols, double arr[rows][cols])
    {
    //print the contents of a two-dimensional array

    for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++)
    printf(" %10.3f ", arr[i][j]);

    putchar('\n');
    }
    }
  5. 编写一个函数,把两个数组中相对应的元素相加,然后把结果储存到第 3 个数组中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>

    #define SIZE 10
    void add_arrays(int *addend1, int *addend2, int *sum, int array_length);

    int main() {
    //test add_arrays
    srand(time(NULL));

    int array1[SIZE];
    int array2[SIZE];
    int sum[SIZE];

    // initialize arrays with random ints
    for (int i = 0; i < SIZE; i++)
    {
    array1[i] = rand() % 20;
    array2[i] = rand() % 20;
    }

    // get sum of arrays
    add_arrays(array1, array2, sum, SIZE);

    //print arrays
    printf("%8s %8s %8s\n", "Array 1", "Array 2", "Sum");
    for (int i = 0; i < SIZE; i++)
    printf("%8d %8d %8d\n", array1[i], array2[i], sum[i]);

    return 0;
    }

    void add_arrays(int *addend1, int *addend2, int *sum, int array_length)
    {
    // calculate elementwise sum of two arrays
    for (int *tar = sum; tar < sum + array_length; tar++, addend1++, addend2++)
    *tar = *addend1 + *addend2;
    }
  6. 编写一个程序,提示用户输入3组数,每组数包含5个double类型的数(假设用户都正确地响应,不会输入非数值数据)。该程序应完成下列任
    务。
    a.把用户输入的数据储存在3×5的数组中
    b.计算每组(5个)数据的平均值
    c.计算所有数据的平均值
    d.找出这15个数据中的最大值
    e.打印结果
    每个任务都要用单独的函数来完成(使用变长数组作为函数形参的方式)。完成任务b,要编写一个计算并返回一维数组平均值的函数,利用循环调用该函数3次。对于处理其他任务的函数,应该把整个数组作为参数,完成任务c和d的函数应把结果返回主调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <stdio.h>
#define ROWS 3
#define COLUMNS 5

void input_data(int rows, int columns, double array[][5]);
double compute_row_average(double *array, int cols);
double compute_array_average(int rows, int cols, double array[rows][cols]);
double largest_vlue(int rows, int cols, double array[rows][cols]);

int main() {
double data[ROWS][COLUMNS];

input_data(ROWS, COLUMNS, data);

//print row averages
printf("Row Averages:\n");
for (int i = 0; i < ROWS; i++)
{
printf("\t Average for row %d: %3.f\n", i + 1,
compute_row_average(data[i], COLUMNS));
}

//print array average
printf("Average for entire array: %.3f\n",
compute_array_average(ROWS,COLUMNS, data));

// print largest value
printf("Maximum array value: %.3f\n",
largest_vlue(ROWS, COLUMNS, data));

return 0;
}

void input_data(int rows, int columns, double array[][5])
{
printf("Enter 3 groups of 5 double values each:\n");
for (int i = 0; i < rows; i++)
{
printf("Grop %d:", i + 1);
for (int j = 0; j < columns; j++)
scanf("%lf", array[i] + j);
}
}

double compute_row_average(double *array, int cols)
{
double total = 0;
for (int i = 0; i < cols; i++)
total += array[i];

return total / cols;
}

double compute_array_average(int rows, int cols, double array[rows][cols])
{
double total = 0;
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
total += array[i][j];

return total / (rows * cols);
}

double largest_vlue(int rows, int cols, double array[rows][cols])
{
double max = array[0][0];
for (int i = 0; i < rows; i++)
for (int j = 0; j < cols; j++)
if (array[i][j] > max)
max = array[i][j];

return max;
}
  1. 编写一个函数,读入10个字符串或者读到EOF时停止。该程序为用户提供一个有5个选项的菜单:打印源字符串列表、以ASCII中的顺序打印字
    符串、按长度递增顺序打印字符串、按字符串中第1个单词的长度打印字符串、退出。菜单可以循环显示,除非用户选择退出选项。当然,该程序要能真正完成菜单中各选项的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define COUNT 10
#define LIMIT 50

void sort_ASCII(char *strings[], int n);
void sort_length(char *strings[], int n);
int fwlen(char *string);
void sort_firstword_length(char *strings[], int n);
char * get(char *string, int n);
void print_menu(void);

int main() {
char strings[COUNT][LIMIT];
char *strptrs[COUNT];
char * success;
char ch;

// initialize array of pointer
for (int i = 0; i < COUNT; i++)
strptrs[i] = strings[i];

printf("Enter up to 10 strings (EOF to stop): \n");

//read up to ten strings from input
for (int i = 0; i < COUNT; i++)
{
printf("%d: ", i + 1);
success = get(strings[i], LIMIT);

// if EOF encountered, stop reading strings
if (!success)
break;
}
printf("\n");

print_menu();
while((ch = getchar()) != 'q')
{
//discard rest of the line
if (ch != '\n')
while (getchar() != '\n')
continue;

// sort strings
switch (ch) {
case ('a'):
sort_ASCII(strptrs, COUNT);
break;
case ('l'):
sort_length(strptrs, COUNT);
break;
case ('f'):
sort_firstword_length(strptrs, COUNT);
break;
case ('o'):
break;
default:
printf("Invalid input. Try again.\n\n");
print_menu();
continue;
}
// print sorted strings
puts("");
for (int i = 0; i < COUNT; i++)
puts(strptrs[i]);

puts("");
print_menu();
}
puts("Bye");
return 0;
}

void sort_ASCII(char *strings[], int n)
{
// sort array of string pointers by ASCII collating sequence

char *tmp;

for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
{
if (strcmp(strings[i], strings[j]) > 0)
{
tmp = strings[i];
strings[i] = strings[j];
strings[j] = tmp;
}
}
}

void sort_length(char *strings[], int n)
{
// sort array of string pointer by length

char *tmp;

// 冒泡排序
for (int i = 0; i < n - 1; i++)
for (int j = i + 1; j < n; j++)
{
if (strlen(strings[i]) > strlen(strings[j]))
{
tmp = strings[i];
strings[i] = strings[j];
strings[j] = tmp;
}
}
}

int fwlen(char *string)
{
// return length of first word of string

int length = 0;

// skip leading whitespace
while (isspace(*string))
string++;

// count first word length
while (!isspace(*string))
{
length++;
string++;
}
return length;
}

void sort_firstword_length(char *strings[], int n)
{
// sort array of string pointers by ASCII collating sequence

char *tmp;

for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
{
if (fwlen(strings[i]) > fwlen(strings[j]))
{
tmp = strings[i];
strings[i] = strings[j];
strings[j] = tmp;
}
}
}

char * get(char *string, int n)
{
// wrapper for fgets that repalces first newline with null

char *ret_val = fgets(string, n, stdin);
while (*string != '\0')
{
if (*string == '\n')
*string = '\0';
string++;
}
return ret_val;
}

void print_menu(void)
{
puts("Choose an option: ");
puts("(o) Print strings in original order.");
puts("(a) Print strings in ASCII collating sequence.");
puts("(l) Print strings ordered by length.");
puts("(f) Print strings ordered by length of the first word.");
puts("(q) Quit.");
puts("");
puts("Enter a character: ");
}
  1. 编写一个通过命令行运行的程序计算幂。第1个命令行参数是double类型的数,作为幂的底数,第2个参数是整数,作为幂的指数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void print_error_message(void);

int main(int argc, char *argv[]) {
double base;
long power;
char *end;

if (argc != 3)
{
print_error_message();
return 1;
}

// get exponent
end = argv[2];
while(*end != '\0')
end++;
power = strtol(argv[2], &end, 10);

if(*end) //error condition
{
print_error_message();
return 1;
} //strtol 函数会将 end 指向转换后的字符串的最后一个字符的下一个位置,通常是字符串的结束符 \0。

printf("%f ^ %ld = %f\n", base, power, pow(base, power));

return 0;
}

void print_error_message(void)
{
puts("Usage: <program_name> <arg1 base:double> <arg2 power:int>");
}
  1. 编写一个程序,生成1000个1~10范围内的随机数。不用保存或打印这些数字,仅打印每个数出现的次数。用 10 个不同的种子值运行,生成的
    数字出现的次数是否相同?可以使用本章自定义的函数或ANSI C的rand()和srand()函数,它们的格式相同。这是一个测试特定随机数生成器随机性的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 1000
#define RANGE 10

void generate_random_numbers(void);

int main() {
for (int seed= 1; seed <= 10; seed++)
{
printf("Seed: %d\n", seed);
generate_random_numbers();
printf("\n");
}
return 0;
}

void generate_random_numbers(void)
{
srand((unsigned int) time(NULL)); // 设置种子值为当前时间

int counts[RANGE] = {0}; // 用于记录每个数字出现的次数

for (int i = 0; i < SIZE; i++)
{
int random_count = rand() % RANGE + 1; // 生成1~10范围内的随机数
counts[random_count - 1]++; // 更新对应数字出现次数的计数
}

// 打印每个数字出现的次数
for (int i = 0; i < RANGE; i++)
printf("Number %d: %d times\n", i + 1, counts[i]);
}
  1. 编写一个符合以下描述的函数。首先,询问用户需要输入多少个单词。然后,接收用户输入的单词,并显示出来,使用malloc()并回答第1个问
    题(即要输入多少个单词),创建一个动态数组,该数组内含相应的指向char的指针(注意,由于数组的每个元素都是指向char的指针,所以用于储
    存malloc()返回值的指针应该是一个指向指针的指针,且它所指向的指针指向char)。在读取字符串时,该程序应该把单词读入一个临时的char数组,使用malloc()分配足够的存储空间来储存单词,并把地址存入该指针数组(该数组中每个元素都是指向 char 的指针)。然后,从临时数组中把单词拷贝到动态分配的存储空间中。因此,有一个字符指针数组,每个指针都指向一个对象,该对象的大小正好能容纳被储存的特定单词。下面是该程序的一个运行示例:

How many words do you wish to enter? 5
Enter 5 words now:
I enjoyed doing this exerise
Here are your words:
I
enjoyed
doing
this
exercise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_WORD_LENGTH 20 // 单词的最大长度

void get_words(int n);

int main(void) {
int num_words;

printf("How many words do you wish to enter?");
scanf("%d", &num_words);

while (getchar() != '\n')
continue; // 清楚输入缓冲区

if (num_words > 0)
get_words(num_words);
else
printf("No words to enter.\n");

return 0;
}

void get_words(int n)
{
char **words = (char **)malloc(n * sizeof(char *)); // 动态分配存储单词指针的数组
if (words == NULL)
{
fprintf(stderr, "Memory allocation failed. Exiting,\n");
exit(EXIT_FAILURE);
}

printf("Enter %d words now:\n", n);

for (int i = 0; i < n; i++)
{
char temp[MAX_WORD_LENGTH];
scanf("%s", temp); // 读取单词到临时数组中
words[i] = (char *) malloc((strlen(temp) + 1) * sizeof(char)); // 为单词分配存储空间
if (words[i] == NULL)
{
fprintf(stderr, "Memory allocation failed. Exiting.\n");
exit(EXIT_FAILURE);
}
strcpy(words[i], temp); // 复制单词到动态分配的存储空间中
}

printf("Here are your words:\n");
for (int i = 0; i < n; i++)
printf("%s\n", words[i]);

// 释放分配的内存
for (int i = 0; i < n; i++)
free(words[i]);
free(words);
}

复习题

假设在测试程序时要暂时跳过一块代码,如何在不移除这块代码的前提下完成这项任务?

使用条件编译指令。一种方法是使用#ifndef

1
2
3
4
5
6
#define _SKIP_      /* 如果不需要跳过代码,则删除这条指令 */
#ifndef _SKIP_

/* 需要跳过的代码 */

#endif

常量命名

有一个不常用的命名约定,即在名称前带c或k前缀来表示常量(如,c_level或k_line)。

显示八进制和十六进制

在C程序中,既可以使用和显示不同进制的数。不同的进制要使用不同的转换说明。以十进制显示数字,使用%d;以八进制显示数字,使用%o;以十六进制显示数字,使用%x。另外,要显示各进制数的前缀00x0X必须分别使用%#o%#x%#X。,

打印short、long、long long和unsigned类型

打印unsigned int类型的值,使用%u转换说明;打印long类型的值,使用%ld转换说明。如果系统中int和long的大小相同,使用%d就行。但是,这样的程序被移植到其他系统(int和long类型的大小不同)中会无法正常工作。在x和o前面可以使用l前缀,%lx表示以十六进制格式打印long类型整数%lo表示以八进制格式打印long类型整数

  • 注意,虽然C允许使用大写或小写的常量后缀,但是在转换说明中只能用小写。

C语言有多种printf()格式。对于short类型,可以使用h前缀。%hd表示以十进制显示short类型的整数%ho表示以八进制显示short类型的整数。h和l前缀都可以和u一起使用,用于表示无符号类型。例如,%lu表示打印unsigned long类型的值。

scanf()

scanf()的输入形式,scanf("%d",&Alphabet) 要先将%d包含以后才能使用&进行传参。

打印浮点数

十六进制浮点数
printf("And it's %a in hexadecimal, powers of 2 notation\n",a boat);

printf()

ANSI C标准 printf() 转换说明转换标准.png)

printf()的修饰符的修饰符.png)
注意类型可移植性

printf()中的标记中的标记.png)

scanf()

ANSI C中scanf()的转换说明转换说明.png)

scanf()转换说明中的修饰符转换说明中的修饰符.png)
scanf()转换说明中的修饰符续转换说明中的修饰符续.png)

  • scanf() 更像是获取单词的函数,而不是获取整个字符串的函数。
  • 使用 %s 转换说明时,scanf() 会从第一个非空白字符开始,读取直到遇到下一个空白字符(空格、制表符、换行符等)为止。
  • 如果指定了字段宽度,如 %10sscanf() 将读取指定数量的字符或者在遇到第一个空白字符时停止(以先满足条件为准)。
    字段宽度和scanf().png)

scanf()的典型用法是读取并转换混合数据类型为某种标准形式。
例如,如果输入行包含一种工具名、库存量和单价,就可以使用scanf()

真值

从数值方面而不是从真/假方面来看测试条件。要牢记:关系表达式为真,求值得1;关系表达式为假,求值得0。因此,这些表达式实际上相当于数值。
例如,用while(goats)替换while (goats !=0),因为表达式goats != 0goats都只有在goats的值为0时才为0或假。
第1种形式(while (goats != 0))对初学者而言可能比较清楚,但是第2种形式(while (goats))才是C程序员最常用的。

比较常量

如果待比较的一个值是常量,可以把该常量放在左侧有助于编译器捕获错误:
5 = canoes <—— 语法错误
5 == canoes <—— 检查canoes的值是否为5
可以这样做是因为C语言不允许给常量赋值,编译器会把赋值运算符的这种用法作为语法错误标记出来。许多经验丰富的程序员在构建比较是否相等的表达式时,都习惯把常量放在左侧。

比较算数运算符优先级

比较算数运算符优先级

ctype.h 头文件中的字符测试函数

 ctype.h头文件中的字符测试函数
ctype.h头文件中的字符映射函数

ctype.h

ctype.h系列的字符函数(如,issapce()isalpha())为创建以分类字符为基础的测试表达式提供了便捷的工具。

条件运算符: ?:

expression1 ? expression2 : expression3

如果 expression1 为真(非 0),那么整个条件表达式的值与 expression2的值相同;如果expression1为假(0),那么整个条件表达式的值与expression3的值相同。

1
2
3
(5 > 3) ? 1 : 2 值为1
(3 > 5) ? 1 : 2 值为2
(a > b) ? a : b 如果a >b,则取较大的值

switch 语句

switch在圆括号中的测试表达式的值应该是一个整数值(包括char类型)。case标签必须是整数类型(包括char类型)的常量或整型常量表达式(即,表达式中只包含整型常量)。不能用变量作为case标签。
switch的构造如下:

1
2
3
4
5
6
7
8
9
switch ( 整型表达式)
{
case 常量1:
语句 <--可选
case 常量2:
语句 <--可选
default : <--可选
语句 <--可选
}

输入流

考虑下面的输入:
is 28 12.4
在我们眼中,这就像是一个由字符、整数和浮点数组成的字符串。但是对 C程序而言,这是一个字节流。第1个字节是字母i的字符编码,第2个字节是字母s的字符编码,第3个字节是空格字符的字符编码,第4个字节是数字2的字符编码,等等。所以,如果get_long()函数处理这一行输入,第1个字符是非数字,那么整行输入都会被丢弃,包括其中的数字,因为这些数字只是该输入行中的其他字符:

1
2
while ((ch = getchar()) != '\n')
putchar(ch); // 处理错误的输入

虽然输入流由字符组成,但是也可以设置scanf()函数把它们转换成数值。例如,考虑下面的输入:
42
如果在scanf()函数中使用%c转换说明,它只会读取字符4并将其储存在char类型的变量中。如果使用%s转换说明,它会读取字符4和字符2这两个字符,并将其储存在字符数组中。如果使用%d转换说明,scanf()同样会读取两个字符,但是随后会计算出它们对应的整数值:4×10+2,即42,然后将表示该整数的二进制数储存在 int 类型的变量中。如果使用%f 转换说明,scanf()也会读取两个字符,计算出它们对应的数值42.0,用内部的浮点表示法表示该值,并将结果储存在float类型的变量中。

函数

1.1 ANSI C要求在每个变量前都声明其类型

不能像普通变量声明那样使用同一类型的变量列表:

1
2
void dibs(int x, y, z) /* 无效的函数头 */
void dubs(int x, int y, int z) /* 有效的函数头 */

如果变量是同一类型,这种形式可以用逗号分隔变量名列表,如下所示:
1
2
void dibs(x, y, z)
int x, y, z; /* 有效 */

1.2 使用函数前先声明

在使用函数之前,要用ANSI C形式声明函数原型:
void show_n_char(char ch, int num);
当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型。根据个人喜好,你也可以省略变量名:
void show_n_char(char, int);
在原型中使用变量名并没有实际创建变量,char仅代表了一个char类型的变量,以此类推。

1.3 省略函数原型却保留函数原型的优点

1
2
3
4
5
6
7
8
9
// 下面这行代码既是函数定义,也是函数原型
int imax(int a, int b) { return a > b ? a : b; }
int main()
{
int x, z;
...
z = imax(x, 50);
...
}

递归

递归的关键在于每个递归调用都会等待它的下一级递归完成,然后再继续执行后面的代码,最终实现整个递归的效果。

可以假设有一条函数调用链——fun1()调用fun2()、fun2()调用 fun3()、fun3()调用fun4()。当 fun4()结束时,控制传回
fun3();当fun3()结束时,控制传回 fun2();当fun2()结束时,控制传回fun1()。递归的情况与此类似,只不过fun1()、fun2()、fun3()和fun4()都是相同的函数。

递归的基本原理

  1. 每级函数调用都有自己的变量。也就是说,第1级的n和第2级的n不同,所以程序创建了4个单独的变量,每个变量名都是n,但是它们的值各
    不相同。当程序最终返回 up_and_down()的第1 级调用时,最初的n仍然是它的初值1。
    递归中的变量
  2. 每次函数调用都会返回一次。当函数执行完毕后,控制权将被传回上一级递归。程序必须按顺序逐级返回递归,从某级up_and_down()返回
    上一级的up_and_down(),不能跳级回到main()中的第1级调用。
  3. 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行。例如,程序清单9.6中的打印语句#1位于递归调用之前,它按照递归的
    顺序:第1级、第2级、第3级和第4级,被执行了4次。
  4. 递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行。例如,打印语句#2位于递归调用之后,其执行的顺序是第4级、第3
    级、第2级、第1级。递归调用的这种特性在解决涉及相反顺序的编程问题时很有用。稍后将介绍一个这样的例子。
  5. 虽然每级递归都有自己的变量,但是并没有拷贝函数的代码。程序按顺序执行函数中的代码,而递归调用就相当于又从头开始执行函数的代
    码。除了为每次递归调用创建变量外,递归调用非常类似于一个循环语句。实际上,递归有时可用循环来代替,循环有时也能用递归来代替。
  6. 递归函数必须包含能让递归调用停止的语句。通常,递归函数都使用if或其他等价的测试条件在函数形参等于某特定值时终止递归。为此,
    每次递归调用的形参都要使用不同的值。例如,程序中的up_and_down(n)调用up_and_down(n+1)。最终,实际参数等于4时,if的测试
    条件(n < 4)为假。

return

返回值不仅可以赋给变量,也可以被用作表达式的一部分。
返回值不一定是变量的值,也可以是任意表达式的值。
return (n < m) ? n : m;

void variables(double *, double *, double *);

数组

如果初始化数组时省略方括号中的数字,编译器会根据初始化列表中的项数来确定数组的大小。

整个数组的大小除以单个元素的大小就是数组元素的个数。

for (index = 0; index < sizeof days / sizeof days[0]; index++)
sizeof days是整个数组的大小(以字节为单位),sizeof day[0]是数组中一个元素的大小(以字节为单位)。

只有在函数原型或函数定义头中,才可以用int ar[]代替int * ar

由于函数原型可以省略参数名,所以下面4种原型都是等价的:

1
2
3
4
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);

但是,在函数定义中不能省略参数名。下面两种形式的函数定义等价:
1
2
3
4
5
6
7
8
9
int sum(int *ar, int n)
{

}

int sum(int ar[], int n)
{

}

如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形式参数时应使用关键字const

1
2
3
4
5
6
7
8
9
int sum(const int ar[], int n); /* 函数原型 */
int sum(const int ar[], int n) /* 函数定义 */
{
int i;
int total = 0;
for( i = 0; i < n; i++)
total += ar[i];
return total;
}

一般而言,如果编写的函数需要修改数组,在声明数组形参时则不使用const;如果编写的函数不用修改数组,那么在声明数组形参时最好使用const。

复合字面量

普通的数组声明:int diva[2] = {10, 20};
下面的复合字面量创建了一个和diva数组相同的匿名数组,也有两个int类型的值:
(int [2]){10, 20} // 复合字面量

  • 去掉声明中的数组名,留下的int [2]即是复合字面量的类型名
    (int []){50, 20, 90} // 内含3个元素的复合字面量
    因为复合字面量是匿名的,所以不能先创建然后再使用它,必须在创建的同时使用它。
    还可以把复合字面量作为实际参数传递给带有匹配形式参数的函数:
    1
    2
    3
    4
    int sum(const int ar[], int n);
    ...
    int total3;
    total3 = sum((int []){4,4,4,5,5,5}, 6);
    第1个实参是内含6个int类型值的数组,和数组名类似,这同时也是该数组首元素的地址。这种用法的好处是,把信息传入函数前不必先创建
    数组,这是复合字面量的典型用法。

可以把这种用法应用于二维数组或多维数组。例如,下面的代码演示了如何创建二维int数组并储存其地址:

1
2
3
int (*pt2)[4]; // 声明一个指向二维数组的指针,该数组内含2个数组元素,
// 每个元素是内含4个int类型值的数组
pt2 = (int [2][4]) { {1,2,3,-9}, {4,5,6,-8} };

如上所示,该复合字面量的类型是int [2][4],即一个2×4的int数组。

变长数组

C99引入了变长数组(Variable-Length Arrays,VLA),允许使用变量表示数组的维度。以下是一个使用变长数组的例子:

1
2
3
int quarters = 4;
int regions = 5;
double sales[regions][quarters]; // 变长数组(VLA)

变长数组的限制和特点:

  1. 存储类别限制: 变长数组必须是自动存储类别,不能使用static或extern存储类别说明符。

  2. 初始化限制: 不能在声明中初始化变长数组。

  3. 可选特性: C11标准将变长数组作为可选特性,而不是必须强制实现的特性。

  4. 不能改变大小: 变长数组的”变”指的是在创建数组时可以使用变量指定数组的维度,而不是可以修改已创建数组的大小。一旦创建,数组的大小保持不变。

计算二维数组元素之和的示例:
考虑一个函数 sum2d,计算int类型的二维数组所有元素之和。以下是该函数的声明和定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int sum2d(int rows, int cols, int ar[rows][cols]); // ar是一个变长数组(VLA)

int sum2d(int rows, int cols, int ar[rows][cols])
{
int r;
int c;
int tot = 0;

for (r = 0; r < rows; r++)
for (c = 0; c < cols; c++)
tot += ar[r][c];

return tot;
}

  • 函数原型中,ar 是一个二维变长数组,使用了 rowscols 作为两个维度。
  • 函数定义中,使用了 rowscols 来表示二维数组的大小,可以处理任意大小的二维int数组。
    注意: 形参列表中必须在声明 ar 之前先声明 rowscols

此外,函数的定义也可以使用省略形参名的方式:
int sum2d(int, int, int ar[*][*]); // ar是一个变长数组(VLA),省略了维度形参名

指针

声明指针

声明指针时需要需要带有变量,如果不想加变量则需要有*

不要混淆 *(dates+2)*dates+2 。间接运算符()的优先级高于+,所以 `dates+2` 相当于(*dates)+2:

1
2
3
*(dates + 2) // dates第3个元素的值

*dates + 2 // dates第1个元素的值加2

一元运算符*++的优先级相同,但结合律是从右往左,所以start++先求值,然后才是*start。也就是说,指针start先递增后指向。使用后缀形式(即start++而不是++start)如果使用*++start,顺序则反过来,先递增指针,再使用指针指向位置上的值。如果使(*start)++,则先使用start指向的值,再递增该值,而不是递增指针。这样,指针将一直指向同一个位置,但是该位置上的值发生了变化。虽然*start++的写法比较常用,但是*(start++)这样写更清楚。

当涉及到 const 修饰符和指针时,有几个要点需要注意:

  • const修饰指针所指向的数据,表示指针所指向的数据不能通过这个指针进行修改。例如:
    1
    2
    3
    4
    const double *pd = rates; // pd指向数组的首元素
    *pd = 29.89; // 不允许修改
    pd[2] = 222.22; // 不允许修改
    rates[0] = 99.99; // 允许修改,因为rates未被const限定
  • 指向 const 的指针 (const double *pc) 可以指向非 const 的数据,但不能通过这个指针修改所指向的数据:
    1
    2
    3
    4
    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    const double *pc = rates; // 有效
    pc = &rates[3]; // 有效
    *pc = 92.99; // 不允许修改
  • 指向非 const 的指针(double *pnc)可以指向 const 数据,但也不能通过这个指针修改所指向的 const 数据:
    1
    2
    3
    4
    5
    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
    double *pnc = rates; // 有效
    pnc = &rates[3]; // 有效
    pnc = locked; // 不允许
  • const 可以用于指向数组的指针,保护数组数据不被修改:
    1
    2
    3
    4
    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    const double *pc = rates; // 指向数组的首元素
    show_array(rates, 5); // 有效,数组名转换成指向 const 的指针
    show_array(locked, 4); // 有效,数组名转换成指向 const 的指针
  • const 还可以用于创建常量指针,该指针一旦指向一个地址,就不能再指向别处:
    1
    2
    3
    4
    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    double * const pc = rates; // pc指向数组的开始
    pc = &rates[2]; // 不允许,因为该指针不能指向别处
    *pc = 92.99; // 允许修改 rates[0] 的值
  • const 也可以用于创建既不能更改所指向地址,也不能修改指向地址上的值的指针:
    1
    2
    3
    4
    double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
    const double * const pc = rates; // 不能修改指向的地址,也不能修改地址上的值
    pc = &rates[2]; // 不允许
    *pc = 92.99; // 不允许
  • 演示程序
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* order.c -- 指针运算中的优先级 */
    #include <stdio.h>
    int data[2] = { 100, 200 };
    int moredata[2] = { 300, 400 };
    int main() {
    int * p1, *p2, *p3;
    p1 = p2 = data;
    p3 = moredata;
    printf("*p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3);
    printf("*p1++=%d, *++p2=%d, (*p3)++=%d\n", *p1++, *++p2, (*p3)++);
    printf("*p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3);
    return 0;
    }
  • 该程序输出
    1
    2
    3
    *p1 = 100, *p2 = 100, *p3 = 300
    *p1++=100, *++p2=200, (*p3)++=300
    *p1 = 200, *p2 = 200, *p3 = 301
    只有(*p3)++改变了数组元素的值,其他两个操作分别把p1和p2指向数组的下一个元素。

字符串声明比较

两种声明几乎相同:

1
2
const char *pt1 = "Something is pointing at me;";
const char ar1[] = "Something is pointing at me;";

数组形式(ar1[])

  • 在内存中分配一个内含29个元素的数组,每个元素对应一个字符,还有一个末尾的空字符’\0’。
  • 字符串作为可执行文件的一部分储存在数据段中,静态存储区。
  • 程序开始运行时为数组分配内存,将字符串拷贝到数组中,此时有两个副本:一个在静态内存中,一个在数组中。
  • 数组名ar1是该数组首元素地址的别名,是地址常量,不能更改。可以进行ar1+1等操作,但不允许进行++ar1这样的操作。

指针形式(*pt1)

  • 编译器为字符串在静态存储区预留29个元素的空间,并为指针变量pt1留出一个储存位置,将字符串的地址储存在指针变量中。
  • 指针形式只拷贝字符串的地址给指针,不会拷贝字符串本身。指针可以改变指向的位置,可以使用递增运算符。
  • 字符串字面量被视为const数据,因此指针pt1需要声明为指向const数据的指针,不能通过pt1改变所指向的数据。

总结:初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。

空字符与空指针的区别

  • 空字符 (‘\0’):

    • 用于标记C字符串的末尾。
    • 对应字符编码是0。
    • 在字符串中不可能是其他字符的一部分。
    • 是整数类型,占1字节。
  • 空指针 (NULL):

    • 有一个值,该值不会与任何数据的有效地址对应。
    • 通常用于表示特殊情况,例如遇到文件结尾或未能按预期执行。
    • 是指针类型,占4字节(通常)。

注意:
虽然它们可以用数值0来表示,但从概念上看,空字符和空指针是不同类型的0。

字符串函数

  • 请注意,那些使用const关键字的函数原型表明,函数不会更改字符串。

char *strcpy(char * restrict s1, const char * restrict s2);
该函数把s2指向的字符串(包括空字符)拷贝至s1指向的位置,返回值是s1。

char *strncpy(char * restrict s1, const char * restrict s2, size_t n);
该函数把s2指向的字符串拷贝至s1指向的位置,拷贝的字符数不超过n,其返回值是s1。该函数不会拷贝空字符后面的字符,如果源字符串的字符少于n个,目标字符串就以拷贝的空字符结尾;如果源字符串有n个或超过n个字符,就不拷贝空字符。

char *strcat(char * restrict s1, const char * restrict s2);
该函数把s2指向的字符串拷贝至s1指向的字符串末尾。s2字符串的第1个字符将覆盖s1字符串末尾的空字符。该函数返回s1。

char *strncat(char * restrict s1, const char * restrict s2, size_t n);
该函数把s2字符串中的n个字符拷贝至s1字符串末尾。s2字符串的第1个字符将覆盖s1字符串末尾的空字符。不会拷贝s2字符串中空字符和其后的字符,并在拷贝字符的末尾添加一个空字符。该函数返回s1。

int strcmp(const char * s1, const char * s2);
如果s1字符串在机器排序序列中位于s2字符串的后面,该函数返回一个正数;如果两个字符串相等,则返回0;如果s1字符串在机器排序序列中位于s2字符串的前面,则返回一个负数。

int strncmp(const char * s1, const char * s2, size_t n);
该函数的作用和strcmp()类似,不同的是,该函数在比较n个字符后或遇到第1个空字符时停止比较。

char *strchr(const char * s, int c);
如果s字符串中包含c字符,该函数返回指向s字符串首位置的指针(末尾的空字符也是字符串的一部分,所以在查找范围内);如果在字符串s中未找到c字符,该函数则返回空指针。

char *strpbrk(const char * s1, const char * s2);
如果 s1 字符中包含 s2 字符串中的任意字符,该函数返回指向 s1 字符串首位置的指针;如果在s1字符串中未找到任何s2字符串中的字符,则返回空字符。

char *strrchr(const char * s, int c);
该函数返回s字符串中c字符的最后一次出现的位置(末尾的空字符也是字符串的一部分,所以在查找范围内)。如果未找到c字符,则返回空指针。

char *strstr(const char * s1, const char * s2);
该函数返回指向s1字符串中s2字符串出现的首位置。如果在s1中没有找到s2,则返回空指针。

size_t strlen(const char * s);
该函数返回s字符串中的字符数,不包括末尾的空字符。

s_gets()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
char * s_get(char * st, int n)
{
char * ret_val;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (*st != '\n' && *st != '\0')
st++;
if (*st == '\n')
*st = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>        // 提供 fgets()和getchar()的原型
#include <string.h> // 提供 strchr()的原型
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n');
if (find) // 如果地址不是 NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\0')
continue;
}
return ret_val;
}

内存分配:malloc() 和 free()

malloc()

malloc()函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说, malloc()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。malloc()函数可用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转换为匹配的类型。如果 malloc()分配内存失败,将返回空指针。

1
2
3
double * ptd;

ptd = (double *) malloc(30 * sizeof(double));

以上代码为30个double类型的值请求内存空间,并设置ptd指向该位置。
注意,指针ptd被声明为指向一个double类型,而不是指向内含30个double类型值的块。

1
2
3
double item[n];

ptd = (double *) malloc(n * sizeof(double));

free()

静态内存的数量在编译时是固定的,在程序运行期间也不会改变。自动变量使用的内存数量在程序执行期间自动增加或减少。但是动态分配的内存
数量只会增加,除非用 free()进行释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
int main()
{
double glad[2000];
int i;
...
for (i = 0; i < 1000; i++)
gobble(glad, 2000);
...
}
void gobble(double ar[], int n)
{
double * temp = (double *) malloc( n * sizeof(double));
.../* free(temp); // 假设忘记使用free() */
}

calloc()

1
2
3
long * newmem;

newmem = (long *)calloc(100, sizeof (long));

和malloc()类似,返回指向void的指针。如果要储存不同的类型,应使用强制类型转换运算符。

calloc()函数还有一个特性:它把块中的所有位都设置为0(注意,在某些硬件系统中,不是把所有位都设置为0来表示浮点值0)。

free()函数也可用于释放calloc()分配的内存。

动态内存分配和变长数组

对多维数组而言,使用变长数组更方便。当然,也可以用 malloc() 创建二维数组,但是语法比较繁琐。如果编译器不支持变长数组特性,就只能固
定二维数组的维度,如下所示:

1
2
3
4
5
6
7
8
int n = 5;
int m = 6;
int ar2[n][m]; // n×m的变长数组(VLA)
int (* p2)[6]; // C99之前的写法
int (* p3)[m]; // 要求支持变长数组
p2 = (int (*)[6]) malloc(n * 6 * sizeof(int)); // n×6 数组
p3 = (int (*)[m]) malloc(n * m * sizeof(int)); // n×m 数组(要求支持变长数组)
ar2[1][2] = p2[1][2] = 12;

先复习一下指针声明。由于malloc()函数返回一个指针,所以p2必须是一个指向合适类型的指针。第1个指针声明:
int (* p2)[6]; // C99之前的写法

表明p2指向一个内含6个int类型值的数组。因此,p2[i]代表一个由6个整数构成的元素,p2[i][j]代表一个整数。
第2个指针声明用一个变量指定p3所指向数组的大小。因此,p3代表一个指向变长数组的指针,这行代码不能在C90标准中运行。

fopen()

fopen()的模式字符串.png)

文件结尾

为了避免读到空文件,应该使用入口条件循环(不是do while循环)。鉴于getc() (和其他C输入函数)的设计,程序应该在进入循环体之前先尝试读取。

1
2
3
4
5
6
7
8
9
10
// 设计范例 #1
int ch; // 用int类型的变量储存EOF
FILE * fp;
fp = fopen("wacky.txt", "r");
ch = getc(fp); // 获取初始输入
while (ch != EOF)
{
putchar(ch); // 处理输入
ch = getc(fp); // 获取下一个输入
}

以上代码可简化为:

1
2
3
4
5
6
7
8
// 设计范例 #2
int ch;
FILE * fp;
fp = fopen("wacky.txt", "r");
while (( ch = getc(fp)) != EOF)
{
putchar(ch); //处理输入
}

fseek()

文件的起始点模式

fseek()的第1个参数是FILE指针,指向待查找的文件,fopen()应该已打开该文件。
fseek()的第2个参数是偏移量(offset)。该参数表示从起始点开始要移动的距离(参见表列出的起始点模式)。该参数必须是一个long类型的值,可以为正(前移)、负(后移)或0(保持不动)。
fseek()的第3个参数是模式,该参数确定起始点。

下面是调用fseek()函数的一些示例,fp是一个文件指针:
fseek(fp, 0L, SEEK_SET); // 定位至文件开始处
fseek(fp, 10L, SEEK_SET); // 定位至文件中的第10个字节
fseek(fp, 2L, SEEK_CUR); // 从文件当前位置前移2个字节
fseek(fp, 0L, SEEK_END); // 定位至文件结尾
fseek(fp, -10L, SEEK_END); // 从文件结尾处回退10个字节

文本模式调用.png

指针访问结构成员

如果him == &fellow[0],那么*him == fellow[0],因为&*是一对互逆运算符。
因此,可以做以下替代:
fellow[0].income == (*him).income
必须要使用圆括号,因为.运算符比*运算符的优先级高。
总之,如果him是指向guy类型结构barney的指针,下面的关系恒成立:
barney.income == (*him).income == him->income // 假设 him == &barney

位移运算

移位运算符针对2的幂提供快速有效的乘法和除法:

number << n number 乘以2的n次幂
number >> n 如果number为非负,则用number除以2的n次幂

这些移位运算符类似于在十进制中移动小数点来乘以或除以10。

掩码

因为ASCII码只使用最后7位,所以有时需要用掩码关闭其他位,其相应的二进制掩码是什么?分别用十进制、八进制和十六进制来表示这个掩码。

掩码的二进制是1111111;十进制是127;八进制是0177;十六进制是0x7F

条件编译

#ifdef 判断是否定义了标识符。如果定义了则执行#else#endif指令之前的所有指令并编译所有C代码,如果未定义则执行#else#endif指令之间的所有代码。

#ifndef 判断后面的标识符是否是未定义的。通常用于防止多次包含一个文件。

#if指令很像C语言中的if#if后面跟整型常量表达式,如果表达式为非零,则表达式为真。
可以按照if else的形式使用#elif
较新的编译器提供另一种方法测试名称是否已定义,即用#if defined(VAX)代替#ifdef VAX
这里,defined是一个预处理运算符,如果它的参数是用#defined定义过,则返回1;否则返回0。这种新方法的优点是,它可以和#elif一起使用。

1
2
3
4
5
6
7
8
9
#if defined (IBMPC)
#include "ibmpc.h"
#elif defined (VAX)
#include "vax.h"
#elif defined (MAC)
#include "mac.h"
#else
#include "general.h"
#endif

如果在VAX机上运行这几行代码,那么应该在文件前面用下面的代码定义VAX:
#define VAX

#define 中使用参数

在#define中使用参数可以创建外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中。

函数宏定义的组成

  • #define SQUARE(X) X*X

#运算符

下面是一个类函数宏:
#define PSQR(X) printf("The square of X is %d.\n", ((X)*(X)));
假设这样使用宏:
PSQR(8);
输出为:
The square of X is 64.
C允许在字符串中包含宏参数。在类函数宏的替换体中,#号作为一个预处理运算符,可以把记号转换成字符串。例如,如果x是一个宏形参,那么#x就是转换为字符串”x”的形参名。这个过程称为字符串化(stringizing)。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* subst.c -- 在字符串中替换 */
#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))
int main(void)
{
int y = 5;
PSQR(y);
PSQR(2 + 4);
return 0;
}
该程序的输出如下:
The square of y is 25.
The square of 2 + 4 is 36.

调用第1个宏时,用”y“替换#x。调用第2个宏时,用”2 + 4“替换#x。ANSI C字符串的串联特性将这些字符串与printf()语句的其他字符串组合,生成最终的字符串。

预处理器粘合剂:##运算符

#运算符类似,##运算符可用于类函数宏的替换部分。而且,##还可用于对象宏的替换部分。##运算符把两个记号组合成一个记号。例如,可以这样做:
#define XNAME(n) x ## n
然后,宏XNAME(4)将展开为x4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// glue.c -- 使用##运算符
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);
int main(void)
{
int XNAME(1) = 14; // 变成 int x1 = 14;
int XNAME(2) = 20; // 变成 int x2 = 20;
int x3 = 30;
1212
PRINT_XN(1); // 变成 printf("x1 = %d\n", x1);
PRINT_XN(2); // 变成 printf("x2 = %d\n", x2);
PRINT_XN(3); // 变成 printf("x3 = %d\n", x3);
return 0;
}
该程序的输出如下:
x1 = 14
x2 = 20
x3 = 30

  • 注意,PRINT_XN()宏用#运算符组合字符串,##运算符把记号组合为一个新的标识符。

变参宏: ...__VA_ARGS__

stdvar.h 头文件提供了工具,让用户自定义带可变参数的函数。
通过把宏参数列表中最后的参数写成省略号(即,3个点…)来实现这一功能。这样,预定义宏_ _VA_ARGS_可用在替换部分中,表明省略号代表什么。例如,下面的定义:
`#define PR(…) printf(
VA_ARGS _)`
假设稍后调用该宏:

1
2
3
4
5
PR("Howdy");
PR("weight = %d, shipping = $%.2f\n", wt, sp);
对于第1次调用,_ _VA_ARGS_ _展开为1个参数:"Howdy"。
对于第2次调用,_ _VA_ARGS_ _展开为3个参数:"weight = %d,
shipping = $%.2f\n"、wt、sp。

因此,展开后的代码是:
1
2
printf("Howdy");
printf("weight = %d, shipping = $%.2f\n", wt, sp);

宏和函数的选择

宏的一个优点是,不用担心变量类型(这是因为宏处理的是字符串,而不是实际的值)。
对于简单的函数,程序员通常使用宏,如下所示:

1
2
3
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
#define ABS(X) ((X) < 0 ? -(X) : (X))
#define ISSIGN(X) ((X) == '+' || (X) == '-' ? 1 : 0)

(如果x是一个代数符号字符,最后一个宏的值为1,即为真。)
用圆括号把宏的参数和整个替换体括起来。这样能确保被括起来的部分在下面这样的表达式中正确地展开:
forks = 2 * MAX(guests + 3, last);
用大写字母表示宏函数的名称。

预定义宏

预定义宏

内联函数

最简单的方法是使用函数说明符 inline 和存储类别说明符static。通常,内联函数应定义在首次使用它的文件中,所以内联函数也相当于函数原型。
编译器优化内联函数必须知道该函数定义的内容。这意味着内联函数定义与函数调用必须在同一个文件中。最简单的做法是,把内联函数定义放入头文件,并在使用该内联函数的文件中包含该头文件即可。

1
2
3
4
5
6
7
8
9
// eatline.h
#ifndef EATLINE_H_
#define EATLINE_H_
inline static void eatline()
{
while (getchar() != '\n')
continue;
}
#endif

一般都不在头文件中放置可执行代码,内联函数是个特例。因为内联函数具有内部链接,所以在多个文件中定义同一个内联函数不会产生什么问题。

数学库

数学函数

可变参数:stdarg.h

必须按如下步骤进行:

  1. 提供一个使用省略号的函数原型;
  2. 在函数定义中创建一个va_list类型的变量;
  3. 用宏把该变量初始化为一个参数列表;
  4. 用宏访问参数列表;
  5. 用宏完成清理工作。

因为va_arg()不提供退回之前参数的方法,所以有必要保存va_list类型变量的副本。

1
2
3
4
5
6
7
8
9
10
11
va_list ap;                 // 声明一个对象储存参数
va_list apcopy; // 声明一个复制对象储存参数
double
double tic;
int toc;
...
va_start(ap, lim); // 把ap初始化为一个参数列表
va_copy(apcopy, ap); // 把apcopy作为ap的副本
tic = va_arg(ap, double); // 检索第1个参数
toc = va_arg(ap, int); // 检索第2个参数
va_end(ap); // 清理工作

本节主要介绍一些文本处理相关的linux命令,是日后信息安全工作中的情报侦察、数据梳理、日志分析等技术的重要基础。

1. awk 命令

awk是一种处理文本文件的语言,是一个强大的文本分析工具。

相比较屏幕处理的优点,awk在处理庞大文件时不会出现内存溢出或是处理缓慢的问题,通常用来格式化文本信息。 awk也是一个非常棒的数据处理工具!相较于sed常常作用于一整个行的处理, awk则比较倾向于一行当中分成数个字段来处理。

awk处理过程: 依次对每一行进行处理,然后输出。

命令的基本格式是:awk ‘{print($1)}’

案例:

1
2
cat 1.txt|awk '{print($2)}'                 #打印出文本中每行的第二个字段


1
2
cat /etc/passwd |awk -F ":" '{print($1)}'  #-F指定分隔符,以“:”为分隔符,打印出文本中每行第一个字段


1
2
cat /etc/passwd |awk -F ":" '{print($1,"+++",$3)}'   #打印出第一个字段与第三个字段,并在其中间添加+++内容
cat /etc/passwd |awk -F ":" '{print($1"+++"$3)}' #对比差异


1
2
cat passwd | awk -F ":" '{print $NF}'       #打印出文本中每行的最后一个字段
cat passwd | awk -F ":" '{print $(NF-1)}' #打印出文本中每行的倒数第二个字段。$(NF-1)为倒数第二,依此类推。

2. sed 命令

sed 是stream editor(流编辑器)的简称,是一款强大的,并且有些复杂的程序。

sed 本身是一个管线命令,可以将数据进行替换、删除、新增、提取特定行等功能,主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。sed按行来执行命令。

接下来的实验,我们要以/etc/passwd文件内容为例子。

拷贝文件cat /etc/passwd > 1.txt

我们以一个例子来介绍sed命令,sed 's/:/+/g'

s代表搜索,g代表的则是全文。不加g的话则是替换每行第一个出现的。所以这条命令代表的将文件中所有的:替换为+

cat 1.txt |sed 's/:/+/3g' #代表的则是从第三次匹配开始替换,依此类推。

sed -i 's/:/+/g' 1.txt #参数-i,直接修改文件,并非打印出来。

以上这些命令中是以/做为该命令的定界符,如果需要修改或者匹配的字段带有/则不能再使用/做为该条命令的定界符,可以使用任意的定界符,不冲突就行。cat passwd |sed 's!:!/!g' 以!号做为定界也是一样的。

此外,在此命令中,搜索位置使用^代表一行的开头,$代表一行的结尾。sed是以行为单位执行的。

我们用如下案例展示:

cat ip.txt |sed 's#^#http://#g'在每行之前加上http://

cat ip.txt |sed 's#$#:8080#g'在每行末尾加上“:8080”

sed '/^$/d' 2.txt删除空白的行。

sed '/^s/'d 删除文件中所有开头是s的行。

3. grep 命令

grep这个名字来自于短语“global regular expression print”,所以我们能看出 grep 程序和正则表达式有关联。

本质上,grep 程序会在文本文件中查找一个指定的正则表达式,并把匹配行输出到标准输出。

命令格式: grep [options] [pattern] [filename]

案例:

1
2
3
4
5
6
7
8
cat passwd |grep root                          # 只显示带有root关键字的行
grep -e root passwd # 匹配有root的行
grep -v root passwd # 忽略匹配到的行
cat passwd |grep -E "root|sshd" # 包含多个可能性的行
cat passwd |grep -E '^r' # 以r开头的行
cat passwd |grep -E 'n$' # 以n结尾的行
grep “root” /etc/passwd /etc/shadow # 查询多个文件
grep "passwd" /etc -rn # -r 为递归 -n 显示关键字出现在第几行 在多级目录中对文本进行递归查找

4. find 命令

find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则 find 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。

这是一个超级复杂的命令,最简单的模式为:find 路径 -name [名字]

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
find / -size 1000k                #从根目录查找大小为1000K的文件
find -name '1*' #在当前目录下查找文件名以1开始的文件
find / -name '1*' #从根目录查找文件名以1开始的文件
find / -user farmsec1 #从根目录查找属主为farmsec1的文件
find / -group farmsec #从根目录查找属组为farmsec的文件
find /etc -type f/d #按sock类型查找,f代表文件,d代表目录
find . -type f -mtime -7 #搜索7天内当前目录下修改过的文件(-7代表7天内,7代表前7天那一天,+7代表7天前)
find . -type f -mmin -10 #搜索10分钟内当前目录下修改过的文件
find . -atime -1 -type f #搜索当前目录下一天内被访问的文件(-1代表1天内,1代表前1天那一天,+1代表1天前)
find . -amin -10 -type f #搜索当前目录下10分钟内被访问的文件
find . -ctime -1 -type f #搜索当前目录下一天内状态被改变(列如权限)的文件(-1代表1天内,1代表前1天那一天,+1代表1天前)
find . -cmin -10 -type f #搜索当前目录下10分钟内状态被改变的文件
find . -perm 777 #搜索处当前文件下符合777权限的文件

  • exec选项
1
2
3
4
5
6
find 搜索路径 [选项] 搜索内容 -exec 命令2{}\;
find . -type f -perm 644 -exec ls -l {} \;
find / -exec grep "Hello" {} \;

find / -name "*.tmp" -exec rm -f {} \; (危险)
find / -name "*" -ctime +2 -exec rm -f {} \;(危险,别敲)

5. ag 命令

ag类似grepfind,但是执行效率比后两者高。

最基本的用法为ag -g <File Name>,从当前目录寻找文件

案例:

1
2
3
4
5
6
7
8
9
ag -g <File Name>            # 类似于 find . -name <File Name>
ag -i PATTERN # 忽略大小写搜索含PATTERN文本
ag -A [number] PATTERN #搜索含PATTERN文本,并显示匹配内容之后的n行文本,例如:ag -A 5 abc会显示搜索到的包含abc的行以及它之后5行的文本信息。
ag -B [number] PATTERN #搜索含PATTERN文本,并显示匹配内容之前的n行文本
ag -C [number] PATTERN #搜索含PATTERN文本,并同时显示匹配内容以及它前后各n行文本的内容。
ag --ignore-dir <Dir Name> #忽略某些文件目录进行搜索。
ag -w PATTERN #全匹配搜索,只搜索与所搜内容完全匹配的文本。
ag --java PATTERN #在java文件中搜索含PATTERN的文本。
ag --xml PATTERN #在XML文件中搜索含PATTERN的

6.stat命令

用于显示文件信息:

用法:stat [文件或目录]

7. split 命令

split命令可以将一个大文件分割成很多个小文件,有时需要将文件分割成更小的片段,比如为提高可读性,生成日志等。

参数:

  • -b 按文件大小进行切割
  • -l 按行数来进行切割
  • -d 为使用数字为生成文件的后缀
  • -a 指定后缀的长度

案例:

1
2
split -b 30k messages    # 按照每个文件30K大小切割messages文件。
split -l 300 messages # 将messages文件切割为每3行一份。

8.sort命令

sort命令是在Linux里非常有用,它将文件进行排序,并将排序结果标准输出。

1
2
3
cat 1.txt|sort            #默认的排序方式,从首字母开始
cat 1.txt|sort -t #按照字典进行排序
cat 1.txt|sort -n #按照数字进行排序

9. uniq 命令

uniq用于报告或忽略文件中的重复行,一般与sort命令结合使用。

要注意先排序,后去重。因为uniq命令只能消除相邻且相同的行。

1
2
3
cat 1.txt |sort -n |uniq              #排序去重
cat 1.txt|sort -n |uniq -c #列出重复的次数
cat 1.txt |sort -n |uniq -d #列出有哪些行是重复的

以上系列命令可以用于日志分析:

如统计IP地址的访问数量并按照数量进行排序: cat access_log |awk '{print($1)}'|sort|uniq -c |sort -nr |more

针对访问量最大的IP分析其访问内容: cat access_log |grep 'IP地址'|head -n 100

10. nl 命令

nl命令用于显示文件内容行号。

案例:

11.远程连接命令

11.1 ssh命令

ssh命令是Linux的远程连接工具。

ssh命令:Linux的远程连接工具

例如ssh 192.168.0.149,以当前终端用户身份远程连接IP为192.168.0.149的计算机。

  • -l 为指定用户 如 ssh -l farmsec 192.168.0.149以farmsec用户远程登录,也可写成ssh farmsec@192.168.0.149
  • -p 为指定端口号

执行ssh的默认情况下,会使用本地的用户名连接对方的用户名,例如本地的root连接对面服务器的root,如果两端用户名不一样,则需要使用-l username指定用户名。

再确认密码后,方能登录成功,而同时变化的文件为:/root/.ssh/known_hosts 在第一次登录时,openssh将会提示不知道这台登录的主机,只要输入yes,就会把这台主机的“识别信息”添加到known_hosts文件中. 第二次登陆后,则无须此步骤,但是如果出现错误,例如主机产生变化,则需要删除/root/.ssh/known_hosts内容。

11.2 linux下远程连接smb

Smb服务为网络文件共享协议,它允许应用程序和终端用户从远端的文件服务器访问文件资源 用法:在kali的文件–>其他位置-->连接到服务器处输入:smb://192.168.0.149/share

关于此部分知识,还将在后续有关windows的章节中详述。

11.3 rdesktop 命令

rdesktop命令用于在linux下链接windows远程桌面。

用法:rdesktop 192.168.0.178

关于此部分知识,还将在后续有关windows的章节中详述。

12. URL 相关命令

URL,既是俗称的网址/网页链接。

12.1 curl 命令

curl命令是一个利用URL规则在命令行下工作的文件传输工具。它支持文件的上传和下载,所以是综合传输工具,但按传统,习惯称curl为下载工具。作为一款强力工具,curl支持包括HTTP、HTTPS、ftp等众多协议,还支持POST、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征。

用法:curl [选项] [url]

参数:

  • -i 显示头部信息
  • -v 显示请求全过程解析
  • -O 下载

案例:

1
2
3
4
curl www.farmsec.com
curl www.farmsec.com -i
curl www.farmsec.com -v
curl -O https://bootstrap.pypa.io/get-pip.py

12.2 wget 命令

wget命令:用来从指定的URL下载文件。wget非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性,如果是由于网络的原因下载失败,wget会不断的尝试,直到整个文件下载完毕。如果是服务器打断下载过程,它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。

1
2
wget https://bootstrap.pypa.io/get-pip.py

前面我们下载下的文件名都是乱码,所以我们需要给它自定义文件名 加入-O的参数即可指定文件名

1
2
wget  https://bootstrap.pypa.io/get-pip.py -O 123.py

如果文件比较大时,加入参数-b,进行后台下载,然后可使用使用tail -f wget-log查看进度。

1
2
-c:继续执行上次终端的任务;可以在下载中断后再次使用。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

必记

  • -u URL, —url=URL Target URL (e.g. “http://www.site.com/vuln.php?id=1“)
  • -r REQUESTFILE Load HTTP request from a file
  • -m BULKFILE Scan multiple targets given in a textual file
  • —cookie=COOKIE HTTP Cookie header value (e.g. “PHPSESSID=a8d127e..”)
  • —mobile Imitate smartphone through HTTP User-Agent header
  • —random-agent Use randomly selected HTTP User-Agent header value
  • —proxy=PROXY Use a proxy to connect to the target URL

`的使用

  • —dbms=DBMS Force back-end DBMS to provided value

  • —prefix=PREFIX Injection payload prefix string

  • —suffix=SUFFIX Injection payload suffix string

  • —technique=TECH.. SQL injection techniques to use (default “BEUSTQ”)

  • —batch Never ask for user input, use the default behavior

意外情况

  • —level=LEVEL Level of tests to perform (1-5, default 1)
  • —risk=RISK Risk of tests to perform (1-3, default 1)

  • —second-url=SEC.. Resulting page URL searched for second-order response

  • —chunked Use HTTP chunked transfer encoded (POST) requests

  • —hpp Use HTTP parameter pollution method

  • —tamper=TAMPER Use given script(s) for tampering injection data

结尾

  • —is-dba Detect if the DBMS current user is DBA

  • —dbs Enumerate DBMS databases

  • —current-user Retrieve DBMS current user

  • —current-db Retrieve DBMS current database

  • -D -T -C —dump

  • —os-shell Prompt for an interactive operating system shell

网站time

  • —delay=DELAY Delay in seconds between each HTTP request
  • —timeout=TIMEOUT Seconds to wait before timeout connection (default 30)

  • —time-sec=TIMESEC Seconds to delay the DBMS response (default 5)


sqlmap 参数分类

能够发包 —> 构造http请求

  • -u
  • -r
  • —data
  • -m
  • —cookie

-u “url” —cookie
-r cookie
-u —cookie=” “ —data=” “

提高测试效率

-p --dbms

应对特殊参数情况

time tamper

数据解决

—dbs

1
2
3
-D xxx --tables
-D xxx -T xxx --columns
-D xxx -T xxx -C xxx,xxx --dump

—is-dba —currxxxx-user

nmap 常用命令

必要命令

-iL xx.txt

-sV # 服务版本

-p # 端口

-A -Pn 0-65536 # -A 全扫描 -Pn 直接发包
-iL xxx.txt

其他命令

-sP # 探测此网段的存活主机 可以连同 | grep nmap 一起使用 这样看起来更清爽一些

-p 80 # 检测特定端口

-sT # 检测TCP端口

-sU # 检测UDP端口

-sV # 探测端口的服务和版本

-O # 探测操作系统的类型和版本

-Pn # 直接进行深度测试,不检测主机存活

组合技

1
2
3
masscan -p 1-65535 192.168.0.0/24 --rate=2000 >>ports.txt
cat ports.txt |awk '{print($6":"$4)}'|sed 's#/tcp##g' >>portclean.txt
nmap -sV -p 35386 192.168.0.