话说C语言的const

话说C语言的const

const在C语言中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰
的对象为常量(immutable)。

我们来分情况看语法上它该如何被使用。

1、函数体内修饰局部变量。
例:
void func(){
const int a=0;
}

首先,我们先把const这个单词忽略不看,那么a是一个int类型的局部自动变量,
我们给它赋予初始值0。

然后再看const.

const作为一个类型限定词,和int有相同的地位。
const int a;
int const a;
是等价的。于是此处我们一定要清晰的明白,const修饰的对象是谁,是a,和int没
有关系。const 要求他所修饰的对象为常量,不可被改变,不可被赋值,不可作为左值(l-value)。
这样的写法也是错误的。
const int a;
a=0;
这是一个很常见的使用方式:
const double pi=3.14;
在程序的后面如果企图对pi再次赋值或者修改就会出错。

然后看一个稍微复杂的例子。
const int* p;
还是先去掉const 修饰符号。
注意,下面两个是等价的。
int* p;
int *p;
其实我们想要说的是,*p是int类型。那么显然,p就是指向int的指针。
同理
const int* p;
其实等价于
const int (*p);
int const (*p);
即,*p是常量。也就是说,p指向的数据是常量。
于是
p+=8; //合法
*p=3; //非法,p指向的数据是常量。

那么如何声明一个自身是常量指针呢?方法是让const尽可能的靠近p;
int* const p;
const右面只有p,显然,它修饰的是p,说明p不可被更改。然后把const去掉,可以
看出p是一个指向 int形式变量的指针。
于是
p+=8; //非法
*p=3; //合法

再看一个更复杂的例子,它是上面二者的综合
const int* const p;
说明p自己是常量,且p指向的变量也是常量。
于是
p+=8; //非法
*p=3; //非法

const 还有一个作用就是用于修饰常量静态字符串。
例如:
const char* name=David;
如果没有const,我们可能会在后面有意无意的写name[4]='x'这样的语句,这样会
导致对只读内存区域的赋值,然后程序会立刻异常终止。有了 const,这个错误就
能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译
期被发现。

const 还可以用来修饰数组
const char s[]=David;
与上面有类似的作用。

2、在函数声明时修饰参数
来看实际中的一个例子。
NAME
memmove -- copy byte string

LIBRARY
Standard C Library (libc, -lc)

SYNOPSIS
#include

void *
memmove(void *dst, const void *src, size_t len);

这是标准库中的一个函数,用于按字节方式复制字符串(内存)。
它的第一个参数,是将字符串复制到哪里去(dest),是目的地,这段内存区域必须
是可写。
它的第二个参数,是要将什么样的字符串复制出去,我们对这段内存区域只做读
取,不写。
于是,我们站在这个函数自己的角度来看,src 这个指针,它所指向的内存内所存
储的数据在整个函数执行的过程中是不变。于是src所指向的内容是常量。于是就
需要用const修饰。
例如,我们这里这样使用它。
const char* s=hello;
char buf[100];
memmove(buf,s,6); //这里其实应该用strcpy或memcpy更好

如果我们反过来写,
memmove(s,buf,6);
那么编译器一定会报错。事实是我们经常会把各种函数的参数顺序写反。事实是编
译器在此时帮了我们大忙。如果编译器静悄悄的不报错,(在函数声明处去掉
const即可),那么这个程序在运行的时候一定会崩溃。

这里还要说明的一点是在函数参数声明中const一般用来声明指针而不是变量本身。
例如,上面的size_t len,在函数实现的时候可以完全不用更改len的值,那么是否
应该把len也声明为常量呢?可以,可以这么做。我们来分析这么做有什么优劣。
如果加了const,那么对于这个函数的实现者,可以防止他在实现这个函数的时候修
改不需要修改的值(len),这样很好。
但是对于这个函数的使用者,
1。这个修饰符号毫无意义,我们可以传递一个常量整数或者一个非常量整数过
去,反正对方获得的只是我们传递的一个copy。
2。暴露了实现。我不需要知道你在实现这个函数的时候是否修改过len的值。

