01 - C语言基本数据类型和控制流程
01-C语言基本数据类型和控制流程
前言
- 希望你有Java基础,我会在文中大量采用Java语言进行同类相比,以达到解释目的。
- 学个NDK就要把整个C体系全部干一遍么?本着解决问题的态度,只介绍相关开发的东西,节约大家的时间。
- 本系列文章,并不能让笔者成为专业领域开发工程师,要成为专业领域资深开发工程师,需要更多的努力。
目标
学习目标如下:
- IDE初步解析
- 初步理解C与C++语言
- 运算符优先级
- 堆栈和内存分配
- 写一个在Visual Studio中的程序
材料准备
IDE准备
我们的目的是执行NDK开发,所以我们的在IDE选择的时候,原则上是是要是文本编译工具都可以。当然,易用性和开发效率也需要考虑的话,推荐使用
- VS系列(Visual Studio),推荐2013~2015。Visual Studio 2013下载地址
VS社区版本是免费的,需要下载C,lunix的插件平台支持。
NDK开发语言说明
NDK使用C和C++开发都可。
C与C++
- C面向过程,C++面向对象
- C是子集,C++是C的超集(C之上的扩展,非父类的意思)
- 大部c语言程序都可以不加修改的拿到C++下使用
- C广泛运用与底层开发
- NDK开发直接使用C++即可
C中的基本数据类型
-
char/short/int/long/float/double/bool
可以说几乎所有高级开发语言都有这些类型了,使用定义如下,与java无异
int x = 10; long l = 100L;
关于这些类型的长度占用如下:
类型 | 长度/字节 | 取值范围 | 存储方式 |
---|---|---|---|
char | 1 | -128~127 | 有符号二进制补码形式 |
[signed]char | 1 | -128~127 | |
unsigned char | 1 | 0~255 | |
short [int] | 2 | -32768~32767 | |
unsigned short [int] | 2 | 0~65535 | |
int | 4 | -2147483648~2147483647 | 定点有符号二进制补码形式 |
[signed] int | 4 | -2147483648~2147483647 | |
unsigned [int] | 4 | 0~4294967295 | |
long [int] | 4 | -2147483648~2147483647 | |
[signed] long [int] | 4 | -2147483648~2147483647 | |
unsigned long [int] | 4 | 0~4294967295 | 无符号(那就是正数啦) |
float | 4 | -3.4*10^38~3.410^38* | 浮点形式存储 |
double | 8 | -1.798*10^308~1.79810^308* | 浮点形式存储 |
long double | 8 | -1.798*10^308~1.79810^308* | |
long long | 8 |
注意,以上长度占用都是32位系统的,如果是64位,占用如下:
image上表中第一行的大写字母和数字含义如下所示: 系统遵循的标准
l表示:int类型
L表示:long类型
P表示:pointer指针类型
32表示:32位系统
64表示64位系统
如:LP64表示,在64位系统下的long类型和pointer类型长度为64位。
64位Linux 使用了 LP64 标准 ,即:long类型和pointer类型长度为64位
8个位1个字节,上图中中的长度显示的是位数。
我们大部分系统,比如win系统是lLP32,LP64规则的。
C中的String
C中,是没有“java字符串”的,在 C 语言中,字符串实际上是使用 null 字符 '\0' 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 "Hello" 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 "Hello" 的字符数多一个。
char hello[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
通常我们进行字符串定义的时候,使用如下方法:
char hello[] = "Hello";
C中有大量操作字符串的函数:
请在使用的时候引入包string.h
序号 | 函数 & 目的 |
---|---|
1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
3 | strlen(s1); 返回字符串 s1 的长度。 |
4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 |
5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
使用方式为:
#include <string.h>
/* 复制 str1 到 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\n", str3);
/* 连接 str1 和 str2 */
strcat(str1, str2);
printf("strcat( str1, str2): %s\n", str1);
/* 连接后,str1 的总长度 */
len = strlen(str1);
printf("strlen(str1) : %d\n", len);
string部分内容,摘取于菜鸟教程字符串
C中的数组
声明数组的时候,就需要声明数组大小,且必须在编译期间就定义。不能通过赋值等方式进行定义。
// 方式1
int intArray[10];
// 方式2
int intArray[] = {1,2,3,4,5}
与Java一样,必须将数组大小定义。当然,写法上与java有点不一样,这个需要注意。
至于前文提到的“且必须在编译期间就定义”,是为了避免如下写法:
int n = 5;
int array[n]; // 编译错误,必须直接定义
当然,如果使用const关键字,可以改变局面
const int n = 5;
int array[n]; // n用了const修饰,此处变异就可以通过
至于const是什么,请见下文解释。
C中的一些方法介绍
-
const
C中的常量修饰符,类似于java中的final字段。
定义为const的变量其内存值是无法被改变的。其用法如下:
const int x = 5;
由于变量的内存值无法改变,这也是可以解释为何数组长度可以用const修饰过的变量来定义了。因为如果不是const的,其值是会被任意改变的(被指针或者什么改变),导致违反了数组本身定义的原则。
-
sizeof
计算基本类型数据所占用的内存大小(注意,是类型,不是变量。。。。)。
由于每个系统遵循的制式不一样,有lLP32,LP64,LLP64以及lLP64,基本变量在这些系统上跑,占用大小多少都有一些差池,所以我们在系统中需要对其进行sizeof。
由于此方法大量运用在内存地址的动态分配上。
定义如下:
int x = sizeof(object);
使用如下
int x = 10; int y = sizeof(x); // 可以对变量进行sizeof,得出变量类型的真实占用大小 int y1 = sizeof(int); // 可以直接对类型进行sizeof
-
printf
控制台格式化打印输出,等于Java中的 String.format("%d",10)。
控制台输出只用这一个就可以了,其他的都可以忽略。由于本文是非专业文章,其他的不介绍。
学生问一个生物教授:“野外考察的时候,如果遇到不认识的植物,恰好又有学生问你,你怎么化解尴尬?”
“一般不会有,因为我走在前面,会把自己不认识的花草全部踩死。”。
格式如下:
printf("格式化字符串",输出表列)
使用如下:
printf("%d\n",1000); // 输出数字1000
关于其他数据输出格式如下:
字符 对应数据类型 含义 示例 d/i int 输出十进制有符号32bits整数,i是老式写法 printf("%i",123);
输出123o unsigned int 无符号8进制(octal)整数(不输出前缀0) printf("0%o",123);
输出0173u unsigned int 无符号10进制整数 printf("%u",123);
输出123x/X unsigned int 无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF(不输出前缀0x) printf("0x%x 0x%X",123,123);
输出0x7b 0x7Bf/lf float(double) 单精度浮点数用f,双精度浮点数用lf(printf可混用,但scanf不能混用) printf("%.9f %.9lf",0.000000123,0.000000123);
输出0.000000123 0.000000123。注意指定精度,否则printf默认精确到小数点后六位F float(double) 与f格式相同,只不过 infinity 和 nan 输出为大写形式。 例如 printf("%f %F %f %F\n",INFINITY,INFINITY,NAN,NAN);
输出结果为inf INF nan NAN
e/E float(double) 科学计数法,使用指数(Exponent)表示浮点数,此处”e”的大小写代表在输出时“e”的大小写 printf("%e %E",0.000000123,0.000000123);
输出1.230000e-07 1.230000E-07g float(double) 根据数值的长度,选择以最短的方式输出,%f或%e printf("%g %g",0.000000123,0.123);
输出1.23e-07 0.123G float(double) 根据数值的长度,选择以最短的方式输出,%f或%E printf("%G %G",0.000000123,0.123);
输出1.23E-07 0.123c char 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 printf("%c\n",64)
输出As char* 字符串。输出字符串中的字符直至字符串中的空字符(字符串以空字符’\0‘结尾) printf("%s","测试test");
输出:测试testS wchar_t* 宽字符串。输出字符串中的字符直至字符串中的空字符(宽字符串以两个空字符’\0‘结尾) setlocale(LC_ALL,"zh_CN.UTF-8");
wchar_t wtest[]=L"测试Test";
printf("%S\n",wtest);
输出:测试testp void* 以16进制形式输出指针 printf("%010p","lvlv");
输出:0x004007e6n int* 什么也不输出。%n对应的参数是一个指向signed int的指针,在此之前输出的字符数将存储到指针所指的位置 int num=0;
printf("lvlv%n",&num);
printf("num:%d",num);
输出:lvlvnum:4% 字符% 输出字符‘%’(百分号)本身 printf("%%");
输出:%m 无 打印errno值对应的出错内容 printf("%m\n");
a/A float(double) 十六进制p计数法输出浮点数,a为小写,A为大写 printf("%a %A",15.15,15.15);
输出:0x1.e4ccccccccccdp+3 0X1.E4CCCCCCCCCCDP+3 -
include
include方法,可以理解为java中的import方法。
include方法写在文件头的,有两种格式
#include <stdio.h> // 系统指定路径,通常c中的预制方法,导入头文件等啥的用这个 #include "a.h" // 用户自定义相对路径,通常自己写的文件就这么玩儿
一个是尖括号,一个是冒号。注意看注释区分区别
-
system("pause")
system()就是调用(DOS)系统命令(和shell命令)。
pause代表暂停一下。这里介绍这个的原因,时候我们在写一些例子的时候,需要暂停一下系统执行,才能看到执行结果。达到演示的效果。
system("pause"),让操作系统来暂停该程序进程的执行,同时程序运行到此语句处时,会在程序的窗口上显示“Press any key to continue . . .” 也就是 “按任意键继续...”,即等待用户按下任意一个按键使该程序从暂停状态返回到执行状态继续从刚才暂停的地方开始执行。
在使用system(“pause”)的时候,需要include包stdlib.h。
#include <stdlib.h> system("pause");
谈谈一些概念:堆栈与内存分配
-
堆栈与内存分配
-
什么是“堆”,什么是“栈”,以java角度来谈
我要偏个题,要谈下“静态”。
静态存储区:
- 内存在程序编译的时候就分配好的区域,程序在整个运行中都存在。主要存放:静态,全局的static数据和常量。(static 为java中“静态”标志)
栈:
- 一种先进后出的结构
- 栈使用的是CPU一级缓存
- "栈"的运算速度非常快,应为它是放在处理器中的,由于在CPU里面也导致它容量有限
- 在执行函数(方法)时,函数的一些局部变量的存储都是放在栈上的,函数结束的时候,这些存储单元会被清除掉。
- 内存分配策略是:连续内存分配(如在类中定义的arrayList,先分配个255,不够再连着分配一个255*2 的连续空间)。
- java中,方法变量是放在栈中的
堆:
-
碎片式内存分配,不连续:
-
不够用临时申请
-
也叫动态内存分配(又例如早方法中定义ArrayList,先分配255,后续不够,在在其他非连续的地方分配个内存),有时候可以用malloc 或者 new 来申请分配一个内存。C/C++中需要手工释放,java中是GC自动释放。
-
虽然java不用管内存回收,这是方便,但在一个侧面看来,其内存管理可以说是不受控的!
-
堆是存放在二级缓存中
-
java中,类属性变量是在放在堆中的
——java,乃至android开发的内存管理就主要讲“堆”的处理。
-
-
“堆”与“栈”的区别
-
连续内存与非连续内存
- “堆”是不连续的内存区,空间相对比较灵活。
- “栈”是连续的内存区,大小是操作系统控制的。
- “堆”因为内存不是连续的(与“栈”区别),导致“堆”是有碎片化的,碎片化的管理(读取等)就是效率相对栈低的一个原因。
- "栈",是先进后出,切内存的分配是连续的,完全不会产生碎片。
-
缓存级别
- 栈使用的是一级缓存
- 堆是存放在二级缓存中
-
管理级别
- “栈”是操作系统管理
- “堆”大多是可以开放给程序管理的,也就是程序员可以通过代码进行主动管理
-
-
一些实践
- class中的成员变量、对象,是放在“堆”中的。
- class 中的 函数方法中的变量、对象,则就是放在“栈”中的。
- 对象引用是放在“堆”中的。不管这个引用在class中,还是在函数中,都是!!!
-
C中的内存分配
C跟java不一样,java的内存管理是自动的,C不是。
这里的内存管理,主要讲的就是堆内存管理。
栈内存很小,C里面可能就几M,如果分配一个40M大小的数组,用栈就爆缸了。所以需要在堆中分配。也引出我们以下几个方法:
以下方法需求引入stdlib.h声明,一般IDE(VasulStudio)默认在新建工程的时候都会导入
-
malloc与memset
malloc:动态申请堆内存空间
memset:初始化动态申请的内存空间,防止malloc申请的内存地址原本是有值得。
// 当数据无法确定 或者 比较庞大 需要使用动态内存申请 在堆中 // 这里的di1前的*号写法,是代表的“指针”,这里死记硬背即可。这里用了一个int指针指向了这个1M大小的int类型空间。 int *di1 = (int*)malloc(1 * 1024 * 1024); // 动态申请内存应该紧跟 memset 初始化内存 // 将di1指向的内存地址初始化(设置为0),且初始化的大小为1*1024*1024 memset(di1, NULL, 1 * 1024 * 1024);
-
calloc
malloc+memset方法一起做了。
// 申请40个字节的内存(10*4=40,sizeof(int)是4),并将内存初始化为 null int *di2 = (int*)calloc(10, sizeof(int));
-
relloc
动态扩容堆内存空间。注意,只能扩大,不能缩小!!!
// 对malloc申请的内存进行大小的调整 realloc(di1, 20 * sizeof(int)); // 将di1地址,调整到20*4=80个字节堆内存
-
alloc
栈内存分配,一般不用。我这里就不介绍了。
int *p = (int *)alloca(sizeof(int) * 10); // 由于是“栈内存”,所以根本不用考虑释放的事情
-
free以及制空
动态分配的内存回收掉。
//标准写法为: int *di1 = (int*)malloc(1*1024*1024); if (di1) { // 判断 free(di1); // 释放 di1 = NULL; // 制空:正常写NULL,但是C中,0也是NULL,写0也可。建议写NULL }
请注意,释放是三个个动作,一个判断,一个free,一个制空。
-
-
控制流程
-
for
-
if
-
switch
-
while
-
break
-
continue
-
goto
这玩意不太好玩
除了goto,其他都跟java一样。不多讲了。
谈谈一/二/三目运算符
如下表所示:
image函数的调用与“声明”
C++程序调用函数,有点像脚本语言编程,如果方法在函数调用后定义,会编译报错,所以这里有一个概念叫做“声明”,也就是 .h文件,他能解决这个问题。
-
初识 .h 头文件
-
什么是 .h 文件?什么又是 .cpp文件呢?
.cpp文件:源文件,代码逻辑的具体实现是在这个文件中实现的。
.h文件:就是一个“声明”,里面有各种变量定义,以及方法定义,关于方法定义可以放在子类自行实现,也可以在.h文件中自行实现,
两者之间的关系:.cpp文件include .h文件,使用/实现.h文件中的“声明定义”,规范代码,标准。
这很像java中的interface接口定义。
我们在使用一些方法变量的时候,比如使用malloc,或是system("pause")的时候,需要include <XXXX.h>文件,“规范”就是这个道理。
-
-
声明的两种方式
-
方式1:类中先“声明”这个方法:也就是在使用前声明这个方法
int function(); void main(){ function(); } int function(){ // 方法实现 }
-
方式2:写一个 .h的声明头文件,再include到cpp文件中(推荐此方法)
请见include章节描述
-
用VS写一个程式
讲了那么多,还是要玩一下的,这里就写一个指针程序吧,为下一篇文章做铺垫
- 写一个程序
- 多少用上点我们之前讲的内容
- 指针
新建程序
文件 --> 新建 -->项目
image选择Visual C++,选中Win32控制台应用程序。
编写代码与执行程序
include <stdio.h>
include <tchar.h>
include <stdlib.h>
int _tmain(int argc, _TCHAR* argv[])
{
// 变量,指针
int x = 10;
int *p = &x;
// N目运算符
printf("%p \n",p); // 打印十六进制内存地址
int y = *p++;
printf("%d \n",y);
y = (*p)++;
printf("%d \n",y);
if (!y || y < 0 ){
printf("不靠谱啊兄弟,y为%u \n",y);
}
// 命令框暂停
system("pause");
p = 0;
return 0;
}
程序写完,选好调试模式(release还是debug),选择“本地windows调试器”
程序返回结果为
0045FB74
10
-858993460 // 走到一个不知道什么值的内存上了
不靠谱啊兄弟,y为-858993460
对于里面的什么 *p啥的,目前看看就好,下一篇博文会讲解。
断点
与Eclipse,AndroidStudio等IDE一样,对应行数左边的列上点击即可。程序运行到了过后,是自动暂停的。
image内存查找
在调试模式下,在断点生效的情况下,可以打开“内存”窗口。
断点的时候,可以在“调试”--“窗口”--“内存”--选择内存1/2/3/4进行查看。数据查看需求调整到对应字节,并带符号显示
根据我们打印的内存值,可以在这个窗口中找到对应的值
image如上图,根据数据类型,右键选择一些配置参数,可以看到对应的值。
学习目标小结
学习目标如下:
- [x] IDE初步解析
- [x] 初步理解C与C++语言
- [x] 运算符优先级
- [x] 堆栈和内存分配
- [x] 写一个在Visual Studio中的程序