资料来源:南科大 于仕琪 C/C++ Program Design
LINK:CPP/week04 at main · ShiqiYu/CPP · GitHub
一、本节内容
1. Makefile的使用
1.1 Makefile简介
- Makefile是一个简化和组织编译的工具。
- Makefile是一组带有变量名和目标的命令,可以使用Makefile编译您的项目(程序)或仅编译项目中的更新文件。
- 当存在非常多的文件需要一起编译时,使用Makefile是一个很好的选择

1.2 Makefile中的宏/变量定义



1.3 Makefile中的函数
- wildcard: 查找文件
- patsubst:替换文件

1.4 总结:通用的makefile指令模板
# Using functions
SOURCE = $(wildcard ./*.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SOURCE))
TARGET = #Enter target name here
CC = g++
CFLAGS = -c -Wall
$(TARGET):$(OBJS)
$(CC) -o $@ $(OBJS)
%.o: %.cpp
$(CC) $(CFLAGS) $< -o $@
.PHONY:clean
clean:
rm -f *.o $(TARGET)
2. 输入与输出指令
C: scanf & printf | scanf使用空格、tab、和回车分割字符(是否结束) |
C: gets & puts | gets()可以读入含有空格的句子,同时以回车判读是否结束 |
C++: cin & cout | cin使用空格、tab、和回车分割字符(是否结束) |
C++: cin.getline( ) & cin.get( ) | getline()和get()都读取整个输入行,直到换行符终止读取。但是,getline()遇到换行符后会在输入队列中丢弃换行符,而get()会将其保留在输入队列中。 |
二、习题笔记
习题1
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
int cards[4]{};
int hands[4];
double price[] = {2.8,3.7,5,9};
char direction[4] {'L',82,'U',68};
char title[] = "ChartGPT is an awesome tool.";
cout << "sizeof(cards) = " << sizeof(cards)
<< ",sizeof of cards[0] = " << sizeof(cards[0]) << endl;
cout << "sizeof(price) = " << sizeof(price)
<< ",sizeof of price[0] = " << sizeof(price[1]) << endl;
cout << "sizeof(direction) = " << sizeof(direction)
<< ",length of direction = " << strlen(direction) << endl;
cout << "sizeof(title) = " << sizeof(title)
<< ",length of title = " << strlen(title) << endl;
//Print the value and address of each element in cards and hands respectively.
int i;
for (i = 0; i < 4; i++)
{
printf("value of cards[%d] is %d,\t address is %p\n", i, cards[i], &cards[i]);
}
for (i = 0; i < 4; i++)
{
printf("value of hands[%d] is %d,\t address is %p\n", i, hands[i], &hands[i]);
}
return 0;
}
Tips:
- 输出地址:%p,变量加&。参考文档:c语言:编辑程序输出取数组地址_输出数组的地址c语言-优快云博客
- 遍历输出数组,含有每次标号的,用printf最便利
- cards定义的数组没有填写具体的值,便默认为0;hands只是定义了数组,初始值为随机的
习题2
知识点:字节序
字节序(Endianness)是指在计算机内存中多字节数据(例如整数、浮点数)的存储顺序。具体来说,它涉及到字节在内存中的排列方式。
有两种常见的字节序类型:
- 小端序(Little-endian):在小端序中,最低有效字节(Least Significant Byte,LSB)存储在最低内存地址处,而最高有效字节(Most Significant Byte,MSB)存储在最高内存地址处。这意味着数据的低位字节排在前面。
- 大端序(Big-endian):在大端序中,最高有效字节(MSB)存储在最低内存地址处,而最低有效字节(LSB)存储在最高内存地址处。这意味着数据的高位字节排在前面。
假设我们有一个16位整数,其十六进制表示为0x1234
。在小端序中,它在内存中的存储方式如下:
低地址 -> 0x34
高地址 -> 0x12
而在大端序中,它在内存中的存储方式如下:
低地址 -> 0x12
高地址 -> 0x34
字节序在计算机体系结构、网络通信和文件格式中都非常重要。例如,网络协议通常需要指定数据的字节序,以确保不同计算机之间的数据传输正确解释。
需要注意的是,不同的体系结构和操作系统可能使用不同的字节序。因此,在处理二进制数据时,我们需要了解所使用的字节序,以避免出现错误。
运行结果:
原理分析:
这段 C++ 代码使用了联合体(union),工作原理如下:
- 首先,定义了一个名为
data
的联合体。联合体允许在相同的内存位置存储不同类型的数据,但同一时刻只能使用其中一个成员。 data
联合体有三个成员:int n
:整数类型。char ch
:字符类型。short m
:短整数类型。- 在32/64位系统中,char 占1 byte,int 占 4 byte, short 占 2 byte。可参考:int、long、long int、long long、uint64_t字节长度-优快云博客
- 在
main
函数中,我们创建了一个名为a
的data
联合体变量。 - 我们使用
sizeof
运算符来获取a
的大小(字节数),并打印出来。注意,联合体的大小等于其最大成员的大小,因为它们共享相同的内存空间。关于联合体、结构体的大小计算参考:关于结构体、联合体大小的计算-优快云博客 - 接下来,我们对
a
的成员进行赋值和打印:a.n = 0x40;
:将整数成员n
设置为十六进制值0x40
。a.ch = '9';
:将字符成员ch
设置为字符'9'
。a.m = 0x2059;
:将短整数成员m
设置为十六进制值0x2059
。a.n = 0x3E25AD54;
:将整数成员n
设置为十六进制值0x3E25AD54
。
- 每次设置成员后,我们使用
printf
打印出a.n
、a.ch
和a.m
的值。
现在,让我们逐行分析输出结果:
sizeof(a)
和sizeof(union data)
都是 4 字节,因为int
是联合体中最大的成员且是最小成员char的倍数,因此首先输出两个4- 第二次打印:
0x40, @, 40
。a.n
的十六进制值是0x40
,对应的 ASCII 字符是'@'
,a.m
的十六进制值是0x40
。 - 第三次打印:
0x39, 9, 39
。a.ch
的值是字符'9'
,对应的 ASCII 值是0x39
,a.m
的十六进制值是0x39
。 - 第四次打印:
0x2059, Y, 2059
。a.m
的十六进制值是0x2059,a.n
的十六进制值也是0x2059,a.ch
的十六进制值是0x59(因为只有1个byte,20超出范围被舍弃了),
对应的 ASCII 字符是'Y'。
- 第五次打印:
0x3E25AD54, T, AD54
。a.n
的十六进制值是0x3E25AD54
,a.ch
的十六进制值是0x54(因为只有1个byte,;剩余的超出范围被舍弃了),
对应的 ASCII 字符是'T'
,a.m
的十六进制值是0xAD54(剩余的超限被舍弃)
。 - 由此可以看出是小端序(Little-endian)
总结:这段代码展示了联合体的特性,以及在不同成员之间共享内存的能力。需要注意的是,联合体的使用需要谨慎,因为它涉及到不同类型之间的内存共享,可能导致未定义行为。
习题3
#include <iostream>
using namespace std;
enum class Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};
enum class Weather {
Sunny,
Rainy,
Cloudy,
Snowy
};
struct DayInfo {
Day day;
Weather weather;
};
bool canTravel(DayInfo dayInfo) {
return (dayInfo.day == Day::Saturday || dayInfo.day == Day::Sunday) &&
dayInfo.weather == Weather::Sunny;
// 以一种简单的方式实现单一 return
}
// 将字符串映射到 Day 枚举
Day stringToDay(const string& dayStr) {
if (dayStr == "Sunday") return Day::Sunday;
if (dayStr == "Monday") return Day::Monday;
if (dayStr == "Tuesday") return Day::Tuesday;
if (dayStr == "Wednesday") return Day::Wednesday;
if (dayStr == "Thursday") return Day::Thursday;
if (dayStr == "Friday") return Day::Friday;
if (dayStr == "Saturday") return Day::Saturday;
// 如果输入无效,返回默认值
return Day::Sunday;
}
// 将字符串映射到 Weather 枚举
Weather stringToWeather(const string& weatherStr) {
if (weatherStr == "Sunny") return Weather::Sunny;
if (weatherStr == "Rainy") return Weather::Rainy;
if (weatherStr == "Cloudy") return Weather::Cloudy;
if (weatherStr == "Snowy") return Weather::Snowy;
// 如果输入无效,返回默认值
return Weather::Sunny;
}
int main() {
DayInfo today;
cout << "请输入今天的星期(英文):" << endl;
string dayInput;
cin >> dayInput;
today.day = stringToDay(dayInput);
cout << "请输入今天的天气(英文):" << endl;
string weatherInput;
cin >> weatherInput;
today.weather = stringToWeather(weatherInput);
if (canTravel(today)) {
cout << "今天可以出行!" << endl;
}
else {
cout << "今天不适合出行。" << endl;
}
return 0;
}
Tips:
1. enum枚举类型不能采用cin直接输入,需要编写映射函数或采用强制类型转换。参考:C语言枚举类型(enum)的各种用法_枚举类型enum用法-优快云博客
2. 映射函数中,由于只需要读取dayStr或weatherStr的值,因此采用const string& 的方式进行定义更好。
const string& dayStr
:
const string&
表示一个对字符串的引用。- 引用是一个别名,它指向实际存储的字符串。
- 使用引用可以避免复制大型字符串,提高性能。
- 如果函数内部不需要修改字符串,使用引用是一种好的选择。
习题4
main.cpp
#include <iostream>
#include "fib.hpp"
using namespace std;
int main() {
int n;
do
{
cout << "请输入一个正整数 n: ";
cin >> n;
}while (n <= 0 );
cout << "斐波那契数列前 " << n << " 个数:\n";
for (int i = 1; i <= n; i++)
{
cout << fibonacci(i) << " ";
if (i % 10 == 0)
{
cout << "\n";
}
else if (i == n)
{
cout << "\n";
}
}
return 0;
}
fib.cpp
int fibonacci(int n) {
if (n <= 1)
return n;
int a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}
fib.hpp
#pragma once
int fibonacci(int n);
makefile
# Using functions
SOURCE = $(wildcard ./*.cpp)
OBJS = $(patsubst %.cpp, %.o, $(SOURCE))
TARGET = main #change here
CC = g++
CFLAGS = -c -Wall
$(TARGET):$(OBJS)
$(CC) -o $@ $(OBJS)
%.o: %.cpp
$(CC) $(CFLAGS) $< -o $@
.PHONY:clean
clean:
rm -f *.o $(TARGET)