知识扫盲
CPU从内容读取指令,执行相应的操作.
计算机只能识别1和0指令
第一个C语言程序
连接:就是把我们自己写的.c编译后的.o文件和系统函数合并在一起,生成一个可执行文件
编译:
分步编译:
cc -c xx.c
cc xx.o -o name
快速编译:
cc xxx.c -o name //快速编译,不会报错
执行:
./name
文件名:
.c 源文件
.o 编译后的文件 (windows .obj)
.out 可执行文件 (windows .exe)
基本语法
关键字
就是c语言提供的有特殊含义的符号,也叫做保留字
标识符
就是我们自己定义的一些符号和名称,用来区分某些东邪,比如函数名,变量名
标识符命名规则
- 只有26个字母,数字,下划线组成
- 严格区分大小写
- 不能以数字开头
- 不可以使用关键字
- 有意义
注释
注释用来表明一段代码有什么作用,不会被编译,还可以用来排错
可以写任何文字,一般用豆绿色
单行注释: // 可以嵌套
多行注释: /* */ 不可以嵌套
数据
静态数据
永久性的数据,一般存在硬盘
硬盘的访问速度很慢
动态数据
在程序运行过程中,动态产生的临时数据,一般存储在内存中.
断电就会消失.
内存访问非常快
数据类型
- 基本数据类型
- 指针类型
- 构造类型
- 空类型
常量
表示一些固定的数据
整形常量
10 -33
浮点常量
0.33f 0.055
字符常量
'a' 'dd' '+' //字符那个用单引号
字符串常量
"fasdfa" "343ed" //字符串用双引号
0.0是小数
变量
一些经常改变的不确定数据,就用变量来表示,比如游戏积分
定义变量
- 使用之前必须定义
- 变量类型 变量名
- 不同的变量类型占用不同大小的存储空间.
- 第一次赋值是初始化
int 4个字节
char 1个字节
输出变量:
printf('%d',name);
占位符:
%d/$i 整形
%.4f 4为小数
%c 字符
%p 地址
变量在函数中的作用域
在函数里面:
从开始定义的那个地方开始,一直到函数结束.
函数在调用的是偶分配内存空间,函数结束的时候释放内存空间
变量在代码块中的作用域
代码块:
int main()
{
{
这就是一个代码块,在这里面定义的变量,只能在这里面用
}
//但是在这里定义的变量可以被代码块中使用
int a = 10;
{
printf('d%',a);
}
}
在代码块中使用变量,优先在自己的快里找,然后依次往上找
在函数外面定义的是全局变量,函数内的为局部变量
代码块用来提高性能的,可以在适当的时候释放变量,不会一直占用内存
及时回收不再使用的变量
变量的内存分析
变量在内存中占据多个字节:
char 1
int 4
float 4
double 8
内存的寻址从最大的开始,也就是从底部入栈
变量越先定义,内存的地址就越大.
与之对应的变量的地址是变量所占字节分配中的最小的一位
可以用&name取得name的地址. 输出用 &p
只要定义变量,就会分配地址,不管有没有值
scanf函数
用来接收用户的输入,是个阻断函数,必须完成后,下面的程序才会执行
只接收变量的地址
scanf("%d",&name);
多个参数以分隔符(任意)隔开,在输入的时候也必须以这个符号隔开
如果以空格隔开,在输入的时候用tab或者回车也可以
里面不能写\n,不然不会停止
运算符
+加法运算 还可以做正号
%去余 两边都是整数 正负数与左边的值有关
类型转换
自动转换
在运算或者赋值时,会自动将大类型转换为小类型
int a = 19.8; //19
也可能将小类型提升为大类型:
double c = 10.9 +9; //19.9000000
运算结果和参与运算的数据类型有关,两个整数运算结果必定是整数.
1/3 = 0;
强制转换
int a = (int)12.3;
符合运算
a += 1+2+3; //等于 a = a+1+2+3;
b = ++a 先自增运算在赋值
b = a++ 先赋值在自增运算
b = (++a) + (a++) //b = 10 + 12
b = (a++) + (++a) //b = 11 + 11
只有变量才允许自增自减
sizeop
获取一个变量或常量占据了多少字节
关系运算符
< <= > >= 的优先级高于 == !=
逻辑运算符
&& 逻辑与
|| 逻辑或
!逻辑非
三目运算符
int a = 10>5 ? 1:0;
流程控制
条件语句
如果要在if后面定义新的变量,必须用{}括起来
if()
int a = 10; //报错
if()
printf("%d",a); //对的
在switch语句中,case中也不能定义变量,不然又作用域不明确了,非要定义变量的话,必须用大括号括起来
switch(c){
case '+':
int sum = a + b; //报错
break;
}
但是可以加个大括号解决
switch(c){
case '+':
{
int sum = a + b; //报错
break;
}
}
循环
white循环
continue; 终止本次循环,继续下一次循环
break; 结束整个循环
如果没有大括号,就会执行挨着循环的那一条语句,注意变量作用域
do while 不管条件怎么样,先执行一次,再判断条件是否成立
注意事项
for(int i=0;i<5;i++){
int i = 10;
printf("%d",i); //会输出5个 10 ,因为每一次循环结束,{}中的i就被释放了
}
这是对的,因为括号里的作用域要比{}里面的大,可以看成for循环也是一个区域,而{}是form循环区域里的一起个小块
函数
格式:
返回值类型 函数名(参数){
函数体
}
形参: 函数括号里的参数,可有可无
实参: 调用函数时传递的参数
函数体内不能定义和形参一样的变量
return 退出函数,返回一个具体的值给函数调用者 ,返回的类型必须和定义函数时的类型一样
也可以没有返回值,这样定义函数:
void test(){
return;
} 这个函数也可以用return退出函数;
不写返回类型,默认返回int类型
就算写了返回类型,也可以不返回值
注意
函数不能重名
函数不能嵌套定义
函数要定义在main函数前面,因为程序是从上往下执行的
可以这样解决,把函数放在main的后面,但是要在main的前面写一个函数声明:
void test();
int main(){};
voed test(){}
函数可以重复声明
还可以在调用前声明:
init main(){
void test();
test();
}
编译的时候只检查语法
链接的时候会检查函数存不存在
include
系统自带的用<>
我们自己写的用""
多人开发
各自编译出来各自的之后,然后合并
cc a.o b.o c.o
还可以直接编译,但是不会提示报错信息
cc a.c b.c c.c
使用别人的函数,还在前面写出声明,我们可以引入.h文件
#include "func.h";
一般一个.c文件都会对应一个.h文件
main 和 printf
main函数中,return 0,正常退出;return -1,异常退出;
printf也有返回值,是输出占用字符的数量
中文占三个字符
进制
就是计数的方式
要了解四种进制: 10 2 8 16
默认就是10进制
二进制:
int num = 0b1100; //前面带个0b就是二进制
八进制:
int num = 014; //前面带个0就是八进制
十六进制:
int num = 0xc; //前面带个0x就是十六进制
%d十进制输出, %o八进制输出,%x十六进制输出,%p输出地址,$f输出小数
变量在内存中的存储细节
任何数字在内存中存的都是二进制
一个int类型占四个字节,32bit
0000 0000 0000 0000 0000 0000 0000 0000 (一个字节八位,32位存4个字节)
进制转换
二进制转十进制
0000 0000 0000 0000 0000 0000 0000 1100 = 02的0次方 + 0的2的1次方 + 12的2次方 + 12的3次方
十进制转二进制
67 = 64 + 2 + 1
= 2的6次方 + 2的一次方 + 2的0次方
= 100011
二进制的取值范围
0-2的n次方 -1
int 2的31次方-1 因为第一位是符号位
最高位是0是正数
最高位是1是负数
类型说明符
int 4字节 %d (32位处理器2字节)
long 8字节 %ld (32位处理器4字节) 4354353425423523453425L
long long 8字节 %lld (32位处理器8字节) 34524352345243523452LL
short 2字节 %sd
sigend 有符号,正数,0,负数
unsigend 无符号 (Int会取值到32位)
同一个类型的修饰符不能同时使用,不同类型的可以同时使用
位运算
按位与: 相同为1 9&5
10101
01011
-----
00001
可以按位与一个1,返回最后一位的值,用来判断奇偶性,1为奇数,0为偶数
按位或: 任何一个为1就为1 9|8
101010
100101
------
101111
按位异或: 不同为1 9^8
101010
100101
------
001111
按位取反: 0变1,1变0 ~9
左移: 把整数的各二进制全部左移N位,高位丢弃,低位补0, 左移N位其实就是乘以2的N次方
有时候会把负数搞成正数
右移: 往右移动N位,低位丢失,高位补符号位,右移就是除以2的N次方
Mac高位补符号位,其他看编译器
char类型
字符在内存用是以ASCII码存的,占一个字节
A 65 内存中 0100 0001
B 66 内存中 0100 0010
列子:
char a = 'A' + 32; // 结果是a
先进行运算 65 + 32 = 97 //就是a了
单引号只能用于一个单字节的字符
数组
构造类型: 由无数个基本类型组成的类型,构造类型
定义:
类型 名字[元素个数]
int arrs[5];
arrs[1] = 3;
arrs[2] = 4;
int arrs[5] = {1,2,3,4,5};
int arrs[5] = {[4]=9,[5]=9};
int arrs[] = {3,4,5};
int ages[bian]; //对
int ages[bian] = {3,3,4};//错 如果在定义的时候初始化,必须写常量
长度:
int count = sizeof(arrs)/sizeof(int);
数组放到函数里,可以省略参数:
void change(int array[]){}
如果在函数修改了数组元素,原数组也会改变,因为传进来的就是一个地址.也就是一个指针,指针永远是八个长度
所以无法获取到数组长度,在传递参数的时候,要把长度也传过去
基本类型是值传递
二维数组:
int arr[2][4] = {
{},
{},
...
}
字符串
定义:
char name[] = "adsfdsa"; //默认最后会有一个\0
char name[7] = "adfads";
0的作用:
如果没有\0,字符串就不知道在哪结束,就会把其他的变量也都输出了,在内存中一直往下找,知道找到\0停止
长度
#include <string.h>
strlen("aaa"); //原理就是去说字符串的字节数,也就是指针地址,不包括\0
字符串数组:
name[3][10] = {"fdfd","dfa","adaa"}
指针变量
定义:
变量类型 *变量名
int *p = a; //把a的地址存到了p里面
指针变量只能存储地址
指针能根据一个地址值,访问对应的存储空间,并进行值的修改
int表示P只能指向int类型的存储空间
赋值 *p = 10;
取值 *p;
指针使用注意
- 指针变量只能存地址
- 指针变量不能为空,必须要有确定指向的值,指针变量未经过初始化,不要拿来访问其他存储空间
- 定义指针时候的*没有任何意义,仅仅是一个象征,和后面的*不一样
- int *p; 代表 (int *)p,仅仅说明p是一个指针类型变量
- p = &a; 把某一个变量的地址给p , 后面再使用*p就是代表a的空间
- *p = 90; 代表的是给a的空间赋值, 而 *p是直接取出a空间的值
- char *cp; 字符指针
指向指针的指针
int a = 10;
int *p = &a; //等于 int *p; p = &a;
int **pp = &p; //等于 int **pp; pp = &p;
此时,
*p
**p
本质上指向的都是变量a的存储空间,打印他们的地址,都是一样的
而每个指针变量,还有自己的地址,都是不一样的
指针类型
%zd, sizeof(*cp);
任何指针都占用8个字节的存储空间
指针的的类型,决定了指针从地址开始取多少个字节
指针与数组
int ages[5] = {34,56,65,67,34};
int *p;
p = &ages[0]; //或者 p = &ages;
p++; // 或者p+1;
printf("%d\n",*p);
把数组的一个元素的指针给p,
p存储的是指针类型,是一个地址,那么p+1,就是一个地址加上整形1,由于整形1占4个字节,那么p所指向的
地址也加四个字节,又因为整形占四个字节,所以p+1相当于指针p指向数组的元素地址向下移动了一个位置,
也就变成下一个元素了
指针变量的+1究竟加多少,取决与指针的类型:
int * 4
char * 1
double * 8
把数组给一个指针变量,就是把数组的第一个元素的地址给了指针变量
指针与字符串
char name[] = "it";
char *name2 = "it"; //把一个字符串给一个指针变量,参照数组,其实就是把字符串的第一个字符的地址给了指针变量.
printf("%c\n",*name2); //i
printf("%s\n",name2); //it
在OC中,用的比较多的就是指针操作字符串
数组定义的字符串是在栈中,它的值是随便可以更改的 (字符串变量)//使用场合,字符串的内容需要经常修改
而指针指向的字符串,是放在常量区,值不可以更改 (字符串常量) //经常用到的不需要修改的字符串
*name2 = "it";
*name3 = "it";
他们的地址是一样的
也就是说,放在常量区的会有一个缓存,我们下次再用的时候就不会再开辟一个存储空间了
指针数组: char *name[5] = {"aaaa","baaa","caaa","daaa","eaa"}
返回指针的函数
char *test()
{
return "json";
}
指向函数的指针
//void代表函数的返回值类型
//固定写法 (*p)代表 这是一个函数指针
//() 指针指向的函数有没有形参
void (*p)();
p = test; //把test函数的指针给P
(*p)(); //这样就可以直接代用函数 (*p)取出函数 ()调用函数
p(); //这样也可以调用函数 p已经代表函数的指针了
test(); //这样原始调用
其他数据类型
变量类型
局部变量:
定义在函数或者代码块中的变量,函数和代码块一执行完就销毁
全局变量:
定义在文件中,程序执行完销毁
结构体
构造类型:
数组 只能有一种数据类型
结构体:
可以有混合类型
结构体类型是不存在的,要我们自己定义:
struct Person //定义类型
{
int age;
double height;
char *name;
}
struct Person p = {20,1.55,"jack"}; //定义变量
p.age = 30; //还可以这样定义值
struct Person p = {.age = 30,.height = 1.44,.name = "rose"}; //还可以这样定义
内存分析:
定义结构体并不会分配存储空间
一个结构体占的空间,是所有成员的和
对其算法,结构体所占的存储空间,一定是最大成员所占字节数的倍数
定义结构体的多种方式
上面说了一种,还有一种
struct Person //定义类型
{
int age;
double height;
char *name;
} p; //定义变量;
不可重用的方式:
struct //定义类型
{
int age;
double height;
char *name;
} p; //定义变量;
类型的作用域
和变量的作用域差不多
结构体数组
struct Person pes[3] = {
{},
{},
{}
};
指向结构体的指针
struct Person *p;
p = &Person; //指针变量想保存谁,就把谁的地址拿过来存起来
第一种方式:
Person.age;
第二种方式:
(*p).age;
第三种方式:
p->age;
嵌套定义:
struct Person //定义类型
{
int age;
double height;
char *name;
struct Date a;
struct Date b;
};
枚举类型
定义枚举类型
enum Season
{
spring, //0
summer, //1
autumn, //2
winter //3
}
定义枚举变量
enum Season s = spring;
总结
基本数据类型
int
long int, long: 8字节 %ld
short int, short: 2 %d %i
unsigned int, unsigned: 4 %zd
signed int, signed , int:4 %d %i
float\double
float: 4 %f
double: 8 %f
char
1字节 %c %d
char类型保存在内存中的是它的ASCII值
'A' => 65
构造类型
数组
只能由同一种类型的数据组成
定义: 数据类型 数组名[元素个数]
结构体
可以由不同类型的数据组成
先定义结构, 再利用类型定义变量
指针类型
变量的定义
int *p
指针赋值
p = &age;
取值
*p;
修改值
*p = 22;
枚举类型
当一个变量只允许有几个固定取值时,可以使用枚举
其他
预处理指令
把代码翻译成0和1之前执行的指令(编译)
所有的预处理指令都是以#开头
位置随便写(也是有作用域的,从编写指令的那个位置开始,一直到文件结束)
宏定义
全局定义
#define COUNT 6 //也就是常量了,不过在编译之前就会执行,这样数组使用不会报错了
int age[COUNT] = {1,2,3,4,5,6};
失效
#undef COUNT;
带参数的宏定义
#define sum(v1,v2) (v1+v2) //不加括号在用的时候会乱
条件编译
按条件去编译
#if (A == 5)
...
#elif (A == 6)
...
#endif
...
判断有没有定义宏
#if define(MAX) //#ifndef MAX
...code...
#endif
...
文件包含
<>表示系统自带的文件, ""表示自定义的文件
不允许循环包含 A包含B,B包含A
解决多次包含只解析一次:
#ifndef A
#define A 123 //可以不写值,定义一个空的宏
int sum () {}
#endif
一般以文件名为宏名
关键字
typedef
int
typedef int MyInt; //给int起个别名
char *
typedef char * String; //给字符串指针一个别名
String name = "asdf";
结构体
typedef struct Student Mystu; //给结构体定义一个别名
Mystu stu1,stu2;
还可以
typedef struct Student
{
int age;
} Mystu;
还可以
typedef struct //只能用Mystu访问了, 上面那种方法还可以使用原本的方式访问
{
int age;
} Mystu;
定义枚举类型,也可以参照结构体
指向函数的指针
typedef int (*MyPoint)(int, int);
Mypoint p = sum;
Mypoint p2 = minus;
原本是
int (*p)(int ,int) = sum;
指向结构体的指针
typedef struct Per * PersonPoint;
PersonPoint p = &p;
还可以用宏定义来代替
#define String char *; //但是只能单个使用,不能 String s1,s2;
static 和 extern
对函数和变量有效
对函数
外部函数:本文本和其他文本都可以访问,默认 extern省略
内部函数:只能被本文本访问,其他文件不能访问 static
两个文件都有同样名字的内部函数,是不冲突的
对变量
全局变量分两种:
外部变量:本文件和其他文件都可以访问 默认 extern ()
不同文件中的同名变量,都代表同一个变量
内部变量:只有本文件可以访问 static
static 定义了一个内部变量
extern 声明了一个外部变量
static 对局部变量的作用
static修饰的局部变量(函数内) 函数结束变量不释放,一直到程序结束
并没有改变局部变量的作用域
多次执行函数,变量的值一直在变,所有的函数共享这个变量
递归
其他补充
指针的实质就是保存了一个地址
指针在64位系统中占8个字节
因为64的系统,内存地址有2的64次方,所以要8个字节去保存
在分配内存的时候,gcc编辑器会做一个优化,优先分配同一类型的变量,
而不是按照代码的编写顺序进行分配
内存结构,从上到下:
系统内存 系统使用
栈内存 保存的函数执行的状态,局部变量值
可分配内存 由应用程序自由调配
堆内存 程序员自己分配
数据段 常量,全局变量,和静态变量
代码段 代码 a + b
array本质上是一个指针常量,地址固定
P指针是一个指针变量,位置可以随便移动 也可以用p[i]取值