P1 数据类型
一.常见的数据类型
char //字符串数据类型 %c
short //短整型
int //整型 %d
long //长整型 %ld
long long //更长的整型
float //单精度浮点数%f
double //双精度浮点数 %lf
sizeof()获取数据在内存中所占用的存储空间,以字节为单位计数
二.变量的分类
例如:int age=10;
1.局部变量
定义在函数代码块内部的变量,或者{}内可以定义局部变量 括号外不能使用
2.全局变量
定义在函数代码块外部的变量,
变量名字相同的时候局部变量优先,局部变量和全局变量建议不要相同。
三.变量的作用域和生命周期
限定这个名字的可用性的代码范围就是这个名字的作用域
局部变量的作用域:变量所在的局部范围{}内
全局变量的作用域:整个工程。
P2初识C语言
scanf是C语言提供的,scanf_s不是标准C语言提供的,是VS编译期提供的,不具有跨平台性。不建议因为使用VS编译期,把scanf替换成VS推荐的scanf_s
#define _CRT_SECURE_NO_WARNINGS 1 让警告失效
一.常量
1.字面常量
2.const修饰的常量
3.define 定义的标识符常量
4.枚举常量
#include <stdio.h>
int main()
{
//①字面常量演示
3.14;//字面常量
1000;//字面常量
//②const-常属性
//const 修饰的常变量,使变量不能进行变值
const float pai = 3.14f; //这里的pai是const修饰的常变量
pai = 5.14;//是不能直接修改的!
// const int n = 10;
// int arr[n] = {0}; 出错!不可以这样使用
// const 修饰的常变量在C语言中只是在语法层面限制了变量,n不能直接被改变,但是n本质上还是一个变量的,所以叫常变量
//③#define的标识符常量演示
#define MAX 100
int arr[MAX] = {0};
printf("max = %d\n", MAX);
//④枚举常量演示 一一列举
//枚举关键字-enum
//举例:性别:男,女,保密
enum Sex //Sex是枚举类型
{
MALE,
FEMALE, //括号中的MALE,FEMALE,SECRET是枚举常量
SECRET
};
//enum Sex s = FEMALE;
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
//注:枚举常量的默认是从0开始,依次向下递增1的。默认MALE是0,FEMALE是1,SECRET是2
//枚举常量不能更改,但是通过枚举类型创建出来的变量可以更改
return 0;
}
二.字符串+转义字符+注释
1.字符串
"hello \n"
这种由双引号 引起来的一串字符称为字符串字面值,简称字符串。
注:字符串的结束的标志 \0 的转义字符,在计算字符串长度的时候 \0是一个结束标志,不算做字符串内容长度。
#include <stdio.h>
//下面代码,打印结果是什么?为什么?(突出'\0'的重要性)
int main()
{
char arr1[] = "bit";
char arr2[] = {'b', 'i', 't'};
char arr3[] = {'b', 'i', 't',0};
char arr4[] = {'b', 'i', 't','\0'};
printf("%s\n", arr1);
printf("%s\n", arr2);
printf("%s\n", arr3);
printf("%s\n", arr4);
return 0;
}
//“abc”里包括'a' 'b' 'c' '\0'
//'\0'是字符串的结束标志,\0的ASCII值是0
#include<stdio.h>
#include<string.h>
int main(){
char arr1[] = "abc";
char arr2[] = {'a', 'b', 'c'};
printf("%d\n",strlen(arr1));
printf("%d\n",strlen(arr2));
return 0;
}
2.转义字符
转义字符 释义
\? 在书写连续多个问号时使用,防止他们被解析成三字母词
\'
用于表示字符常量'
\" 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1-3个八进制的数字。如:\130 X
\xdd dd表示2个十六进制数字。如:\x30 0
3.注释
三. 选择语句
四. 函数
五.数组
int arr[5]={};自动添加内部元素
int arr[] ={1,2,3,4,5,6}
int 是指的数组里元素的类型,[]里的是常量,不可以是变量,数组中每个元素所占空间大小一样,并且内存地址连续
数组可以通过下标访问元素 例如 printf("%d\n",arr[4]); //下标的方式访问元素
六.操作符
操作符种类 | 操作符 | 作用 |
算数操作符 | +-*/% | |
移位操作符 | >> ,<< | |
位操作符 | | 按位与&按位或 ~按位异或 | |
赋值操作符 | =, += ,-=, *= ,<<==,>>==,~=,|=&=, | |
单目操作符 | !,-,+,&取地址,sizeof,~, (类型)强制类型转换 | |
条件操作符 | <表达式1> ? <表达式2> : <表达式3>; "?"运算符的含义是:先求表达式1的值,如果为真,则执行表达式2,并返回表达式2的结果;如果表达式1的值为假,则执行表达式3,并返回表达式3的结果。 | |
逻辑操作符 | &&逻辑与 ||逻辑或 | |
下标引用,函数调用,结构成员 | [] () . ->指针 | |
P4
只要是正数 内存中二进制的补码
正数的原码补码反码都是一样的
原码:直接按照正负写出的二进制序列
反码:原码符号位不变,其他位按位取反
补码:反码+1
一.常见关键字
关键字:register
如果一个变量用register来修饰,则意味着该变量会作为一个寄存器变量,让该变量的访问速度达到最快。例如,一个程序逻辑中有一个很大的循环,循环中有几个变量要频繁进行操作,这些变量可以声明为register类型。
总结:
在使用寄存器变量时,请注意:
(1)待声明为寄存器变量的类型应该是CPU寄存器所能接受的类型,寄存器变量是单个变量,变量长度应该小于等于寄存器长度。
(2)不能对寄存器变量使用取地址符“&”,因为该变量没有内存地址。
(3)尽量在大量、频繁操作时使用寄存器变量,且声明的变量个数应该尽量少。
关键字:signe ,signed
c语言中分为有符号数和无符号数,有符号区分正数和负数,无符号数只有正数,程序中不通过signe ,signed 定义的默认为有符号数
关键字:typedef
类型的重定义
1.对数据类型的使用
typedef int NEW_INT;
将int类型起一个新名字NEW_INT
2.对指针的使用如下
typedef int *PTRINT;
将int* 命名为PTRINT
PTRINT x; 等同于int *x;
3.对结构体的使用
typedef struct NUM
{
int a;
int b;
}DATA,*PTRDATA;
DATA等同于 struct DATA,*PTRDATA等同于struct NUM *。
定义结构体变量可以简化为
DATA data; //定义结构体变量
PTRDATA pdata; //定义结构体指针
进阶待整理
关键字:static
修饰局部变量,全局变量和函数
1.static 修饰局部变量时
static修饰局部变量时,会影响局部变量的生命周期,本质上改变了局部变量的存储位置,生命周期变长,为整个周期。静态局部变量存储于进程中的全局数据区。
2.修饰全局变量
首先我们要知道全局变量的属性:全局变量具有外部链接属性。而static修饰全局变量时,这个全局变量的外部链接属性变为内部链接属性,是其他源文件(.c)文件就可以再使用这个符号全局变量了。则使用时我们会感觉到该全局变量作用域变小。
3.修饰函数
此类情况类似于static修饰全局变量。函数同样具有外部属性。而static修饰函数时,这个函数的外部链接属性变为内部链接属性,是其他源文件(.c)文件就可以再使用这个函数了。则使用时我们会感觉到该函数作用域变小。
总结:
static修饰变量
a. 函数中局部变量:
声明周期延长:该变量不随函数结束而结束
初始化:只在第一次调用该函数时进行初始化
记忆性:后序调用时,该变量使用前一次函数调用完成之后保存的值
存储位置:不会存储在栈上,放在数据段
b. 全局变量
改变该变量的链接属性,让该变量具有文件作用域,即只能在当前文件中使用
c. 修饰变量时,没有被初始化时会被自动初始化为0
2. static修饰函数
改变该函数的链接属性,让该函数具有文件作用域,即只能在当前文件中使用
关键字#define定义常量和宏
三****指针****
内存
内存是电脑的特别重要的储存器,计算中程序的运行都是内存中进行的,
所以为了有效的使用内存,就把内存分为一个个小单元格,每个内存单元格大小一个字节,为了能够有效访问内存的每个单元,就会给内存单元进行编号,这些编号称为该内存单元的地址
变量是创建内存中的(在内存中分配空间),每个内存单元都有地址,所以变量也是有地址的。取出变量的地址如下(首地址)
#include <stdio.h>
int main()
{
int num = 10;
#//取出num的地址
//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印
return 0;
}
int *p 可以理解成整型指针p。*p解引用
地址打印,地址一样
复杂类型说明
1. int*p// P处开始,先与*结合,说明P是一个指针,再和int结合,说明指针所指向的内容是的类型是int型。----P是一个返回整型数据的指针
2. int *p[] // 从P开始先和[]结合,说明P是一个数组,再和int结合说明数组内容是int整型,---P是一个存放整型的整型数组
3. int*p[] //首先和[]结合因为[]优先级高,所以是一个数组,再和*结合说明数组里存放的是执政类型,然后再和int结合说明存放的整型类型的指针
4. int(*p)[] //首先P和*结合说明P是一个指针,(()忽略不需要结合,改变优先级),再和[]结合,说明指针所指向的是一个数组,再和int结合,说明数组里整型,所以P是一个指向由整型数据组成的数组指针
int**p
int P (int)
int (*p)(int)
int *(*P(int))[3]
细说指针
指针是一个特殊的变量,他储存的数值被解释为内存里的一个地址,所以要搞清地址的四个方面:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,指针本身所占据的内存区
1.指针的类型
语法的角度,只要把指针声明语句的指针名字去掉,剩下的就是这个指针的类型。这是指针本身所具有的类型,例如下
char*p // char*
int*p // int *
int**p // int**
int(*p)[] // int (*)[]
int *(*p)[] // int*(*)[]
2.指针所指向的类型
当通过指针来访问指针所指向的内存区,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
语法上,只需要把指针声明语句中指针名字和名字左边的指针生命符号*去掉,剩下的就是指针所指向的类型
char*p // char
int*p // int
int**p // int*
int(*p)[] // int ()[]
int *(*p)[] // int*()[]
在指针的算数运算中,指针所指向的类型有很大的作用。
指针的类型(指针本身的类型)和指针所指向的类型是两个概念,当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。
运算符 | 计算形式 | 意义 |
---|---|---|
(+) | px+n | 指针向地址大的方向移动n个数据 |
(-) | px-n | 指针向地址小的方向移动n个数据 |
(++) | px++或++px | 指针向地址大的方向移动1个数据 |
(- -) | px- -或- -px | 指针向地址小的方向移动1个数据 |
(-) | px-py | 两个指针之间相隔数据元素的个数 |
指针的关系运算见下表:
运算符 | 说明 | 例子 |
---|---|---|
(>) | 大于 | px>py |
(<) | 小于 | px<py |
(>=) | 大于等于 | px>=py |
(<=) | 小于等于 | px<=py |
(!=) | 不等于 | px!=py |
(==) | 等于 | px==py |
两指针之间的关系运算表示它们指向的地址位置之间的关系。指向地址大的指针大于指向地址小的指针。指针与一般整数变量之间的关系运算没有意义。但可以和零进行等于或不等于的关系运算,判断指针是否为空。
3.指针的值-----叫指针所指向的内存区或地址
指针的值是指针本身存储的值,这个值被编译器当做一个地址,而不是一个一般的值,在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
4.指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
二.指针的算数运算
指针可以加上或者减去一个整数(4个字节)。指针的运算的意义和通常数值的加减运算不一样,指针以字节为单位。
//例1
char a[20];
int *p=(int*)a; 强制转换为int但不会改变a的类型
p++
//例2
int a[20];
int *p=a;
p++
在上例中,指针ptr 的类型是int*,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:
int main()
{
int arr[5] = {1,2,3,4,5};
int* p = arr;
for (int i = 0; i < 5; i++)
{
printf("%d\n", *p);
p++;
}
这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下一个单元。
总结一下:
一个指针ptrold 加(减)一个整数n 后,结果是一个新的指针ptrnew,ptrnew 的类型和ptrold 的类型相同,ptrnew 所指向的类型和ptrold所指向的类型也相同。ptrnew 的值将比ptrold 的值增加(减少)了n 乘sizeof(ptrold 所指向的类型)个字节。就是说,ptrnew 所指向的内存区将比ptrold 所指向的内存区向高(低)地址方向移动了n 乘sizeof(ptrold 所指向的类型)个字节。指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。
三.运算符&和*
&是取址运算符:&a得到的是一个指针,
*间接运算符:*p运算结果五花八门。*p的结果是p所指向的东西,他的类型是p所指向的类型,它所占用的地址是p所指向的地址。
四.指针表达式
一个表达式的结果是一个指针,那么这个表达式就叫指针表达式
五.数组和指针的关系
一般而言数组名代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此*array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以*(array+3)等于3。其它依此类推。
上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char *。
*str 也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'
下面总结一下数组的数组名(数组中储存的也是数组)的问题:
声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:
第一,它代表整个数组,它的类型是TYPE[n];
第二,它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。
表达式array+n(其中n=0,1,2,.....)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
上例中ptr 是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?
答案是前者。例如:
int(*ptr)[10];
则在32 位程序中,有:
sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
结构体指针
类型 变量名;
普通指针 char*p;
结构体指针 struct St *p;
四、数组
数组是构成数据类型之一;是具有一定顺序关系的若干个变量的集合,组成数组的各个变量成为数组的元素;数组的元素要求类型相同,数组可以是一维也可以是多维的。
1.1一维数组
在内存中是连续存储的,一维数组的一般说明形式为:
<存储类型> <数据类型> <数组名>[<表达式>]
数组名表示内存的首地址,是地址常量。因此在编程中,数组名不可修改,一旦修改,会造成数据存储位置发生变化,程序会发生重大错误
数组名是地址常量,但是数组中的每一项都是变量,可以被赋予不同的值。
使用函数sizeof(数组名),可以得到数组占用的总内存空间,一维数组编译时分配连续的内存,内存字节数=数组维数*sizeof(数组类型)
C语言中不对数组进行越界检查,使用时要注意下标(从0开始,int a[5]表示a[0]-a[4]可以使用),数组定义长度是可以用变量,如 int a= 10;arr[a];
一维数组初始化,在定义数组时就可以为数组元素赋值,如int a[5]={1,2,3,4,5},初始化有两个原则:若在定义时不赋初值,其元素值为随机数;对static数组元素不赋初值,系统会自动赋以0值;
1.2二维数组
二维数组的一般说明形式为:<数据类型> <数组名>[常量表达式(行)] [常量表达式(列)],在声明时列数不能省略,行数可以,元素个数=行数*列数
二维数组的初始化包括分行初始化和按元素排列顺序初始化,具体包括如下情况:
2、字符数组和字符串
字符数组是元素的类型为字符串的数组,如char[10],符数组在初始化时常用的方法为逐个字符赋值和字符串常量,如"abc"中包含的字符为 "a","b","c","\0"。
2.2 字符串
在C语言中,没有字符串变量,程序员需使用字符数组来处理字符串,字符串结束的标志是’\0’,例 “hello”共5个字符,在内存占6个字节,字符串长度为5,在内存中存放的是字符的ASCII码。
2.3 字符串函数
在C库中实现了很多字符串处理函数,在程序中调用时必须在头文件中包含#include <string.h>,常见的字符串处理函数有:①求字符串长度的函数strlen②字符串拷贝函数strcpy③字符串连接函数strcat④字符串比较函数strcmp,具体用法如下:
strlen()的一个傻瓜式求法为:无论数组元素有多少,见到’\0’即停止,且结果不包括’\0’。
简单说明sizeof()与strlen()的区别为:strlen()求得的结果为有效长度,而sizeof()求得的结果为实际空间大小(长度)。例:char s[10]={‘A’,‘\0’,‘B’,‘C’,‘\0’,‘D’};中使用strlen()函数结果为1,使用sizeof()函数结果为10
3.3 指针与一维数组
在C语言中,数组的指针是指数组在内存中的起始地址,数组元素的地址是指数组元素在内存中的起始地址。一维数组的数组名就是一维数组的指针(起始地址),例如:double x[8],x就是x数组的起始地址。
设指针变量px的值等于数组指针x,即指针变量px指向数组的首元素,int x[20],*px;px=x;则:x[i],(px+i),px[i],*(x+i)具有完全相同的功能,就是就是访问数组第i+1个元素
3.4 指针与二维数组
在C语言中,二维数组的元素连存储,按行优先存。可以把二维数组看作是由多个一维数组组成,比如int a[3][3],含有3个元素:a[0],a[1],a[2],元素a[0]、a[1]、a[2]都是一维数组名。二维数组名代表数组的起始地址,数组名加1,是移动一行元素。因此,二维数组名常被称为行地址。
P5
结构体
结构体是C语言中特别重要的知识点,结构体使得C有能力描述复杂类型。
比如描述学生,学生包括:名字、性别、年龄,身高、体重。
这里只能用结构体来描述
//打印结构体信息
struct Stu s = {"张三", 20, "男", "20180101"};
//.为结构成员访问操作符
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);
//->操作符
struct Stu *ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps- >id);
. 结构体变量.成员
->结构体指针->成员