'0'=48,'1'=49……字符相减即得数值。限制:输入 12 只取首字符 '1',多位数结果错误。
#include <stdio.h>
int main(int argc, char const* argv[]) {
if (argc < 3) { printf("用法: add <数字1> <数字2>\n"); return 1; }
int a = *argv[1] - '0'; // '3'-'0' = 51-48 = 3
int b = *argv[2] - '0'; // '5'-'0' = 53-48 = 5
printf("%d\n", a + b);
return 0;
}
add 3 5 → 8 add 12 34 → 4(错误:仅取首字符 '1'+'3' = 1+3)
可处理多位整数,但输入 abc 时静默返回 0,无法区分错误与合法零值。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const* argv[]) {
if (argc < 3) { printf("用法: add <数字1> <数字2>\n"); return 1; }
printf("%d\n", atoi(argv[1]) + atoi(argv[2]));
return 0;
}
add 123 456 → 579
endptr 指向转换停止处:若不是 '\0',说明含非数字字符。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const* argv[]) {
if (argc < 3) { printf("用法: add <数字1> <数字2>\n"); return 1; }
char *end1, *end2;
long a = strtol(argv[1], &end1, 10);
long b = strtol(argv[2], &end2, 10);
if (*end1 != '\0' || *end2 != '\0') {
printf("输入错误:请输入整数\n"); return 1;
}
printf("%ld\n", a + b);
return 0;
}
add 123 456 → 579
add 123abc 456 → end1 指向 'a',*end1 != '\0' 触发 → 输入错误:请输入整数
int main(int argc, char const* argv[])
| 参数 | 类型 | 全称 | 含义 |
|---|---|---|---|
argc | int | argument count | 命令行参数个数(含程序名,至少为 1) |
argv | char** | argument vector | 指向字符串指针数组的指针 |
执行 add 123 456 时:
argc=3 argv[0]="add" argv[1]="123" argv[2]="456" argv[3]=NULL
argv[0] 是程序名,用户参数从 argv[1] 开始。| 写法 | 类型 | 值 | 说明 |
|---|---|---|---|
argv | char** | 指针数组首地址 | 指向存放 argv[0..3] 的内存块 |
argv[1] | char* | 数组中第二个元素值 | 第二格指针,指向字符串 "123" |
argv[1][0] | char | '1'(49) | 首字符,下标写法 |
*argv[1] | char | '1'(49) | 同上,解引用写法,完全等价 |
// 下标写法 // 指针算术写法(完全等价) argv[1][0] → '1' (49) *(argv[1] + 0) → '1' (49) argv[1][1] → '2' (50) *(argv[1] + 1) → '2' (50) argv[1][2] → '3' (51) *(argv[1] + 2) → '3' (51) argv[1][3] → '\0' ( 0) *(argv[1] + 3) → '\0' ( 0) ← 结束符
a[i] 即 *(a+i) 的语法糖,编译后机器码完全相同。argv[1]+1 将指针后移 1 字节再解引用。操作系统不直接调用 main(),中间有一层 C 运行时库启动代码(CRT),由编译器自动链接。
argv[argc] 必须为 NULL——与字符串以 '\0' 结尾同一哲学,哨兵值标记边界,遍历无需额外长度。所有平台(MSVC / GCC / Clang)均遵守。// 可不用 argc,靠 NULL 判断结束
char **p = argv;
while (*p != NULL) { printf("%s\n", *p); p++; }
| 步骤 | 操作 |
|---|---|
| 1 | 右键项目名 → Properties(属性) |
| 2 | 左侧选 Configuration Properties(配置属性)→ Debugging(调试) |
| 3 | 找到 Command Arguments(命令参数),填入 123 456,点 OK,按 F5 或 Ctrl+F5 运行 |
argv[0],Command Arguments 只需填 argv[1] 起的内容,参数间用空格分隔。| 特性 | atoi | strtol |
|---|---|---|
| 签名 | int atoi(const char*) | long strtol(const char*, char**, int) |
| 错误检测 | ❌ 无,静默返回 0 | ✅ endptr 可检测非法字符 |
| 溢出处理 | ❌ 未定义行为 | ✅ 返回 LONG_MAX/MIN,设 errno |
| 进制 | ❌ 仅十进制 | ✅ 2–36 进制,base=0 自动检测 |
| 适用 | 快速原型 | 生产代码(推荐) |
long strtol(const char *str, char **endptr, int base); // str 要转换的字符串 // endptr 转换结束后指向第一个非数字字符;不需要传 NULL // base 进制:10=十进制,0=自动检测前缀(0x→16, 0→8)
strtol("0xff", NULL, 0); // → 255(十六进制)
strtol("077", NULL, 0); // → 63(八进制)
strtol("99", NULL, 0); // → 99(十进制)
核心原因:strtol 需要把一个 char* 的值写回给调用者,而在 C 里,要修改调用者的变量,必须传入该变量的地址。
// 想让函数修改调用者的 int,要传 int*
void set(int *p) { *p = 42; }
int x = 0;
set(&x); // 传地址
printf("%d", x); // 42
调用者声明了 char *end,要让 strtol 能写入它,就传 &end,类型是 char**:
long strtol(const char *str, char **endptr, int base) {
const char *p = str;
// ... 解析数字,p 移到第一个非数字字符 ...
if (endptr != NULL)
*endptr = (char *)p; // 写回调用者的 end 变量
return result;
}
p 的类型是 const char*,而 *endptr 的类型是 char*,两者带不带 const 不一样,直接赋值会丢掉 const:
*endptr = p; // ❌ const char* 赋给 char*,丢掉 const
// 编译器警告:discards 'const' qualifier
*endptr = (char *)p; // ✅ 强制转换,显式告知编译器「我知道在丢 const,故意的」
str 声明为 const char*,承诺函数内部只读不写字符串——这是对的。endptr 回传给调用者,调用者拿到后可能需要修改字符串,所以回传 char* 而非 const char*。(char*) 强转是必要的妥协。
endptr 是函数的局部变量,存的是调用者 &end 传进来的地址。修改 endptr 本身只改了局部变量,函数返回后消失;必须解引用才能写回调用者的 end。
// 调用者
char *end;
strtol("123abc", &end, 10); // 传入 &end,函数内 endptr = &end
// ❌ endptr = (char*)p
// 只修改了函数内部的局部变量 endptr 本身
// 函数返回后 endptr 消失,调用者的 end 完全没被动
// ✅ *endptr = (char*)p
// 解引用 endptr,找到调用者的 end 变量
// 把值写进去,调用者能看到变化
endptr 存的是 0x5000(即 end 的地址) endptr = (char*)p → 把局部变量 endptr 改成其他地址 → 0x5000 处的 end 没有被碰到 ← 调用者看不到任何变化 *endptr = (char*)p → 去 0x5000 那个地址 → 把那里的内容改掉 ← end 被改掉,调用者能看到
int x = 0; int *ptr = &x; ptr = 42; // ❌ 类型错误,且即使能赋值也只改了 ptr 本身,x 不变 *ptr = 42; // ✅ 通过地址找到 x,把 x 改成 42
T 的变量,参数必须是 T*,函数内部用 *param = 值 写回。T = char*,参数是 char**,写回用 *endptr = (char*)p。endptr = p 改的是指针本身,不是指针指向的内容,两者本质不同。