所以,const一般只用来修饰指针。

再看一个复杂的例子
int execv(const char *path, char *const argv[]);
着重看后面这个,argv.它代表什么。
如果去掉const,我们可以看出
char * argv[];
argv是一个数组,它的每个元素都是char *类型的指针。
如果加上const.那么const修饰的是谁呢?他修饰的是一个数组,argv[],意思就是
说这个数组的元素是只读的。那么数组的元素的是什么类型呢?是char *类型的指
针.也就是说指针是常量,而它指向的数据不是。
于是
argv[1]=NULL; //非法
argv[0][0]='a'; //合法


3、全局变量。
我们的原则依然是,尽可能少的使用全局变量。
我们的第二条规则 则是,尽可能多的使用const。
如果一个全局变量只在本文件中使用,那么用法和前面所说的函数局部变量没有什
么区别。
如果它要在多个文件间共享,那么就牵扯到一个存储类型的问题。

有两种方式。
1.使用extern
例如
/* file1.h */
extern const double pi;
/* file1.c */
const double pi=3.14;
然后其他需要使用pi这个变量的,包含file1.h
#include file1.h
或者,自己把那句声明复制一遍就好。
这样做的结果是,整个程序链接完后,所有需要使用pi这个变量的共享一个存储区域。

2.使用static,静态外部存储类
/* constant.h */
static const pi=3.14;
需要使用这个变量的*.c文件中,必须包含这个头文件。
前面的static一定不能少。否则链接的时候会报告说该变量被多次定义。
这样做的结果是,每个包含了constant.h的*.c文件,都有一份该变量自己的copy,
该变量实际上还是被定义了多次,占用了多个存储空间,不过在加了static关键字
后,解决了文件间重定义的冲突。
坏处是浪费了存储空间,导致链接完后的可执行文件变大。但是通常,这个,小小
几字节的变化,不是问题。
好处是,你不用关心这个变量是在哪个文件中被初始化的。


最后,说说const的作用。
const 的好处,是引入了常量的概念,让我们不要去修改不该修改的内存。直接的
作用就是让更多的逻辑错误在编译期被发现。所以我们要尽可能的多使用const。
但是很多人并不习惯使用它,更有甚者,是在整个程序 编写/调试 完后才补
const。如果是给函数的声明补const,尚好。如果是给 全局/局部变量补const,那
么……那么,为时已晚,无非是让代码看起来更漂亮了。关于const的使用,曾有一
个笑话说,const 就像安全套,事前要记牢。如果做完后才想起来该用而忘了用,
呵呵……呵呵……

