位运算
已知:
int i, j, k; i=j=k=3
求下列表达式的结果
~i|i&j
#include <stdio.h>
int main(void)
{
int i, j, k;
i=j=k=3;
printf("%d\n",~i);//-4
//需要注意的是 C语言的位运算符号是针对补码的运算
//其中3的补码等于他的原码 为0000 0011 取反为 1111 1100
//符号位变为负数 由于负数的补码等于反码+1 则 1111 1100的 反码为 1111 1011
//原码为除符号位取反为 1000 0100 值为-4
printf("%d\n",i&j);
//3 由于 i和j值一样因此与运算之后 值不变
printf("%d\n", ~i|i&j);
//-1 上面得到 ~i补码为 1111 1100 则和 3的补码0000 0011或运算 为 1111 1111
//转换为原码为 1000 0001 因此值为 -1
return (0);
}
~取反运算符,是对数值的二进制位进行取反,是第一个单目运算符,因为只有一个运算对象,运算过程是即0变为1,1变为0,非常好理解。
C 运算符的优先级和关联性
printf()
占位符
printf()
可以在输出文本中指定占位符。所谓“占位符”,就是这个位置可以用其他值代入。
%a
:浮点数。%A
:浮点数。%c
:字符。%d
:十进制整数。%e
:使用科学计数法的浮点数,指数部分的e 为小写。%E
:使用科学计数法的浮点数,指数部分的E 为大写。%i
:整数,基本等同于%d 。%f
:小数(包含float 类型和double 类型)。%g
:6个有效数字的浮点数。整数部分一旦超过6位,就会自动转为科学计数法,指数部分的e 为
小写。%G
:等同于%g ,唯一的区别是指数部分的E 为大写。%hd
:十进制 short int 类型。%ho
:八进制 short int 类型。%hx
:十六进制 short int 类型。%hu
:unsigned short int 类型。%ld
:十进制 long int 类型。%lo
:八进制 long int 类型。%lx
:十六进制 long int 类型。%lu
:unsigned long int 类型。%lld
:十进制 long long int 类型。%llo
:八进制 long long int 类型。%llx
:十六进制 long long int 类型。%llu
:unsigned long long int 类型。%Le
:科学计数法表示的 long double 类型浮点数。%Lf
:long double 类型浮点数。%n
:已输出的字符串数量。该占位符本身不输出,只将值存储在指定变量之中%o
:八进制整数。%p
:指针。%s
:字符串。%u
:无符号整数(unsigned int)。%x
:十六进制整数。%zd
: size_t 类型。%%
:输出一个百分号。
输出格式
(1)限定宽度
printf() 允许限定占位符的最小宽度。
printf("%5d\n", 123); // 输出为 " 123"
上面示例中, %5d
表示这个占位符的宽度至少为5位。如果不满5位,对应的值的前面会添加空格。
输出的值默认是右对齐,即输出内容前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,
可以在占位符的% 的后面插入一个- 号。
printf("%-5d\n", 123); // 输出为 "123 "
上面示例中,输出内容123 的后面添加了空格。
对于小数,这个限定符会限制所有数字的最小显示宽度。
// 输出 " 123.450000"
printf("%12f\n", 123.45);
上面示例中,%12f
表示输出的浮点数最少要占据12位。由于小数的默认显示精度是小数点后6位,所以
123.45
输出结果的头部会添加2个空格。
(2)总是显示正负号
默认情况下, printf()
不对正数显示+ 号,只对负数显示-
号。如果想让正数也输出+
号,可以在占位
符的%
后面加一个+
。
上面示例中,%+d
可以确保输出的数值,总是带有正负号。
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
(3)限定小数位数
输出小数时,有时希望限定小数的位数。举例来说,希望小数点后面只保留两位,占位符可以写
成%.2f
。
// 输出 Number is 0.50
printf("Number is %.2f\n", 0.5);
上面示例中,如果希望小数点后面输出3位( 0.500 ),占位符就要写成%.3f 。
这种写法可以与限定宽度占位符,结合使用。
// 输出为 " 0.50"
printf("%6.2f\n", 0.5);
上面示例中, %6.2f
表示输出字符串最小宽度为6,小数位数为2。所以,输出字符串的头部有两个空
格。
最小宽度和小数位数这两个限定值,都可以用* 代替,通过printf() 的参数传入。
printf("%*.*f\n", 6, 2, 0.5);
// 等同于
printf("%6.2f\n", 0.5);
上面示例中, %.f
的两个星号通过printf()
的两个参数6 和2 传入。
(4)输出部分字符串
%s
占位符用来输出字符串,默认是全部输出。如果只想输出开头的部分,可以用%.[m]s
指定输出的长
度,其中[m] 代表一个数字,表示所要输出的长度。
// 输出 hello
printf("%.5s\n", "hello world");
上面示例中,占位符%.5s
表示只输出字符串“hello world”的前5个字符,即“hello”。
转义字符
这种转义的写法,主要用来表示 ASCII 码定义的一些无法打印的控制字符,它们也属于字符类型的值。
\a
:警报,这会使得终端发出警报声或出现闪烁,或者两者同时发生。\b
:退格键,光标回退一个字符,但不删除字符。\f
:换页符,光标移到下一页。在现代系统上,这已经反映不出来了,行为改成类似于\v 。\n
:换行符。\r
:回车符,光标移到同一行的开头。\t
:制表符,光标移到下一个水平制表位,通常是下一个8的倍数。\v
:垂直分隔符,光标移到下一个垂直制表位,通常是下一行的同一列。\0
:null 字符,代表没有内容。注意,这个值不等于数字0。
转义写法还能使用八进制和十六进制表示一个字符。\nn
:字符的八进制写法, nn 为八进制值。\xnn
:字符的十六进制写法, nn 为十六进制值。
char x = 'B';
char x = 66;
char x = '\102'; // 八进制
char x = '\x42'; // 十六进制
上面示例的四种写法都是等价的。、
整数类型的极限值
有时候需要查看,当前系统不同整数类型的最大值和最小值,C 语言的头文件limits.h
提供了相应的
常量,比如SCHAR_MIN
代表 signed char 类型的最小值-128
, SCHAR_MAX
代表 signed char
类型的最大值127 。
SCHAR_MIN
,SCHAR_MAX
:signed char 的最小值和最大值。SHRT_MIN
,SHRT_MAX
:short 的最小值和最大值。INT_MIN
,INT_MAX
:int 的最小值和最大值。LONG_MIN
,LONG_MAX
:long 的最小值和最大值。LLONG_MIN
,LLONG_MAX
:long long 的最小值和最大值。UCHAR_MAX
:unsigned char 的最大值。USHRT_MAX
:unsigned short 的最大值。UINT_MAX
:unsigned int 的最大值。ULONG_MAX
:unsigned long 的最大值。ULLONG_MAX
:unsigned long long 的最大值。
整数的进制
int x = 100;
printf("dec = %d\n", x); // 100
printf("octal = %o\n", x); // 144
printf("hex = %x\n", x); // 64
printf("octal = %#o\n", x); // 0144
printf("hex = %#x\n", x); // 0x64
printf("hex = %#X\n", x); // 0X64
const 说明符
函数参数里面的const
说明符,表示函数内部不得修改该参数变量。
void f(int* p) {
// ...
}
上面示例中,函数f()
的参数是一个指针p
,函数内部可能会改掉它所指向的值*p ,从而影响到函数
外部。
为了避免这种情况,可以在声明函数时,在指针参数前面加上const 说明符,告诉编译器,函数内部不
能修改该参数所指向的值。
void f(const int* p) {
*p = 0; // 该行报错
}
上面示例中,声明函数时, const 指定不能修改指针p 指向的值,所以*p = 0 就会报错。
但是上面这种写法,只限制修改p 所指向的值,而p 本身的地址是可以修改的。
void f(const int* p) {
int x = 13;
p = &x; // 允许修改
}
上面示例中, p
本身是可以修改, const
只限定*p 不能修改。
如果想限制修改p
,可以把const
放在p 前面。
void f(int* const p) {
int x = 13;
p = &x; // 该行报错
}
如果想同时限制修改p
和*p
,需要使用两个const
。
void f(const int* const p) {
// ...
}
字符串处理函数
strlen()
strlen()
函数返回字符串的字节长度,不包括末尾的空字符\0
。该函数的原型如下。
// string.h
size_t strlen(const char* s);
它的参数是字符串变量,返回的是size_t
类型的无符号整数,除非是极长的字符串,一般情况下当作int 类型处理即可。下面是一个用法实例。
char* str = "hello";
int len = strlen(str); // 5
strlen()
的原型在标准库的string.h
文件中定义,使用时需要加载头文件string.h
。
#include <stdio.h>
#include <string.h>
int main(void) {
char* s = "Hello, world!";
printf("The string is %zd characters long.\n", strlen(s));
}
注意,字符串长度( strlen() )与字符串变量长度( sizeof() ),是两个不同的概念。
char s[50] = "hello";
printf("%d\n", strlen(s)); // 5
printf("%d\n", sizeof(s)); // 50
上面示例中,字符串长度是5,字符串变量长度是50。
如果不使用这个函数,可以通过判断字符串末尾的\0
,自己计算字符串长度。
int my_strlen(char *s) {
int count = 0;
while (s[count] != '\0')
count++;
return count;
}
strcpy()
字符串的复制,不能使用赋值运算符,直接将一个字符串赋值给字符数组变量。
char str1[10];
char str2[10];
str1 = "abc"; // 报错
str2 = str1; // 报错
上面两种字符串的复制写法,都是错的。因为数组的变量名是一个固定的地址,不能修改,使其指向另
一个地址。
如果是字符指针,赋值运算符( =
)只是将一个指针的地址复制给另一个指针,而不是复制字符串。
char* s1;
char* s2;
s1 = "abc";
s2 = s1;
上面代码可以运行,结果是两个指针变量s1
和s2
指向同一字符串,而不是将字符串s1
的内容复制给s2
。
C 语言提供了strcpy()
函数,用于将一个字符串的内容复制到另一个字符串,相当于字符串赋值。该函数的原型定义在string.h
头文件里面。
strcpy(char dest[], const char source[])
strcpy()
接受两个参数,第一个参数是目的字符串数组,第二个参数是源字符串数组。复制字符串之前,必须要保证第一个参数的长度不小于第二个参数,否则虽然不会报错,但会溢出第一个字符串变量的边界,发生难以预料的结果。第二个参数的const
说明符,表示这个函数不会修改第二个字符串。
#include <stdio.h>
#include <string.h>
int main(void) {
char s[] = "Hello, world!";
char t[100];
strcpy(t, s);
t[0] = 'z';
printf("%s\n", s); // "Hello, world!"
printf("%s\n", t); // "zello, world!"
}
上面示例将变量s
的值,拷贝一份放到变量t
,变成两个不同的字符串,修改一个不会影响到另一个。
另外,变量t
的长度大于s
,复制后多余的位置(结束标志\0
后面的位置)都为随机值。
strcpy()
也可以用于字符数组的赋值。
char str[10];
strcpy(str, "abcd");
上面示例将字符数组变量,赋值为字符串“abcd”。
strcpy()
的返回值是一个字符串指针(即char* ),指向第一个参数。
char* s1 = "beast";
char s2[40] = "Be the best that you can be.";
char* ps;
ps = strcpy(s2 + 7, s1);
puts(s2); // Be the beast
puts(ps); // beast
上面示例中,从s2
的第7个位置开始拷贝字符串beast
,前面的位置不变。这导致s2 后面的内容都被截去了,因为会连beast
结尾的空字符一起拷贝。strcpy()
返回的是一个指针,指向拷贝开始的位置。
strcpy()
返回值的另一个用途,是连续为多个字符数组赋值。
strcpy(str1, strcpy(str2, "abcd"));
上面示例调用两次strcpy()
,完成两个字符串变量的赋值。
另外, strcpy()
的第一个参数最好是一个已经声明的数组,而不是声明后没有进行初始化的字符指
针。
char* str;
strcpy(str, "hello world"); // 错误
上面的代码是有问题的。strcpy()
将字符串分配给指针变量str ,但是str 并没有进行初始化,指向
的是一个随机的位置,因此字符串可能被复制到任意地方。
如果不用strcpy()
,自己实现字符串的拷贝,可以用下面的代码。
char* strcpy(char* dest, const char* source) {
char* ptr = dest;
while (*dest++ = *source++);
return ptr;
}
int main(void) {
char str[25];
strcpy(str, "hello world");
printf("%s\n", str);
return 0;
}
上面代码中,关键的一行是while (*dest++ = *source++)
,这是一个循环,依次将source
的每个字符赋值给dest
,然后移向下一个位置,直到遇到\0
,循环判断条件不再为真,从而跳出循环。其中, *dest++
这个表达式等同于*(dest++)
,即先返回dest
这个地址,再进行自增运算移向下一个位置,而*dest
可以对当前位置赋值。
strcpy()
函数有安全风险,因为它并不检查目标字符串的长度,是否足够容纳源字符串的副本,可能导致写入溢出。如果不能保证不会发生溢出,建议使用strncpy()
函数代替。
strncpy()
strncpy()
跟strcpy()
的用法完全一样,只是多了第3个参数,用来指定复制的最大字符数,防止溢出目标字符串变量的边界。
char* strncpy(
char* dest,
char* src,
size_t n
);
上面原型中,第三个参数n 定义了复制的最大字符数。如果达到最大字符数以后,源字符串仍然没有复
制完,就会停止复制,这时目的字符串结尾将没有终止符\0
,这一点务必注意。如果源字符串的字符数小于n ,则strncpy()
的行为与strcpy()
完全一致。
strncpy(str1, str2, sizeof(str1) - 1);
str1[sizeof(str1) - 1] = '\0';
上面示例中,字符串str2
复制给str1
,但是复制长度最多为str1
的长度减去1, str1
剩下的最后一位用于写入字符串的结尾标志\0
。这是因为strncpy()
不会自己添加\0
,如果复制的字符串片段不包含结尾标志,就需要手动添加。
strncpy()
也可以用来拷贝部分字符串。
char s1[40];
char s2[12] = "hello world";
strncpy(s1, s2, 5);
s1[5] = '\0';
printf("%s\n", s1); // hello
上面示例中,指定只拷贝前5个字符。
strcat()
strcat()
函数用于连接字符串。它接受两个字符串作为参数,把第二个字符串的副本添加到第一个字
符串的末尾。这个函数会改变第一个字符串,但是第二个字符串不变。
该函数的原型定义在string.h
头文件里面。
char* strcat(char* s1, const char* s2);
strcat()
的返回值是一个字符串指针,指向第一个参数。
char s1[12] = "hello";
char s2[6] = "world";
strcat(s1, s2);
puts(s1); // "helloworld"
上面示例中,调用strcat()
以后,可以看到字符串s1 的值变了。
注意, strcat() 的第一个参数的长度,必须足以容纳添加第二个参数字符串。否则,拼接后的字符串
会溢出第一个字符串的边界,写入相邻的内存单元,这是很危险的,建议使用下面的strncat() 代替。
char s1[12] = "hello";
char s2[6] = "world";
strcat(s1+1, s2);//前面一个参数如何加字符串都不会减少
puts(s1); // "helloworld"
strcat(s1, s2+1);
puts(s1); // "helloorld"
strncat()
strncat()
用于连接两个字符串,用法与strcat()
完全一致,只是增加了第三个参数,指定最大添加
的字符数。在添加过程中,一旦达到指定的字符数,或者在源字符串中遇到空字符\0 ,就不再添加了。
它的原型定义在string.h
头文件里面。
char* strncat(
const char* dest,
const char* src,
size_t n
);
strncat()
返回第一个参数,即目标字符串指针。
为了保证连接后的字符串,不超过目标字符串的长度, strncat()
通常会写成下面这样。
strncat(
str1,
str2,
sizeof(str1) - strlen(str1) - 1
);
strncat()
总是会在拼接结果的结尾,自动添加空字符\0
,所以第三个参数的最大值,应该是str1
的变量长度减去str1
的字符串长度,再减去1
。下面是一个用法实例。
char s1[10] = "Monday";
char s2[8] = "Tuesday";
strncat(s1, s2, 3);
puts(s1); // "MondayTue"
上面示例中, s1
的变量长度是10
,字符长度是6
,两者相减后再减去1
,得到3
,表明s1
最多可以再添加三个字符,所以得到的结果是MondayTue
。
strcmp()
如果要比较两个字符串,无法直接比较,只能一个个字符进行比较,C 语言提供了strcmp()
函数。
strcmp()
函数用于比较两个字符串的内容。该函数的原型如下,定义在string.h
头文件里面。
int strcmp(const char* s1, const char* s2);
按照字典顺序,如果两个字符串相同,返回值为0
;如果s1 小于s2 , strcmp()
返回值小于0
;如果s1
大于s2
,返回值大于0
。
下面是一个用法示例。
// s1 = Happy New Year
// s2 = Happy New Year
// s3 = Happy Holidays
strcmp(s1, s2) // 0
strcmp(s1, s3) // 大于 0
strcmp(s3, s1) // 小于 0
注意, strcmp()
只用来比较字符串,不用来比较字符。因为字符就是小整数,直接用相等运算符
( ==
)就能比较。所以,不要把字符类型( char
)的值,放入strcmp()
当作参数。
strncmp()
由于strcmp() 比较的是整个字符串,C 语言又提供了strncmp() 函数,只比较到指定的位置。
该函数增加了第三个参数,指定了比较的字符数。它的原型定义在string.h 头文件里面。
int strncmp(
const char* s1,
const char* s2,
size_t n
);
它的返回值与strcmp()
一样。如果两个字符串相同,返回值为0
;如果s1
小于s2
,strcmp()
返回
值小于0
;如果s1
大于s2
,返回值大于0
。
下面是一个例子。
char s1[12] = "hello world";
char s2[12] = "hello C";
if (strncmp(s1, s2, 5) == 0) {
printf("They all have hello.\n");
}
上面示例只比较两个字符串的前5个字符。
sprintf(),snprintf()
sprintf()
函数跟printf()
类似,但是用于将数据写入字符串,而不是输出到显示器。该函数的原型
定义在stdio.h
头文件里面。
int sprintf(char* s, const char* format, ...);
sprintf()
的第一个参数是字符串指针变量,其余参数和printf() 相同,即第二个参数是格式字符
串,后面的参数是待写入的变量列表。
char first[6] = "hello";
char last[6] = "world";
char s[40];
sprintf(s, "%s %s", first, last);
printf("%s\n", s); // hello world
上面示例中,sprintf()
将输出内容组合成“hello world”
,然后放入了变量s
。
sprintf()
的返回值是写入变量的字符数量(不计入尾部的空字符\0
)。如果遇到错误,返回负值。
sprintf()
有严重的安全风险,如果写入的字符串过长,超过了目标字符串的长度, sprintf()
依然会将其写入,导致发生溢出。为了控制写入的字符串的长度,C 语言又提供了另一个snprintf()
。
snprintf()
只比sprintf()
多了一个参数n ,用来控制写入变量的字符串不超过n - 1 个字符,剩下一个位置写入空字符\0
。下面是它的原型。
int snprintf(char*s, size_t n, const char* format, ...);
snprintf()
总是会自动写入字符串结尾的空字符。如果你尝试写入的字符数超过指定的最大字符数,snprintf()
会写入 n - 1 个字符,留出最后一个位置写入空字符。
下面是一个例子。
snprintf(s, 12, "%s %s", "hello", "world");
上面的例子中, snprintf()
的第二个参数是12,表示写入字符串的最大长度不超过12(包括尾部的空字符)。
snprintf()
的返回值是写入格式字符串的字符数量(不计入尾部的空字符\0
)。如果n 足够大,返回值应该小于n
,但是有时候格式字符串的长度可能大于n
,那么这时返回值会大于n
,但实际上真正写入变量的还是n-1
个字符。如果遇到错误,返回一个负值。因此,返回值只有在非负并且小于n
时,才能确认完整的格式字符串写入了变量。
评论区