目 录CONTENT

文章目录

C语言常见疑难点

smallkun
2023-02-24 / 2 评论 / 0 点赞 / 909 阅读 / 10,132 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-03-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我删除。

位运算

已知:

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 运算符的优先级和关联性

image-20230224135047565

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 类型的最小值-128SCHAR_MAX 代表 signed char 类型的最大值127 。

  • SCHAR_MINSCHAR_MAX :signed char 的最小值和最大值。
  • SHRT_MIN SHRT_MAX :short 的最小值和最大值。
  • INT_MIN INT_MAX :int 的最小值和最大值。
  • LONG_MIN LONG_MAX :long 的最小值和最大值。
  • LLONG_MINLLONG_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 小于s2strcmp() 返回
值小于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 时,才能确认完整的格式字符串写入了变量。

0

评论区