<think> 我们已知B进制加法的要求: 输入:第一行是一个十进制整数B(表示进制,2<=B<=16) 第二行是一个B进制数 第三行是另一个B进制数 输出:两个B进制数的和(用B进制表示) 注意:两个数都是非负整数,且位数不超过1000。 思路: 1. 由于输入的B进制数可能包含字母(当B>10时,用A、B、C等表示10、11、12等),所以我们需要先将这两个字符串形式的B进制数转换为十进制整数(或者直接进行字符串相加,但考虑到位数可能很大,所以我们使用字符串模拟加法,因为可能超过long long的范围)。 2. 但是题目中要求位数不超过1000,所以转换成十进制整数可能会溢出(因为最大可能是一个1000位的16进制数,转换成十进制会非常大),因此我们应该用字符串模拟加法。 步骤: 1. 读取进制B。 2. 读取两个字符串num1和num2。 3. 将两个字符串反转(从低位开始相加)。 4. 模拟加法,注意进位,以及每一位的值不能超过B(因为是B进制),同时注意处理字母: 0-9用数字字符,10-15用'A'-'F'表示。 5. 最后将结果反转。 具体实现: 1. 定义一个数组来存放结果,长度设为1005(因为最多1000位,相加可能进位,所以多开一点)。 2. 将两个字符串反转,然后从低位到高位逐位相加,同时加上进位carry。 3. 每一位相加的结果:sum = digit1 + digit2 + carry。 然后当前位的结果为sum % B,进位carry = sum / B。 4. 注意:将字符转换为数字:如果是'0'-'9',则减去'0';如果是'A'-'F'(或小写字母),则转换为10-15(注意题目输入可能是大写,但为了统一,我们可以用大写输出)。 5. 最后,如果最高位还有进位,则要加上进位。 注意:输入时可能字符串有前导0,但题目说是正整数,所以不会有负号,但可能有前导0?不过按照常规,我们不去管前导0,因为最后输出时如果有多余的前导0要去掉(但题目要求输出结果,所以如果结果是0就输出0,否则不能有前导0)。 但是,题目要求:输入的是B进制正整数,所以不会有前导0?但为了安全,我们在相加后,从结果的高位开始,去掉前导0,但要注意如果全部是0,则要保留一个0。 步骤: 1. 反转两个字符串(也可以不反转,但通常从末尾开始加,反转后可以从0下标开始加)。 2. 将两个字符串补零到相同长度(或者处理到两个字符串都结束为止)。 3. 逐位相加。 例如: 输入: 8 123 456 反转: num1: "321" num2: "654" 然后从0开始逐位相加,进位初始为0。 考虑字母转换的函数: 字符转数字:写一个函数,将字符转换为对应的数字(0-15)。 数字转字符:将0-15转换为对应的字符(0-9和A-F)。 具体代码步骤: 1. 读入进制B,然后读入两个字符串(注意过滤可能的空白字符,如换行?)。 2. 反转两个字符串。 3. 初始化一个结果数组(字符数组),长度1005,全部初始化为0(或'\0')。 4. 初始化进位carry=0。 5. 从0到max(len1, len2)循环: 取出num1的第i位(如果已经超出,则为0),同样取出num2的第i位。 将这两个字符转换为数字。 计算和:sum = digit1 + digit2 + carry 当前位的结果为sum % B,进位carry = sum / B 将当前位的结果转换为字符,存入结果数组的相应位置(从0开始存,最后需要反转结果)。 6. 循环结束后,如果进位不为0,则再添加一位。 7. 反转得到的结果字符串。 8. 去除前导0(注意:如果结果就是0,那么保留一个0)。 9. 输出结果字符串。 注意:转换函数: 字符转数字: int charToDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; return 0; // 如果出现非法字符,按0处理(题目保证是B进制数,所以不会出现非法字符,但为了安全) } 数字转字符: char digitToChar(int num) { if (num >=0 && num <=9) return num + '0'; if (num >=10 && num <=15) return num - 10 + 'A'; return '0'; } 但是,题目输入的是B进制正整数,所以输入的字符串中只包含数字和字母(大写或小写?题目没有明确,但通常是大写,但我们也处理小写)。 然而,题目要求输出是B进制数,且字母用大写表示。 我们统一将输入字符串中的字母转换为大写?或者我们的转换函数同时支持大小写。 另外,反转字符串可以用strrev函数(但C标准库没有strrev,所以自己写一个反转函数)。 具体代码: 步骤: 1. 读入进制B。 2. 读入两个字符串,使用fgets(注意去掉换行符)或者scanf(但可能包含空格?题目说正整数,没有空格,所以scanf("%s")可以)。 3. 反转字符串:自己写一个reverse函数。 注意:输入可能有换行符,所以我们在读入进制B后,用getchar吃掉换行?或者用fgets统一读一行。 例如: scanf("%d", &B); getchar(); // 吃掉换行 fgets(num1, 1002, stdin); // 最大1000位,加换行和结束符,所以开1002 // 然后去掉换行符:num1[strcspn(num1, "\n")] = 0; fgets(num2, 1002, stdin); num2[strcspn(num2, "\n")] = 0; 或者: scanf("%d", &B); scanf("%s", num1); scanf("%s", num2); 用scanf更简单,因为题目没有空格。 4. 反转两个字符串: 写一个reverse_string函数,参数为字符串,然后交换第一个和最后一个,直到中间。 5. 开始逐位相加。 6. 注意:结果数组存储的是字符,我们最后需要反转,并且去除前导0,然后输出。 7. 去除前导0:从结果字符串的高位(即反转后的字符串的末尾)开始,但因为我们存储的结果是反的(低位在前),所以反转结果后,前导0在字符串开头。所以我们在反转结果字符串后,再去除前导0。 但是,我们在相加过程中,是从低位到高位存储的(即结果数组的第0位是最低位),所以最后我们需要反转,然后输出。反转后,开头可能有0。 例如:我们得到的结果数组(在反转前)是:['1','0','0'],表示001?但实际上我们相加后得到的是001?不对,应该是100?所以反转后是001,然后我们去掉前导0,变成1?不对,应该是100。 实际上,我们存储的顺序是:先存最低位,再存高位。所以最后需要反转,反转后就是正常顺序(高位在前,低位在后)。然后我们去除前导0(注意不能把最后一位0也去掉了,如果整个字符串都是0,则保留一个0)。 8. 去除前导0:从反转后的结果字符串的第一个字符开始,跳过所有的'0',直到遇到非0字符或者最后一个字符(如果全部是0,那么保留一个0)。注意:如果结果字符串是"000",那么去除前导0后应该是"0"。 实现去除前导0: 用一个指针指向当前第一个非0字符,如果没找到非0字符,就指向最后一个字符(即0字符前一个,但注意字符串结尾有'\0')。 例如: char result[] = "000"; 去除后应为"0"。 我们可以遍历字符串,找到第一个非0的位置,如果直到结尾(遇到'\0')都没有非0,那么保留一个0。 代码: int start = 0; while (result[start] == '0' && result[start+1] != '\0') { // 如果当前是0,并且后面还有字符(不是最后一个字符),就跳过 start++; } 然后输出从start开始的字符串。 但是注意:我们的结果字符串反转后,可能包含多个0,也可能有进位产生的多个0,但进位产生的0我们不需要去掉,因为进位是必要的。去除前导0是在反转之后进行。 步骤总结: a. 反转两个字符串(num1和num2)。 b. 从0到max(len1, len2)循环相加,得到结果字符串(此时是反的,即低位在前)。 c. 将结果字符串反转(变成高位在前)。 d. 去除前导0。 e. 输出。 但是,我们也可以在相加完成后,先去除反转结果中的后导0?不对,因为反转结果中低位在前,所以后面的0实际上是高位的0?不对,反转结果(相加后得到的结果数组,没有反转前)中,先存的是低位,然后依次是高位。所以,如果我们直接去除后导0(即高位0)?不对,因为高位0在数组的后面(比如数组:低位在0,高位在最后)。但是,我们最后反转的时候,高位0会变成前导0?所以还是等反转后再去除前导0。 所以,我们得到结果数组(未反转)后,我们先将其反转,然后去除前导0,然后输出。 但是,在反转之前,我们存储的结果数组是一个字符数组,我们用一个整数记录当前结果的长度(从0到len-1),然后反转这个字符数组(0和len-1交换)? 或者,我们可以这样:在相加过程中,我们得到的结果数组(未反转)的长度为len,那么反转这个数组(0和len-1交换,1和len-2交换,...)后,再去除前导0。 然而,我们在相加过程中,是顺序存储(从0开始存最低位,然后存更高位),所以最后存储的位置是最高位。因此,我们反转这个数组(0和最后一个交换)之后,数组就变成了正常顺序(0位置是最高位,最后位置是最低位)。然后我们去除前导0(即0位置开始连续的0)。 但是,注意:我们最后可能还有进位,所以我们的结果数组长度可能是max(len1,len2)+1。 具体代码: 我们定义: char num1[1005], num2[1005]; // 输入的两个字符串 int B; // 读入 scanf("%d", &B); scanf("%s", num1); scanf("%s", num2); 反转字符串函数: void reverse(char *s) { int len = strlen(s); for (int i = 0, j = len-1; i < j; i++, j--) { char t = s[i]; s[i] = s[j]; s[j] = t; } } 然后反转num1和num2。 然后,我们进行加法: int len1 = strlen(num1); int len2 = strlen(num2); int maxLen = len1 > len2 ? len1 : len2; char result[1005] = {0}; // 初始化为0,用来存未反转的结果(低位在0,高位在后面) int carry = 0; int i; int index = 0; // 结果数组的当前索引 for (i = 0; i < maxLen || carry; i++) { // 注意:如果进位不为0,还要继续 int d1 = i < len1 ? charToDigit(num1[i]) : 0; int d2 = i < len2 ? charToDigit(num2[i]) : 0; int sum = d1 + d2 + carry; carry = sum / B; int digit = sum % B; result[index++] = digitToChar(digit); } // 此时结果存储在result中,长度为index,但这是从低位到高位(即0位置是最低位,index-1是最高位) 然后,我们反转result,得到正常顺序(0位置是最高位,index-1是最低位)?不对,反转后0位置变成原来的最后一位(即最高位),而index-1变成最低位。但我们希望的是0位置是最高位,然后依次是低位。所以反转后,0位置是最高位,这样我们就可以从0位置开始输出(去除前导0后)。 所以,我们反转result(反转后,字符串就是正常顺序了): for (int i = 0, j = index-1; i < j; i++, j--) { char t = result[i]; result[i] = result[j]; result[j] = t; } result[index] = '\0'; // 添加结束符 然后去除前导0: int start = 0; // 如果整个字符串都是0,那么我们要保留一个0 while (result[start] == '0' && start < index-1) { // 注意:如果最后一个字符是0(即整个字符串就是0,那么不能全部去掉,保留一个0) start++; } 最后输出从start开始的字符串:printf("%s\n", result+start); 但是,注意:如果两个数都是0,那么结果就是0,我们上面的去除前导0会保留一个0(因为start会停在最后一个0前?不对,我们看循环条件:当result[start]=='0'并且start还没有到最后一个字符(即index-1)时,继续。所以如果字符串是"000",index=3,那么: start=0:是'0',并且0<2(index-1=2)成立,start++ -> 1 start=1:是'0',并且1<2成立,start++ -> 2 start=2:此时不满足start<2(因为2<2不成立),退出循环,所以start=2,然后输出从2开始,即"0"(因为第三个字符是0,然后第四个字符是'\0')?不对,因为字符串是"000\0",所以result+2就是"0\0",所以输出0。正确。 所以,以上步骤可以。 但是,我们也可以不反转结果数组,而是直接按正常顺序输出(即从最高位到最低位),那么结果数组的最后一个字符(index-1)是最高位?不对,在未反转前,我们存储的顺序是:0位置是最低位,index-1位置是最高位。所以,如果我们不反转,那么输出的时候应该从index-1到0输出,然后再去除前导0?这样比较麻烦。所以还是反转一下方便。 因此,我们选择先反转结果数组,然后去除前导0,然后输出。 注意:反转结果数组时,我们只反转前index个字符,然后加上结束符。 代码编写: 我们按照上述步骤编写代码。 注意:转换函数charToDigit和digitToChar。 测试用例: 输入: 8 123 456 输出: 601 (因为8进制123+456=601) 验证:1*8^2+2*8+3 + 4*8^2+5*8+6 = 5*8^2+7*8+9 = 5*64+56+9 = 320+65=385 385的8进制:385/8=48余1,48/8=6余0,6/8=0余6 -> 601 正确。 另一个测试用例:16进制 输入: 16 A 1 输出: B 再测试:10+1=11,即B。 再测试:有进位 输入: 16 FF 1 输出:100 所以,我们按照上述思路编写代码。 完整代码: </think> # B进制加法程序(C语言实现) 以下是实现B进制加法的C语言程序。该程序处理2-16进制的大数加法,支持数字和字母表示(A-F),能够处理最多1000位的输入。 ```c #include <stdio.h> #include <string.h> #include <ctype.h> #include <stdbool.h> // 将字符转换为对应的数值(支持大写和小写) int charToValue(char c) { if (isdigit(c)) return c - '0'; if (isupper(c)) return c - 'A' + 10; if (islower(c)) return c - 'a' + 10; return 0; // 非法字符视为0 } // 将数值转换为对应的字符(使用大写字母) char valueToChar(int val) { if (val >= 0 && val <= 9) return '0' + val; if (val >= 10 && val <= 15) return 'A' + val - 10; return '0'; // 非法值视为0 } // 反转字符串 void reverseString(char *str, int len) { int start = 0, end = len - 1; while (start < end) { char temp = str[start]; str[start] = str[end]; str[end] = temp; start++; end--; } } // B进制加法函数 void addBBase(int base, const char *num1, const char *num2, char *result) { int len1 = strlen(num1); int len2 = strlen(num2); int maxLen = len1 > len2 ? len1 : len2; // 反转两个数字以便从最低位开始计算 char rev1[1005] = {0}; char rev2[1005] = {0}; strcpy(rev1, num1); strcpy(rev2, num2); reverseString(rev1, len1); reverseString(rev2, len2); int carry = 0; int index = 0; // 逐位相加 for (int i = 0; i < maxLen || carry; i++) { int digit1 = i < len1 ? charToValue(rev1[i]) : 0; int digit2 = i < len2 ? charToValue(rev2[i]) : 0; int sum = digit1 + digit2 + carry; carry = sum / base; result[index++] = valueToChar(sum % base); } // 反转结果得到正确顺序 reverseString(result, index); result[index] = '\0'; // 去除前导0(但保留单个0) int start = 0; while (result[start] == '0' && start < index - 1) { start++; } if (start > 0) { memmove(result, result + start, index - start + 1); } } int main() { int base; char num1[1005], num2[1005], result[2005] = {0}; // 结果数组需要两倍长度以防进位 // 读取输入 printf("输入进制B(2-16): "); scanf("%d", &base); if (base < 2 || base > 16) { printf("错误:进制必须在2-16之间\n"); return 1; } printf("输入第一个B进制数: "); scanf("%1004s", num1); printf("输入第二个B进制数: "); scanf("%1004s", num2); // 执行加法 addBBase(base, num1, num2, result); // 输出结果 printf("加法结果: %s\n", result); return 0; } ``` ## 程序功能说明 1. **输入格式**: - 第一行:进制数B(2-16) - 第二行:第一个B进制数(支持0-9和A-F/a-f) - 第三行:第二个B进制数(支持0-9和A-F/a-f) 2. **输出格式**: - 两个B进制数相加的结果(使用大写字母表示10-15) 3. **核心算法**: - **字符数值转换**:`charToValue()`将字符转换为对应数值 - **数值字符转换**:`valueToChar()`将数值转换为对应字符 - **字符串反转**:`reverseString()`用于低位对齐 - **大数加法**:从最低位开始逐位相加,处理进位 4. **特殊处理**: - 支持大小写字母输入(自动处理为对应数值) - 去除结果中的前导零(保留单个零) - 处理不同位数数字的加法 - 支持最高1000位的大数加法 ## 输入输出示例 **示例1(8进制加法)**: ``` 输入进制B(2-16): 8 输入第一个B进制数: 123 输入第二个B进制数: 456 加法结果: 601 ``` **示例2(16进制加法)**: ``` 输入进制B(2-16): 16 输入第一个B进制数: A1F 输入第二个B进制数: B2E 加法结果: 154D ``` **示例3(二进制加法)**: ``` 输入进制B(2-16): 2 输入第一个B进制数: 1101 输入第二个B进制数: 1011 加法结果: 11000 ``` ## 算法优化说明 1. **时间复杂度**:$O(n)$,其中n是输入数字的最大位数 2. **空间复杂度**:$O(n)$,使用固定大小的辅助数组 3. **边界处理**: - 处理进位导致的结果位数增加 - 正确处理全零结果 - 处理不同长度数字的加法 4. **错误处理**: - 进制范围检查(2-16) - 输入长度限制(最多1000位) - 非法字符处理(视为0) 该程序能够高效处理2-16进制的大数加法,遵循标准的数学加法规则,并确保结果的正确性和格式规范性[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值