今天小编给大家带来c语言学习之路--由浅入深(快速掌握c基础)。温馨提示:
1.第一个C程序:HelloWorld.c
首先我这里是使用这个软件编写的:
安装过程一直next就好了
安装后在你的代码目录创建一个HelloWorld.c,代码目录可以随意,然后双击打开HelloWorld.c就可以默认进入我们下载的c开发软件中,如图:
其中图中标记为我们java中常用的编译和运行
下面就可以开始我们c语言的第一个helloWorld,通常说从helloWorld开始可以快速成为大神:
#include // java import xxx.xx.pack 引用函数的声明 #include
main() // 程序的入口函数
{
//代码文件目录是本级目录则会执行成功,否则会找不到该类
printf("Hello world ! "); // 控制台打印一个hello world
//如果不是本级目录必须先指定目录地址,才能执行成功
system("java -classpath c:\ HelloWorld");
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
代码中我们首先可以看到
1、 这个东西拿java来说就是我们通常的包名
2、main函数是主入口,和java一样
3、c语言中必须使用system("pause");使命令行暂停,方便观察程序的执行结果,否则结果会在你眼中一闪而过
2.C语言的基本类型与JAVA基本类型对比:
上一节中我们开始我们的第一个helloWorld,下面我们讲学习c中的数据类型:
// java数据类型 和长度int 4个字节 double 8个字节 float 4个字节 long 8个字节
// short 2个字节 boolean 1个字节 char 2个字节 byte 1个字节
// char, int, float, double, signed, unsigned, long, short and void
// c语言中 数据类型比java少一些 在c语言中没有 boolean类型的数据 int 1 代表真 0 代表假
// c 语言中没有String类型的数据 java中表示一个字符串 String , c语言中表示字符串 通过char类型的数组来表示字符串
// c 语言没有byte类型 所有用char的类型表示byte类型
#include #include
// sizeof(); c语言的一个函数 可以把 某种数据类型的长度获取出来 int
main()
{ // %d 类似sql语句的? 占位符
printf("char的长度为%d ", sizeof(char));//1
printf("int的长度为%d ", sizeof(int));//4
printf("float的长度为%d ", sizeof(float));//4
printf("double的长度为%d ", sizeof(double));//8
printf("long的长度为%d ", sizeof(long));//在不同的情况下可能会有不同的大小,但是long的长度一定比int大 4
printf("short的长度为%d ", sizeof(short));// 2
//signed, unsigned, 数据类型的修饰符
// signed int ; 代表的是有符号的int的数据
// unsigned int ; 无符号的int数据
printf("signed int的长度为%d ", sizeof( signed int));//4
printf("unsigned int的长度为%d ", sizeof( unsigned int));//4
// 符号的修饰符 只能修饰 整数类型的数据 long int
// 不能修饰 浮点型的数据 float double
// printf("signed float的长度为%d ", sizeof( signed float));
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
从上面我们可以知道,c语言有以下几种数据类型:
char, int, float, double, long, short
使用char表示java的byte类型数据
使用char数据去表示java中String类型的数据
c的两种修饰符 signed, unsigned,
3.C语言中的输入输出函数:
上一节我们了解c语言的基本数据类型,下面我们看看c的输入输出函数:
/*%d - int
%ld – long int
%c - char
%f - float
%lf – double
%x – 十六进制输出 int 或者long int 或者short int
%o - 八进制输出
%s – 字符串
Int len;
Scanf("%d",&len);*/#include // java import xxx.xx.pack 引用函数的声明 #include
main() // 程序的入口函数
{ int i = 3;
float f = 3.1415;
double d = 6.2815;
char c = 'A'; //通过单引号定义字符
short s = 2;
//输出的时候占位符和数据类型必须一一对应,否则得不到正确的结果
printf("int i=%d ",i);
printf("float f=%f ",f);
printf("char c=%c ",c);
printf("double d=%lf ",d);
printf("short s=%d ",s);
/*char arr[20] ; //定义一个长度为20的数组
//java中是System.in
scanf("%s",arr); // 从键盘接受一个字符串,放在c数组里面
//java中是System.out
printf("s =%s ",arr);
*/
int j ;
scanf("%d", &j);//&代表的是取地址
//从控制台得到j地址所代表的址输出
printf("j=%d ",j);
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
从代码中我们知道c语言的 输入:scanf();函数 根据地址去输入&j 输出:printf();函数
4.指针入门:
上一节我们学到了输入输出,下面我们将学习一个新名词指针 首先java中是没有指针这个名词的,
指针是什么? 指针就是一个地址 地址代表的就是一块内存空间 指针变量是什么? 用来存放指针
从上面我们就可以知道java中的内存控件就是c语言中的指针,下面我们看下代码:
#include #include
main()
{
int i =5;// 定义一个int 类型的变量i 值 =5
//%#X表示16进制的地址占位符
printf("i的地址 %#X ",&i);
//获取i的地址,&i就是一个指针
// &i;
//定义一个指针变量,数据类型*
int* p ; // 指针变量 定义一个int* 类型的变量p
//其他两种表示方式:int *p, int * p;
p = &i; // 就是把i的指针赋给指针变量p ,现在指针变量p里面存放的内容(数据) 就是i的地址
printf("p里面的内容为(i的地址) %#X ",p);
//*号 操作符
// *号的几种含义
//1 . *号放在某种数据类型的后面,代表就是这种数据类型的指针 int* float*
//2 . *号 代表一个乘法符号 3*5 = 15;
//3 . *号放在一个指针变量的前面 -> 代表取这个指针变量所存放的地址里面对应的数据
printf("i=%d ",i);
printf("*p的值%d ",*p);
// 改变p的值 会不会影响i的值?
//p = NULL;
// printf("i=%d ",i);//5
// 改变i的值 会不会影响p的值?
// i = 100;
// printf("p里面的内容为(i的地址) %#X ",p);
// 通过上述实验 p和 i 是两个不同的变量 ,改变i的值 不会影响 p的值,同理,更改p的值 也不会影响i的值
// 更改*p的值 会不会影响i的值
// *p = 88;
// printf("i=%d ",i); //88
// 更改i的值 会不会影响 *p的值呢?
// i = 99;
// printf("*p的值%d ",*p); //99
//*p 和i 其实代表的是同一个变量,代表是同一块内存空间
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
从代码中我们可以知道:
· 1.指针就是地址,而c语言中我们在变量前面加上&符号就可以到地址也就是指针如:int i=9;那么i的指针表示方式是&i
· 2.指针变量就是用来存放指针的一个变量,如:数据类型* 变量名;如:int * p 这样就是一个指针变量,将我们上面的&i=p这样我们就将i的指针放在了指针变量p中
· 3.得到指针变量中的值使用符号*,例如我们将上面指针变量p的指针i的值取出来表示就是:*p
· 4.*p 和i 其实代表的是同一个变量,代表是同一块内存空间
·
5.指针介绍:
上一节我们已经知道了指针和指针变量的用法,下面我们将通过一个小程序进一步说明指针
#include #include
main()
{
// 所有的变量都会分配一块内存空间
// 指针就是用来表示一块内存空间的地址的
// 地址可以用过 &这个符号获取到某个变量的在内存中的地址
// 这个地址如果想把他存放起来 就需要有一个变量 去存放这个地址
// 存放内存地址的变量 就是指针变量
// 指针和指针变量
// 指针是用来表示一块内存地址的,
// 指针变量是用来存放一个内存地址的 .
//
printf("ready go! 剩余时间60秒 ");
int time = 60;
printf("time变量对应的内存地址为%#X ", &time);
for(;time>0;time--){
printf("剩余时间%d ",time);
sleep(4000);
}
printf("游戏结束");
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
从代码中我们编写了一个for循环,打印time指针值,可以在命令行程序已经在开始走了
从图中可能大概你们可以看到我使用一个外挂软件找到了time指针值,外挂地址
怎么使用外挂软件:
1.首先点击图中的箭头图标,
2. 弹出Process List在里面找到我们的程序,然后点击,
3.左边Address栏就会出现我们程序中的time地址
从图中我们居然看到我们的程序运行到22的时候怎么又从59开始了,正常情况下我们是21啊,这是怎么回事呢? 哈哈,这其实就是用到了我们上图 中的外挂,首先我们使用外挂找到我们的time地址,然后将time 的值改为60之后就发现命令行又从60开始了。
通过这个外挂我们就更加深刻了解指针的作用,下面我们将使用几个案例去了解指针的一些细节
6.案例:使用指针交换两个数据:
#include
#include
// 问 java 中有值传递和引用传递 吗? 他们的区别是什么?
// 其实在java中只有值传递 , 没有引用传递
// Person p = new Person(); p里面存放的内容 就是person对象的地址
void swap2(int* p , int* q){ // 传递的形参为 i 和j 变量的地址
// *p 代表 i *q 代表就是 j
int temp;
temp = *p;
*p = *q;
*q = temp;
}
void swap1(int i ,int j){ // 形参 i 和j 跟主函数里面的i和j是两个不同的变量
printf("子函数 i 地址%#X ",&i);
printf("子函数 j 地址%#X ",&j);
int temp;
temp = i;
i = j;
j = temp;
}
main()
{
//利用指针 可以在子函数里面修改主函数里面的数据
int i = 3;
int j = 5;
printf("i=%d ",i);
printf("j=%d ",j);
printf("主函数 i 地址%#X ",&i);
printf("主函数 j 地址%#X ",&j);
/*/交换两个数字
int temp;
temp = i;
i = j;
j = temp;
*/
//方法一中确实将i,j的值交换了,但是当函数一执行完,函数被回收,i,j就被销毁了,交换失败
// swap1(i,j);
//方法二接收的是i,j的地址,i,j在main函数里,所以数据交换成功
swap2(&i,&j);
printf("交换后 ");
printf("i=%d ",i);
printf("j=%d ",j);
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
7.案例:使用指针获取子函数的数据:
#include
#include
//int** q 表示里面存放的是一个int* q的指针
f(int** q){
int i = 3;
printf("子函数 i的地址 %#X ",&i);
// *q 代表的就是p变量
*q = &i;
} /**
使用指针的时候 不可以访问已经被系统回收掉的数据
子函数执行完毕后 子函数里面所有的局部变量都会别系统回收
*/
main()
{
// 希望在主函数里面去使用子函数里面的变量 i
// f();
// 希望在主函数里面得到子函数 里面int i变量的地址
int* p ; //存放子函数f中 int i的地址的一个变量
f(&p);
/**在6中我们已经说过函数会被回收,
* 当f()函数一执行完,就会被回收,i就会被回收
* 但是回收是有一定的时间,所以如果我们在正在回收还未回收完全时去取值,
* 会得到正确的值或者地址,但是如果我们比如下面我先打印地址值,在去打印*p
* 此时p是i的地址值,而*p就不是了,因为此时i被回收了,但是我们注释输入地址值
* 此时*p就会成功打印i的值
*/
// printf("主函数 i的地址 %#X ",p);
// printf("i的值为 %d ",*p);
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
8.案例:使用指针返回一个以上的值
#include
#include
// public List getPersons() {};
// public byte[] getbytes(){};
/*
如果让子函数 更改主函数里面的数据
如何让子函数 返回一个以上的值
1.子函数的形参 为 主函数中要修改的变量的地址
2. 调用子函数的时候 把要修改的变量的地址 传递给子函数
3. 在子函数里面 修改这个地址里面存放的变量的内容
4. 主函数使用这个变量的时候 里面的值就发生了变化
*/
int f(int* p, int* q){
*p = 33;
*q = 55;
}
main()
{
int i = 3;
int j = 5;
f(&i,&j);
printf("i=%d ",i);//33
printf("j=%d ",j);//55
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
6.7.8案例总结:
1.子函数在main函数中调用结束后会被销毁,随之传入的形参也会被销毁
2.main函数里的数据要想通过子函数进行交互,子函数的传入的参数必定是指针变量
3.方法中的返回值不能像java中一样返回集合之类的数据,通过指针去返回多个数据。
9.指针的常见错误:
通过6、7、8节我们细致的了解了指针,下面我们将讲解指针的一些错误:
#include #include
main()
{
/* int* p; //定义一个指针变量 垃圾值 -> 野指针
//printf("*p=%d ",*p);
*p = 1231;
// 立刻蓝屏
//
指针变量如果没有赋值就不能使用
*/
/* int d = 324233;
char* c; ; // 编译错误 不符合的指针类型
c = &d;
printf("*p = %c ",*c);
类型不相同的指针不可以互相转换
*/
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
错误总结:
1.指针变量中必须得有指针,否则在取指针变量中的指针的值的时候会异常
2.不能给没有定义指针的指针变量的指针的值赋值
3.指针变量和指针的数据类型必须一一对应
10、指针占多少个字节:
之前第二2节中我们知道了c的基本数据类型的字节数,那么指针占多少个字节呢? 看代码:
#include #include
main()
{
int i =3;
double d = 3.141692;
float f = 3.1423;
char c ='B';
int* ip = &i;
double* dp = &d;
float* fp = &f;
char* cp = &c;
//电脑不同输入的值可能是4可能是8
printf("int 类型指针变量的长度为 %d ",sizeof(ip)); //8
printf("double 类型指针变量的长度为 %d ",sizeof(dp)); //8
printf("float 类型指针变量的长度为 %d ",sizeof(fp)); //8
printf("char 类型指针变量的长度为 %d ",sizeof(cp)); //8
// 在32位的操作系统上 因为程序 最大能使用的内存空间的地址 就是2的32次方
// 指针只需要4位 就可以表示出来所有的内存空间
// 64 并且编译支持64位 8位
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
首先我们定义了不同的几个变量,然后将他们的地址放入指针变量中,最后输出,我们居然发现输出结果都一样。
总结:
1..指针变量的内存大小是固定的,与数据类型没有关系
2.指针变量的内存大小最终取决于我们的电脑
11、使用char* 指针表示字符串
在第二节中我们知道,c语言中是使用char数组去表示一个java中的String字符串, 在我们学习指针之后我们将看看怎么用指针更加简单的表示一个字符串:
#include #include
main()
{
char arr[20] ={'h','e','l','l','o',''};
// 利用char类型指针 方便的表示一个字符串
char* arr1= "hello world";
printf("%s",arr1);
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
从代码中我们可以看到使用char数组的方式是比较复杂的,然后我们使用指针的方式:char* arr1="",这样就可以简单的直接表示一个字符串
注意:在java中我们定义数组可以int arr[]; int [] arr;但是在c语言中[]只能卸载变量名后面,如:int arr[]
12、指针与数组
指针进阶:
#include
#include
// 数组是一块连续的内存空间 数组名 就是内存空间的首地址
// 数组名[i] == *(数组名+i);
main()
{
/* char[] arr = new char[20];
char arr[] ;
*/
// 创建一个长度为5的int类型的数组
int arr[5] ={1,2,3,4,5};
printf("a[0]=%d ",arr[0]);
printf("a[4]=%d ",arr[4]);
// 逻辑上是错误的代码 数组下标越界
// printf("a[5]=%d ",arr[5]);
// windows xp 缓冲区越界补丁
// arr是一个什么东西呢?
printf("arr = %#X ",arr);
// 打印 数组的第一个元素的地址
printf("arr[0]地址 = %#X ",&arr[0]);
// 打印数组中的第二个元素
printf("arr[1]=%d ",arr[1]);
printf("arr[1]=%d ", *(arr+1));
//问题: arr[i] *(arr+i) 代表的是同一个变量么?
// 代表的是同一块内存空间 指向的是同一个变量
//通过实验 : 数组名表示的 就是这个数组第一个元素 的首地址
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
· 语言语法上没有数组越界,在逻辑上存在数组越界,
· arr是一个指针,arr的指针就是&arr[0]的指针,也就是说数组的指针就是数组第一个元素的指针
· 数组中的数据的指针是一块连续的内存空间
· arr[i]等同于*(arr+i)
13、指针的计算
通过上一节我们大概知道了指针与数组的关系,下面我们通过一个案例去巩固一下:
#include #include
main()
{
int i =3; //天津的某个路上 盖了一个房子 3
int j =5; // 北京的某个路上 盖了一个房子 5
int* p = &i; // p 天津的门牌号
int* q = &j; // q 北京的门牌号
// 指针的运算和数组都是紧密关联的
char arr[5]={'a','b','c','d','e'}; //一块连续的内存空间
char* p1 = &arr[2];
printf("char = %c ", *(p1-1));
// char 内存中占用 1个字节
// int 内存 中占用 4个字节
int intarr[5]={1,2,3,4,5}; //一块连续的内存空间
int* q1 = &intarr[2];
printf("char = %d ", *(q1-1));
// 指针的运算 按照 约定好的数据类型 偏移相对应的内存空间的大小
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
总结:
1. 数组中的数据的指针是一块连续的内存空间
2. 指针的运算按照约定好的数据类型偏移相对应的内存空间的大小
14.案例:通过子函数打印数组
#include
#include #define pi 3.1415
// 写一个子函数 打印数组里面的每一个元素
void printArr(int* arr, int len){ // arr是数组的首地址 len数组的长度
int i=0;
for(;i
// printf("arr[%d]=%d ",i,arr[i]); // arr[i] 和 *(arr+i) 代表的含义相同
printf("arr[%d]=%d ",i, *(arr+i));
}
}
main()
{
// int arr[10]={1,2,3,4,5};
printArr(&arr[0],10);
//1 .定义一个数组 缺陷 数组的长度 必须事先申请好
//2. 循环赋值
//3. 打印数组里面的内容
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
小编给大家推荐一个学习氛围超好的地方,C/C++交流企鹅裙:487875004适合在校大学生,小白,想转行,想通过这个找工作的加入。裙里有大量学习资料,有大神解答交流问题,每晚都有免费的直播课程
总结:
1.int arr[5]; 这一句代码一旦执行 ,就立刻会在内存里面申请 5个内存空间 每个内存空间的大小可以存放一个int类型的数据
2.留下疑问:没有办法动态的增加这一块空间的大小, 也没办法减小这一块内存空间,只能提前写死,例如我们代码中arr[10],直接就将我们的arr数组大小写死了
15、realloc()方法介绍
在上一节中我们留下的疑问是不能更改数组的内存控件,而realloc()方法则完美解决了这个问题,具体使用方式:
int arr[10];
arr = realloc(arr,sizeof(int)*12); //空间的长度为12了
首先我们可以看到realloc接收两个参数:
1.第一个参数表示你需要更改大小的变量
2.第二个表示更改之后的长度
我们了解了realloc()方法是可以更改变量内存大小的,那么就会存在这样一个问题,改变之后的内存会不会覆盖之前的数据呢?
1.对于增加内存:realloc是直接在原有的基础上添加内存空间,所以还是会保持原有的数据再进行添加
2.对于减少内存:realloc是会将排列在最后的内存空间值给回收掉的, 比如原始数据int arr[4]={1,2,3,4};减少2个内存的话,就是int arr[8]={1,2}
·
16、动态分配内存
前面我们看到我们定义的那些函数变量等,都是由系统给我们自动分配内存,我们是不用去理会系统到底什么时候去给我们分配,什么时候去回收,
而接下我们要学习的是自己去管理内存即动态分配内存,看代码:
#include
#include
#include
// malloc memory allocate 内存申请
main()
{
// 接受一个参数 申请多大(byte)的内存空间
int* p = (int*)malloc(sizeof(int)); // 在堆内存里面申请一块可以存放一个int类型数据的内存空间
*p = 4; // 就是往 p 里面存放的地址 表示的那块内存空间里面存放一个int类型的数据 4
printf("*p=%d ",*p);
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
代码中我们首先如java导入类的方式,在代码上面添加了一行: 这个就是动态内存所要用到的方法了,接下来我们在main函数中 int* p = (int*)malloc(sizeof(int)); 这样我们即给指针变量p分配了int字节大小的内存空间,完成了一个动态分配内存的操作
这样我们就可以使用如上方式去解决上一节中的疑问了
学到的知识点:
· malloc(byte length)方法用于动态申请内存。
· 动态申请内存其实就是在申请指针
疑问:
· 我们之前看到的,函数使用完成后是会被回收掉的,难道我们动态申请内存怎么被回收呢?不然是会浪费内存空间的
17、动态分配内存2
在上节中我们已经知道了malloc(byte length)方法是用来动态分配内存的, 下面我们将通过一个小案例来进一步讲解动态分配内存,并且解决上一节中的疑问
#include
#include
#include
f(int** address){ //address 存放的是q的地址
// 动态的在堆内存里面申请一块空间
int* p ;
p = (int*)malloc(sizeof(int)*3);
*p = 3;
*(p+1) = 4;
*(p+2) = 5 ;
printf("子函数里面 地址%#X ",p);
*address = p;
// 在子函数里面把p释放掉了
//free(p);
}
main()
{
int* q ;
f(&q);
printf("主函数里面 地址%#X ",q);
printf("*q = %d ",*(q+0));
printf("*q = %d ",*(q+1)); // 残留的内存映像
printf("*q = %d ",*(q+2));
//动态内存分配 程序员可以自己手工的决定一个变量的生命周期
//手工的释放调用内存空间
//不要使用已经回收掉的内存空间里面的数据
system("pause"); // 调用windows下系统的命令 让程序暂停执行 方便观察程序的执行结果
}
首先我们看到是f(int** address) 其中int **表示的是多级指针,下面我们会详细介绍,在这里只要知道他是一个存放指针变量的指针变量就行了, 然后定义一个指针变量p并动态分配内存和赋值, 最后将p传递给f(int** address)的形参
接着我们看到main函数中我们使用了f()函数并得到了f()函数中的值
细心的同学可能可以看到我们在f()方法中可以看到我们注释的一个函数:free(p)
free(p):将动态释放的内存给回收,使用free(p)函数即可解决我们在上一节留下的疑问,其中p是我们需要释放内存的指针变量
接着我将free(p)打开,按理说p分配的动态内存空间的数据是会被free(p)执行后给回收的,最后居然发现在main函数打印(q+0)为0,(q+1)为4,(q+2)为5, 结果可想而知free(p)难道只将第一个数据给回收了?其实在执行free(p)之后确实将p给回收了,(q+1)为4,(q+2)为5,只是残留的映像,在回收过程中只是将p的内存空间标记为可以再次写入,其实真正并没有彻底回收,所以会出现这种情况