文章目录
前言
本文简明扼要的介绍了部分C语言的一些基本内容。
0 补充
C语言标准库
大致包含:
- 标准输入输出(stdio.h)
- 文件操作(stdio.h)
- 字符操作(ctype.h)
- 字符串操作(string.h)
- 数学函数(math.h)
- 资源管理(stdlib.h)
- 格式转换(stdlib.h)
- 日期/时间(time.h)
- 断言(assert.h)
- 各种类型上的常量(limits.h & float.h)
一些特殊的库,用于执行一些特殊的操作:
- 变长参数(stdarg.h)
- 非局部跳转(setjmp.h)
变长参数
#include <stdio.h>
// __VA_ARGS__编译器内置宏
#define print(...) fprintf(stdout, __VA_ARGS__)
#define print2(args...) fprintf(stdout, ##args)
int main()
{
print("%d, %s\n", 123, "hello");
// >> fprintf(stdout, "%d, %s\n", 123, "hello")
}
线程局部存储
线程私有全局变量
__thread int a = 6; // 线程私有全局变量
void printa()
{
while (1)
{
++a;
printf("thread id: %ld, a: %d\n", std::this_thread::get_id(), a);
}
}
int main()
{
for (int i = 0; i < 4; ++i)
{
std::thread(printa).detach();
}
while(1);
}
1. 关键字
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
char | short | int | long | float | double | if | else |
return | do | while | for | switch | case | break | continue |
default | goto | sizeof | auto | register | static | extern | unsigned |
signed | typedef | struct | enum | union | void | const | volatile |
基本数据类型:char, int, float, double
类型修饰: short, long, signed, unsigned
复杂类型: struct, union, enum
流程控制:分支(if, else, switch, case, default),循环(for, while, do),跳转(continue, break, goto, return)
存储类型:auto(和c++大相迳庭), register, static, extern, const, volatile
特殊用途:sizeof(同时也是运算符), typedef, void
2. C语言数据类型
3. 标志符
- 只能由字符(a~z, A~Z)、数字、下划线组成
- 不能以数字开头
- 不能是关键字
- 区分大小写
个人理解标识符是:顾名思义,标识符就是起到标志作用的符号,是给人用的,编译器会将标志符和地址关联起来;程序的运行并不需要标志符,只需要地址和值,所以标识符是给人和编译器用的。
4. 常量类型
- 整型常量
- 实型常量
- 字符常量
- 字符串常量
5. 内存模型
在笔者的环境下,不同数据类型的位数
cahr | int | float | double | short | long | long long | void* | |
---|---|---|---|---|---|---|---|---|
Byte | 1 | 4 | 4 | 8 | 2 | 8 | 8 | 8 |
#include <stdio.h>
void fun();
void fun1();
void fun2();
void fun()
{
int a = 0x04030201;
int b = 0x04030201;
int c = 0x04030201;
int d = 0x04030201;
// int *pa = &a;
printf("fun: \n");
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
printf("d = %p\n", &d);
fun1();
}
void fun1()
{
int a = 0x04030201;
int b = 0x04030201;
int c = 0x04030201;
int d = 0x04030201;
int e = 0x04030201;
// int *pa = &a;
printf("fun1: \n");
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
printf("d = %p\n", &d);
printf("e = %p\n", &e);
fun2();
}
void fun2()
{
int a = 0x04030201;
int b = 0x04030201;
int c = 0x04030201;
int d = 0x04030201;
// int *pa = &a;
printf("fun2: \n");
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
printf("d = %p\n", &d);
}
int main(int *arg, int **argvs)
{
int a = 0x04030201;
int b = 0x04030201;
int c = 0x04030201;
int d = 0x04030201;
// int *pa = &a;
printf("main: \n");
printf("a = %p\n", &a);
printf("b = %p\n", &b);
printf("c = %p\n", &c);
printf("d = %p\n", &d);
fun();
char* pa = (char*)&a;
printf("pa= %p\n", &pa);
}
OUT:
main:
a = 0x7fff04ebfa30
b = 0x7fff04ebfa34
c = 0x7fff04ebfa38
d = 0x7fff04ebfa3c
fun:
a = 0x7fff04ebf9f8
b = 0x7fff04ebf9fc
c = 0x7fff04ebfa00
d = 0x7fff04ebfa04
fun1:
a = 0x7fff04ebf9c4
b = 0x7fff04ebf9c8
c = 0x7fff04ebf9cc
d = 0x7fff04ebf9d0
e = 0x7fff04ebf9d4
fun2:
a = 0x7fff04ebf998
b = 0x7fff04ebf99c
c = 0x7fff04ebf9a0
d = 0x7fff04ebf9a4
pa= 0x7fff04ebfa40
小结:
局部变量在栈空间分配内存时,以函数为单位,先一次性分配出函数内局部变量所需的空间(函数调用不影响函数内局部变量的空间分配), 然后再执行到新的函数调用时由高地址向底地址分配内存
即: 内存的分配以函数为单位由高地址向底地址增长
变量内存分析
- 内存模型是线性的(有序的)
- 对于32位机而言,最大的内存地址是 2 32 2^{32} 232(4294967296)(4GB)
- 对于64位机而言,最大的内存地址是 2 64 2^{64} 264(171亿GB)
- CPU读写内存
- CPU运行时要明确三件事: 1. 存储单元的地址(地址信息) 2. 器件的选择,读或写(控制信息) 3. 读写的数据(数据信息)
- 通过地址总线找到存储单元的地址; 通过控制总线发送内存读写指令; 通过数据总线传输需要读写的数据;
- 变量的存储原则
- 内存内配由高地址向低地址增长
- 变量的首地址是变量存储空间低地址
- 字节序分为小端(intel, 低地址存储低位)和大端(Motorola, 低地址存储高位); 字节地址内位的存储方式是一致的
数组
char arr[3] = {'a', 'b', 'c'};
arr的值等于第一个元素的地址
不建议使用二维数组, 在C++中使用String来处理字符串
arr的值和数组首元素地址相同,arr+1得到的是数组类型的偏移,arr像个数组类型的指针,它指向数组首元素
但是sizeof(arr)得到的是内存块的大小;&arr的值与arr相同,&arr是一个指针类型,它指向数组内存块,&arr+1得到的是整个内存块大小的地址偏移
另外,sizeof既可以用于类,也可以用于类的对象。
数组与指针的区别
如上图所示,在语法上绝大部分情况数组和指针可以混用,但是数组和指针的数据类型是不同的,数组是数组类型,指针是指针类型
char arr[3] = {'a', 'b', 'c'}; // 数组类型
char* parr = arr; // 指针类型
&arr + 1 == arr + sizeof(arr)
体现:
sizeof(arr) == 3
sizeof(parr) == 8
若在a.c文件中定义char arr[3]
,则在b.c文件中需要extern char arr[3]
来使用而非extern char* arr。
变量名与地址
变量名就是地址的别称,用人类容易记忆的方式来代表某个地址
int a;中a就是某个地址0x???的别名,a = 4,就是这块内存赋值 4
a文件中定义char arr[3],在b文件中extern char* arr就是把b文件中的名字对应的a文件中的arr所在地址上。又因为b中arr为指针类型,所以会把arr的值当成地址对待即
a: char arr[3] = {0x11, 0x22, 0x33}; arr就是0x???地址
b: extern char* arr; arr就是0x???地址,地址里装的值是指针类型,地址里的值是什么呢?自然是[0x11 0x22 0x33 第4~第8个字节装的是什么不知道]
6. printf & scanf
- 格式输出函数 printf( “格式控制字符串”, 输出项列表 );
eg: printf(“a = %d, b = %d”, a, b); - 格式控制字符串: %[标志][输出宽度][.精度][长度]类型
类型 | 含义 |
---|---|
d | 有符号十进制 |
u | 无符号十进制 |
f | 单、双精度浮点数(默认保留6位小数) |
c | 字符 |
s | 字符串 |
p | 地址 |
- scanf函数用于接受键盘输入(STDIN)的内容,是一个阻塞式函数,程序会停在scanf函数出现的地方,直到接受到数据才会继续执行。
- scanf(“格式控制字符串”, 地址列表);
eg: scanf(“%d”, &num); - 原理
- 系统会将用户输入的内容先放入输入缓冲区
- scanf方式会从输入缓冲区逐个取出内容赋值给变量
- 如果输入缓冲区的内容不为空,scanf会一直从缓冲区中获取,而不要求再次输入
- 利用setbuf清空缓冲区(所有平台都有效)
- setbuf(stdin, NULL);
puchar&getchar
char ch = 'a';
putchar(ch); // 输出a
ch = getchar(); // 获取一个字符
7 main函数
- main也是个函数名, 只不过该名比较特殊,程序已启动就会自动调用它。即,程序执行的入口。
- 如果main函数执行正常则返回0, 否则返回一个非0的数
- int main(int argc, const char* argv[]);
- argc - 在命令中输入的字符串的个数, 程序名占1个
- argv - 命令行字符串argv[0]为程序名
8 字面值常量
# 二进制
0b1010
# 十六进制
0xFF
# 八进制
0777
# 十进制
10
# 科学计数法
1e3
1.1e3
45U # 无符号
34.F # (float)34.0
2L # (long)2
2LL # (long long)2
2ULL # (unsigned long long)2