零:
声明、定义、初始化:
声明:前面加上 extern, 表示变量或函数在其他文件中定义了。
extern int a; // 前面加上了extern
extern int func(int a, double b);
// extern 可置于变量和函数前面,表示变量或者函数的定义是在其他文件中,
// 提示编译器遇到这个变量或者函数的时候,在其他文件/模块中寻找它的定义
// 如果在声明函数的时候,extern 是可以被省略
定义:只要前面没有 extern 的,就属于定义,定义有存储空间,可以获取地址,但是地址内没有合法的值
int a;
int a[5];
赋值:对已定义的变量进行赋值操作
a = 10;
初始化:定义变量的同时进行赋值操作,地址和值都可以被获取。
int a = 3;
int a[5] = { 0 };
一、字符串函数
int strlen(const char* str);
int a = strlen(str):返回不包括字符串结尾'\0'的字符串长度,即有效字符长度
int strnlen(const char* str, int maxlen);
int a = strlen(str, len) : 返回len个char长度以内,不包括字符串结尾'\0'的字符串长度。
int strcat(char* str1, const char* str2);
strcat(str1, str2): 将参数str2 追加到 str1里;
int strncat(char* str1, const char* str2, int len);
strncat(str1, str2, len):字符串有限追加len个char字节长度的字符串
...(此处省略很多字);
char* strtok(char* str, const char* delim);
strtok(str, delim): 分解字符串str为一组字符串子串,str就是要被分隔的字符串,delim分隔符
strtok()函数每次会把分隔符所在的位置置为'\0',调用前和调用后的str已经不一样了;
/*
char s[20] = "abcsabscccs";
strtok(s, "s");
printf("%s\n", s);
strtok(NULL, "s");
printf("%s\n", s);
strtok(NULL, "s");
printf("%s\n", s);
*/
二、函数参数的 进栈顺序 和 参数计算顺序
1. 大端对齐 和 小端对齐
unsigned int a = 0x12345678;
大端:就是把数值的高位数值放在内存的低位字节,把数值的低位数值放在内存的高位字节
地址: 0x00c1 0x00c2 0x00c3 0x00c4
数值: 0x12 0x34 0x56 0x78
小端:就是把数值的高位数值放在内存的高位字节,把数值的低位数值放在内存的低位字节
地址: 0x00c1 0x00c2 0x00c3 0x00c4
数值: 0x78 0x56 0x34 0x12
大端:IBM、Sun的服务器是大端,初代的苹果电脑PowerMac也是大端。
小端:x86架构的CPU是小端,ARM(英国)架构大多也是小端。
2. 函数参数的进栈顺序
#include <stdio.h>
void func(int a, int b, int c)
{
printf("a = %d : [%p]\n", a, &a);
printf("b = %d : [%p]\n", b, &b);
printf("c = %d : [%p]\n", c, &c);
}
int main(void)
{
func(100,200,300);
return 0;
}
//Win32下的编译结果
a = 100 : [0x5fcad0] +4
b = 200 : [0x5fcad8] +4
c = 300 : [0x5fcae0]
//Ubuntu Linux下编译结果
a = 100 : [0x5fcad0] +4
b = 200 : [0x5fcad8] +4
c = 300 : [0x5fcae0]
C程序在执行时:"先入栈的数据在栈底,为高地址;后入栈的数据在栈顶,为低地址。"
"先进后出,后进先出"
上面的例子可以看出:函数参数的进栈顺序是"从右往左"。
// Mac OS X下的编译结果
a = 100 : [0x5fcae0] -4
b = 200 : [0x5fcad8] -4
c = 300 : [0x5fcad0]
Mac OS X 使用的编译器是 LLVM Clang,这个编译器会优化一些代码,
导致函数参数的进栈顺序是"从左往右"。
3. 函数参数计算顺序
#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;
}
//Win32 MSVC 下编译结果是
110, 40, 60
//Ubuntu Linux GUN GCC下编译结果是
110, 40, 60
//Mac OS X LLVM Clang下编译结果
60, 40, 60
由此可知:
在MSVC和GCC编译器下,参数的计算顺序是"从右往左",和其参数进栈顺序相同;
在LLVM Clang编译器下,参数的计算顺序是"从左往右",和其参数进栈顺序相同。
所以,当一个函数参数有多个的时候,C/C++语言设计者没有规定函数参数的进栈顺序和计算顺序是哪一种,
这些是有编译器厂商自己规定的。
4. 函数的默认参数
#include <stdio.h>
void func(int a, int b, int c = 300)
{
printf("a = %d : [%p]\n", a, &a);
printf("b = %d : [%p]\n", b, &b);
printf("c = %d : [%p]\n", c, &c);
}
int main(void)
{
func(100,200);
return 0;
}
上面的写法,在Ubuntu Linux和MSVC下是不允许,会报语法错误,实参数量要匹配型参数量,
也不允许函数参数在参数列表内赋值。
但是在LLVM Clang下是允许的,这是由于编译器支持这么干。
C编译器有很多,除了GCC、MSVC、Clang外,
还有一种编译叫ICC,这个编译器是Intel公司做的,会优化代码在Intel CPU上的效率。
还有Turbo C,是美国borland公司的产品,现在估计已经被淘汰了。TC
重点:
由于C语言编译器厂商规范不统一,所以我们在写代码的时候,不要写类似的"UB语句"。
"Undefined Behavior" 代码行为未定义:
如果你的程序违反了C标准的某些规则,那么具体会发生什么,C语言没有定义,
也就是说会得到一些奇怪的结果,都是有可能的。比如说整数溢出就是这种情况。
"Unspeakable Behavior"方案未定义:
C标准可能给你的代码提供了好几种可选方案,但是具体用哪一种,C标准没有定义。
比如说上面的例子,函数参数的计算顺序和进栈顺序不一致,而且任何计算方式都行。
写程序的态度要严谨,我们的代码是给人看的,所以为了将来的代码维护和升级,尽量不要用表达过于笼统的语句。
// 我的代码,只有上帝和我能懂。
// 我是你之前的维护人员,经过测试这里的代码最好不要乱动。
#include <stdio.h>
int main(void)
{
int i = 0;
int a = i + ++i;
printf("%d\n", a);
return 0;
}
//反面教材,这种情况,最后的值会因为编译器不同而不同。
三、一级指针
1. 指针的基本用法:
"32位系统下是4个字节,64位系统下是8个字节"
1) 在配合数据类型定义变量的时候使用*号,是代表这个变量的类型是指针类型
int a = 10; // 定义一个整型变量,并赋值10
int *p = &a; // 定义一个整型指针变量,指向 a 的地址
2) 在配合变量进行操作的时候使用*号,是取值运算符,意思是可以取出这块内存空间的值、
printf("%d\n", *p); //取出p指向的内存空间的值
printf("%d\n", *p += 1); //取出p指向的内存空间的值。并自增1
printf("%d\n", a); // a的值也会被修改,是 11
2. 指针的几种特殊定义方式:
1) int *const p;
指针常量:p是 int*类型,const 修饰的是p,所以p是常量。则p指向的地址不可修改,也就是说不能再指向其他地方了。
但是,可以修改他所指向的地址里的值。
举例:
int a = 10;
int b = 20;
int *const p = &a;
p = &b; //错误
*p = 100; //允许
2) const int *p;
int const *p;
常量指针:p是 int*类型,const 修饰的是 *p,所以*p是常量。则p是一个指向常量的指针,也就是说他也可以指向其他地方,
但是,他所指向的地址里的值不能修改。
举例:
int a = 10;
int b = 20;
const int *p = &a;
p = &b; // 允许
*p = 100; // 错误
3) const int *const p;
常量指针常量:p是 int*类型,const 分别修饰了p 和 *p,所以p和*p都是常量,则p是一个指向常量的指针常量,
也就是说这个指针指向的地址不可修改,地址里的内容也不可修改。
举例:
int a = 10;
int b = 20;
const int* const p = &a;
p = &b; // 错误
*p = 100; // 错误
切记:如果你定义了一个指针,那么就一定要知道这个指针指向了什么地方,而且你要保证你定义的这个指针是正确且有效的,
如果你乱用,我就用程序崩溃来惩罚你!
C语言之所以强大、自由性、高执行效率,很大部分是体现在指针的灵活运用上的。
《C Primer Plus》第五版 很多页
入门书、参考书、
《C和指针》
《C专家编程》《C陷阱与缺陷》《C沉思录》
《The C Programming Language》 K&R C, ANSI C标准出台前,这本书第一版就是当时的C标准。
四、多级指针
#include <stdio.h>
int main(void)
{
int a = 10; // 定义一个整型变量 a,值为 10
int *p = &a; // 定义一个一级整型指针变量p,值为 a的地址
int **pp = &p; // 定义一个二级整型指针变量pp,值是 p的地址
int ***ppp = &pp; // 定义一个三级整型指针变量ppp,值是 pp 的地址
// %p 打印地址, %d 打印整数, %f 打印浮点数, %s 打印字符串,%c 打印字符
// 在配合数据类型定义变量的时候使用*号,是代表这个变量的类型是指针类型
// 在配合变量进行操作的时候使用*号,是取值运算符,意思是可以取出这块内存空间的值、
printf("%p, %p, %p, %p\n", &a, &p, &pp, &ppp);
// 取出各个变量自身在内存的地址
printf("%p, %p, %p, %p\n", &a, p, pp, ppp);
// 除了 &a之外,都是打印各个指针变量存储的值
printf("%p, %p, %p, %p\n", &a, p, *pp, **ppp);
// %p打印地址(其实是按十六进制方式打印出)
// 1. 利用取地址运算符&,打印变量 a 的地址
// 2. 直接打印p的值,也就是 a 的地址
// 3. 利用取值运算符*,取出二级指针变量pp的值,也就是一级指针p的地址,在通过%p打印出p的值,也就是 a 的地址
// 4. 利用取值运算符*,取出三级指针变量ppp的值,也就是二级指针pp的地址,再通过取值运算符*,取出二级指针pp的值
// 也就是p的地址,再通过%p打印出p的的值,也就是 a 的地址
printf("%d, %d, %d, %d\n", a, *p, **pp, ***ppp);
// 如果要取出变量a的值,那么用了多少级指针,就用多少个取值运算符,
// 在取出最后的值之前,各级指针取出的值都是其上一级指针的地址
return 0;
}
c语言基础总结
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.