C语言核心知识点相关总结(一)
零:声明、定义、初始化
声明:extern 可以置于变量或者函数前面,提示编译器遇到这个变量或者函数的时候,在其他/当前模块里寻找。 extern int a; // b.c 有个全局变量a,那么可以在a.c里声明这个a,然后拿过来用,但是并不代表我重新定义了一个a extern int function(int a, double b, char c); // 注意:函数在进行声明的时候,extern 是默认可以省略的。
定义:没有加上 extern 就是定义了。定义后的变量是有存储空间的,可以获取到这个变量的地址,但是不一定有值。 int a; int arr[10];
初始化:定义变量的同时进行赋值操作。 int a = 10; int arr[10] = { 0 };
数据类型: 在C语言里的数据类型分为四大种: 1. 基本类型 在不同平台下所占字节数: Linux 64 Windows 64 Linux 32 Windows 32 字符: char 1 1 1 1
整型: short 2 2 2 2 int 4 4 4 4 long 8 4 4 4 long long 8 8 8 8 size_t 8 8 4 4
浮点型: float 4 4 4 4 (实型)double 8 8 8 8
2. 构造类型 数组:存储了n个相同基本类型的数据,举例:sizeof(int) * n 个字节大小 char str1[5] = "haha"; // 只要用" "括起来的就是字符串,只要是字符串最后一位就是'\0' char str2[5] = {'h', 'a', 'h', 'a','h'}; str1[2] = 'm'; printf("%s\n", str1);
int iarr[5] = {1, 2, 3, 4, 5}; float farr[5] = {1.1, 2.2, 3.14,4.15, 5.001};
结构体:存储了n个可以不相同基本类型的数据 struct 联合体/共同体:存储n个可以不相同基本类型的数据,但是这种类型里的所有数据共享同一块内存空间,内存大小是最大的那个数据类型大小 union 枚举:存储了n个相同基本类型的数据,但是使用的时候只能取其中一个值,内存大小是 sizeof(int); enum
3. 指针类型 32位占4个字节,64位占8个字节 int *p; // 指针类型变量p就是用来存储地址的,定义指针时候的数据类型,代表这个指针指向的内存空间里存储的值的类型 int a = 10; p = &a; *p = 20; printf("%d\n", a);
char *str = "haha"; // char *str 和 char str[5] 都是存储字符串,但是str是一个指针,str是一个数组名 printf("%s\n", str); // char str[5] 存储的"haha"是在栈区, char *str 指向的"haha" 是在常量区,str存储的是这个字符串在常量区的首地址。 *(str + 1) = 'm'; // 错误!! 因为str里的内容保存在常量区,所以里面的值不可以修改。
4. 空类型 void 变量 :意思是空类型变量,不接受任何数据 void 函数 :意思是该函数没有返回值 void 指针 :意思是可以接收任何其他类型的指针
一、 字符串函数 <string.h> 1 strlen(char *str); 这个函数返回值是一个字符串的有效长度(除去'\0'), 有别于 sizeof() 运算符;
2 strnlen(char *str, intmaxlen); 这个函数是返回 maxlen 长度以内、不含'\0'的字符串的长度。
3 strcat(char *str1, char*str2); 将参数str2 追加到 str1的后面(覆盖str1后面的'\0')
4 strncat(char *str1, char*str2, int maxlen); 将参数str2 追加到 str1的后面,但是只追加str2的前maxlen个字节长度的字符串。
5 strcmp(char *str1, char*str2); 按字符依次比较两个字符串,直到遇到不同的字符为止: 如果str1大于str2,返回正数(Windows下是返回1,Linux下是返回两个不同字符串的ascii码差值) 如果str1小于str2,返回负数(Windows下是返回-1,Linux下是返回两个不同字符串的ascii码差值) 如果两个字符串相等,返回 0
举例: str1 = "abcde"; str2 = "c"; strcmp(str1, str2); 返回值是(Windows下是返回-1,Linux下是返回两个不同字符串的ascii码差值: -2)
6 strncmp(char *str1, char*str2, int maxlen); 和 strcmp()函数返回值相同,但是只比较前 maxlen 个字符;
7 strcpy(char *str1, char*str2); 将参数 str2 的字符串拷贝到参数 str1 里面。(拷贝的字符包括'\0')
8 strncpy(char *str1, char*str2, int maxlen); 将参数 str2 的前 maxlen 个字符拷贝到 str1 里。
9 strchr(char *str, charch); //原来是 int ch,但是函数在调用的时候,会转换成char ch 在str中查找指定字符ch,如果找到的话返回ch在str中的位置,如果没找到,返回 NULL // (void*) 0
10 strstr(char *str1, char*str2); 在str1中查找指定字符串str2,如果找到的话返回str2在str1中的位置,如果没找到,返回 NULL
11 strtok(char *str, chardelim); 分解字符串 str 为一组字符串子串,用delim做为字符串的分隔符。 strtok()函数每次分隔会把分隔符的位置置为 '\0', 同时会破坏原先字符串的完整性; 调用函数前的字符串和调用函数后的字符,已经不一样了。所以我们在做字符串分隔的时候,更推荐使用 sscanf();
12 sscanf(char *str, char*format, argument...); scanf()函数是从键盘上读取用户输入,然后把值写到变量里; sscanf()函数是从str里读取数据,按照format格式,将数据写入到变量里。 返回值是成功写入的数据数量;
int a, b, c; char s1[10] = { 0 }; char s2[10] = { 0 }; char s3[10] = { 0 }; sscanf("2016-05-31", "%d - %d - %d",&a, &b, &c); // 按照 %d - %d - %d 的格式分隔,把2016、05、31 这三个整数写入到a、b、c里 sscanf("2016 - 05 - 31", "%s - %s -%s", s1, s2, s3); // 按照 %s - %s - %s 的格式分隔,把2016、05、31 这三个字符串写入到s1、s2、c里 // %s 匹配一串非空白字符,从输入字符中的第一个非空白字符开始匹配到下一个空白字符之前, // 或者匹配到指定的宽度,赋值参数的类型是char *,末尾自动添加'\0'。
13 sprintf(char *str, char*format, argument...); printf()函数是把格式化后的结果输出到屏幕上; sprintf()函数是把格式化后的结果写入到字符串str里; 返回值是 str被写入的字节数,不包括'\0';
char i[10] = "I"; char you[10] = "You"; char str[100] = { 0 };
sprintf(str, "%s love %s", i, you); // 输出字符串"I love You"到字符串 str 里 sprintf(str, "%10.3f", 0.1234567); // 输出字符串 " 0.123" 到字符串 str 里(原先的内容会被清空)
14 字符串转换函数<stdlib.h> atoi(); 把一个 char 类型的数组转换成一个 int. itoa()把一个 int 类型的数字转换成char类型的字符串(只能在Visual C++ 编译器下使用); // Linux是没有的 atoll(); 把一个 char 类型的数组转换成一个 long long.
atof(); 把一个 char 类型的数组转换成一个 double.
举例: char str[10] = "....."; //int float long long int / double / long long n = atoi(str) / atof(str) /atoll(str); printf("%d / %lf / %ld\n", n);
二、函数参数的进栈顺序和运算顺序(引伸出各个平台编译器的不同)
1. 大端对齐和小端对齐:
unsigned int num = 0x12345678;
大端对齐:数值的高位字节存储在内存的低位地址上,数值的低位字节存储在内存的高位地址上。
地址: 0xff1100 0xff1101 0xff1102 0xff1103
数值: 0x12 0x34 0x56 0x78
小端对齐:数字的高位字节存储在内存的高位地址上,数值的低位字节存储在内存的低位地址上。
地址: 0xff1100 0xff1101 0xff1102 0xff1103
数值: 0x78 0x56 0x34 0x12
大端:IBM、SUN的服务器CPU都是大端对齐,最早的苹果电脑PowerPC也是大端。 小端:x86\AMD64(美国)架构CPU(复杂指令集)都是小端对齐,ARM(英国)架构CPU(精简指令集)都是小端对齐。 x86 intel AMD64 AMD
2. 函数的进栈顺序
#include <stdio.h> void func(int a, int b,int c) // 三个形参(本质是局部变量),接收实参的值 { printf("a = %d : %p", a, &a); printf("b = %d : %p", b, &b); printf("c = %d : %p", c, &c); }
int main(void) { func(100, 200, 300); // 三个实参 return 0; }
// Ubuntu GCC 下编译结果 a = 100 : 0xbf8decb0 +4 b = 200 : 0xbf8decb4 +4 c = 300 : 0xbf8decb8
// Windows Visual C++ 下编译结果 a = 100 : 0x0018F720 +4 b = 200 : 0x0018F724 +4 c = 300 : 0x0018F728
// LLVM Clang 下编译结果 a = 100 : 0x7fff547d59e8-4 b = 200 : 0x7fff547d59e4-4 c = 300 : 0x7fff547d59e0
C程序在执行的时候,先入栈的数据是在栈底的,栈底是高地址,后入栈的数据在栈顶,栈顶为低地址。
从上面的例子看得出来: GCC和MSVC下,参数的进栈顺序是"从右往左"。 在LLVM Clang下,参数的进栈顺序是"从左往右"。
3. 函数参数的计算顺序
//1. #include <stdio.h> int main(void) { int a = 10, b = 20, c = 30; printf("%d, %d, %d\n", a + b + c, b = b * 2, c =c * 2); return 0; }
// Windows Visual C++ 下编译结果 110, 40, 60
// Ubuntu GCC 下编译结果 110, 40, 60
// LLVM Clang 下编译结果 60, 40, 60
//2. #include <stdio.h> int a() { printf("a\n"); return 1; }
int b() { printf("b\n"); return 2; }
int main(void) { printf("%d, %d\n", a(), b()); return 0; }
//MSVC 下编译结果 b a 1, 2
// Ubuntu GCC 下编译结果 b a 1, 2
// LLVM Clang 下编译结果 a b 1, 2
4. 函数的默认参数
#include <stdio.h> void func(int a, int b,int c = 300) // 三个形参(本质是局部变量),接收实参的值 { printf("a = %d : %p", a, &a); printf("b = %d : %p", b, &b); printf("c = %d : %p", c, &c); }
int main(void) { func(100, 200); // 三个实参 return 0; }
上面的写法,在LLVMClang下是可以编译通过的,而且c的值是300,func(100, 200)的值也给了a 和 b。 但是在 MSVC 和 GCC下不允许这么做,也不允许在函数参数列表里赋值。
C编译器: Microsoft Visual C++ / GNUGCC /LLVM Clang / ICC / Turbo C
当一个函数的参数列表里有多个参数的时候,C语言没有规定实参的进栈顺序和计算顺序,而是由编译器自行决定的。
我们在写代码的时候,尽量不要写出UB(行为未定义、奇葩)语句:
"UndefinedBehavior"简单来说就是: 如果你的程序违反了C标准中某些规则,程序具体执行结果会发生什么,C语言没有定义。 也就是说得到的结果可能是某种奇怪的情况,都是有可能发生的。 比如说,整数溢出就是一个"UndefinedBehavior"语句。
"UnspecifiedBehavior"简单来说就是: C标准提供了好多种可选方案,但是没有告诉你一定要用哪一种, 比如说,函数参数的计算顺序就是这种情况。
未完待续,请看下一期《C语言核心知识点相关总结(二)》
精华推荐:
3分钟带你读懂C/C++学习路线
为什么来黑马程序员学C/C++? 稳做IT贵族人才!
|