黑马程序员技术交流社区

标题: 【笔记】C语言核心知识点相关总结(一) [打印本页]

作者: 倾心莫若初见    时间: 2017-2-28 14:18
标题: 【笔记】C语言核心知识点相关总结(一)
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下是返回1Linux下是返回两个不同字符串的ascii码差值)
如果str1小于str2,返回负数(Windows下是返回-1Linux下是返回两个不同字符串的ascii码差值)
如果两个字符串相等,返回 0

举例: str1 = "abcde"; str2 = "c";
           strcmp(str1, str2); 返回值是(Windows下是返回-1Linux下是返回两个不同字符串的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,如果找到的话返回chstr中的位置,如果没找到,返回 NULL // (void*) 0

10 strstr(char *str1, char*str2);
str1中查找指定字符串str2,如果找到的话返回str2str1中的位置,如果没找到,返回 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 的格式分隔,把20160531 这三个整数写入到abc
           sscanf("2016 - 05 - 31", "%s - %s -%s", s1, s2, s3);
           //  按照 %s - %s - %s 的格式分隔,把20160531 这三个字符串写入到s1s2c
           // %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           


大端:IBMSUN的服务器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程序在执行的时候,先入栈的数据是在栈底的,栈底是高地址,后入栈的数据在栈顶,栈顶为低地址。

从上面的例子看得出来:
GCCMSVC下,参数的进栈顺序是"从右往左"
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的值是300func(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贵族人才!
一张贴玩转C/C++:视频+源码+笔记+工具+面试



作者: Tzz    时间: 2017-3-10 11:54
0.0有没有黑马币

作者: a851699    时间: 2017-6-17 11:00
可以 很强势!
作者: 桃树下的boy    时间: 2018-3-31 18:03
456454646464564
作者: 羊羊羊嘤嘤嘤    时间: 2018-7-15 23:51
谢谢哈。。
作者: 番茄炒鸡蛋    时间: 2018-7-16 07:49
学习一下




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2