第一章 词法陷阱

笔记本:《C陷阱与缺陷》

创建时间:2018/4/23 22:06:21                                                               更新时间:2018/4/24 08:36:21

作者:刘依阳


导读

有的人说,学习一门语言,就要知道他的底层原理,动手能力不着急,有的人说,学习一门语言最重要的是要会动手,要有项目实战经验,要知道一些API和框架,要我说,这二者缺一不可。

有的人大一C语言C++学得特别好,善于从上帝视角看待C++程序脚本,沉迷于命令行中字符串的输入输出,编码能力得到了很大的提升。但人的精力和耐力是有限的,没有需求支撑、没有项目支撑,迟早有一天我们会因为学习压力、生活压力、工作压力而被迫抛弃我们最爱的底层原理和OnlineJudge,在这之后,之前学得再扎实的语言语法也会渐渐被我们忘掉大半…

还有的人,沉迷于做项目,项目很大很复杂,能做出来确实很了不起,但其实这之中有大量的重复编码,我们自以为学了很多,其实也只是做了一个苦力活。如果我们只是会做项目,只是知道怎么写,而不知道为什么这么写,不知道项目里每一个类底层都干了些啥,编译器和运行环境为我们做了些啥,那我们写出来的只会是死代码,遇到一个新的问题,自己仍然不会用底层知识去理解去创造自己解决方案,这样的码农迟早要被飞速进步的互联网行业所淘汰,与培训机构出来的 “两年经验” 无异…

因为自己以前走过弯路,所以开始学习之前,还是要拿这段话来提醒一下自己…


进入正题:

有的程序虽然简单,但如果我们不注意,就很容易发生不可预料的错误,即使一些很有经验的程序员,也常常容易忽视这些问题。例如下面这段代码:

#include<iostream>
#define N 5
using namespace std;
main(){int i;int a[N];for(int i=0; i<=N; i++)cout<<(a[i]=0)<<endl;
}

乍一看只是FOR循环中的 “判定表达式” 多了一个等号,在Java中这将产生数组越界异常 “java.lang.ArrayIndexOutOfBoundsException” ,而在C语言中则没有这类运行时异常,程序照常执行,但这里发生了死循环。

其实这正是C语言指针的一个弊端,指针作为C语言中强大的工具,我们能通过它直接对某一内存地址中的值进行读写,在这里,变量 i 的内存地址恰好分配到了 a[9] 的下一个内存地址 a[10] ,每一次 i=10 时进入循环后,将执行 i=a[10]=0i 将被再次置为0,这就造成了我们看到的死循环…

1 词法分析中的“贪心法”

  • 相同的字符在不同的上下文中含义是不一样的,我们都学过编译原理,词法分析自然也不会陌生,术语 “符号” (token) 指的是程序的一个基本组成单元,其作用相当于一个单词,在C语言中,同一个单词 (token) 通常是无二义性的。
  • 每一个符号应该包含尽可能多的字符,也就是说,编译器将程序分解成符号的 方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符。需要注意的是,除了字符串与字符常量,符号的中间不能嵌有空白(空白符、制表符和换行符)。例如,==是单个符号,而= =则是两个符号(注意其中的空格符)。

例如:
            

运行结果如下:
            

2 =不同于==

  • C语言中 “=” 是赋值运算符 “==” 是关系运算符,用于两个数进行比较…,例:
#include<iostream>
using namespace std;
int main(){int x,y=5;cout<<(x=y)<<(x==y)<<x<<y<<endl;//尽量多用括号、空格、转义符等明确语义cout<<(x==y)<<endl;return 0;
}

程序的输出你想不到:

3 &和|不同于&&和||

&:按位与,优先级高于&&,也可作为逻辑与,作为逻辑与时没有短路机制...
|:按位或,优先级高于||,也可作为逻辑或,作为逻辑或时没有短路机制...
&&:逻辑与,短路机制...,返回布尔值(0或1)
||:逻辑或,短路机制...,返回布尔值(0或1)

示例:

#include<iostream>
using namespace std;
int main(){int x,y=5;//&:按位与,优先级高于&&//|:按位或,优先级高于||cout<<(x&y)<<" "<<(x|y)<<" "<<(!x)<<" "<<(x^y)<<" "<<(x>y)<<" "<<(x>>y)<<endl;//C语言没有>>>//&&:逻辑与//||:逻辑或x=y=5;cout<<(x==5&&y!=5)<<(x==5||y!=5)<<endl;//&:也可作为逻辑与,作为逻辑与时没有短路机制...//&&:逻辑与,短路机制...,返回布尔值(0或1)//注意以下两种的区别x=y=5;cout<<(x!=5&x++==5)<<x;cout<<(x!=6&&x++==6)<<x<<endl;x=y=5;cout<<(x!=5&x++==5)<<x<<(x!=6&&x++==6)<<x<<endl;//|:也可作为逻辑或,作为逻辑或时没有短路机制...//||:逻辑或,短路机制...,返回布尔值(0或1)//注意以下两种的区别x=y=5;cout<<(x==5|x++==5)<<x;cout<<(x==6||x++==6)<<x<<endl;x=y=5;cout<<x<<y<<endl;cout<<(x==5|x++==5)<<x<<(x==6||x++==6)<<x<<endl;return 0;
}

运行结果如下:

4 整形常量的进制

在C/C++中,表示8进制整数需要在最前面加0,如0122,在表示十进制的地方,一定不要用0进行文本格式的对齐

5 字符与字符串

示例:

#include<iostream>
using namespace std;
int main(){unsigned int value1 = 'tag1';unsigned int value2 = 'cd';char value3 = 'abcd';cout<<value1<<" "<<value2<<" "<<value3<<endl;printf("\n\n"+'\n');//0printf("\n\n\n"+'\n');//1printf("\n\n\n\n"+'\n');//2//printf('\n');这一句编译报错return 0;
}输出:
1952540465 25444 d`@Mingw runtime failure:Process returned 0 (0x0)   execution time : 0.307 s
Press any key to continue.注释掉//2处输出:
1952540465 25444 d
@Mingw runtime failure:Process returned 0 (0x0)   execution time : 0.254 s
Press any key to continue.注释掉//2和//1处输出:
1952540465 25444 d
ingw runtime failure:Process returned 0 (0x0)   execution time : 0.321 s
Press any key to continue.可以看见每一种输出都不一样...,很诡异吧?

6 小结

括号和空格以及转义符很重要,我们平时应多用,不要在cout<<输出语句中作表达式运算,容易出错,main函数记得要写返回值,牢记词法分析的贪心法…

———

第二章 语法陷阱

笔记本:《C陷阱与缺陷》

创建时间:2018/4/29 12:31:46                                                                                                                                   更新时间:2018/4/29 12:31:58

作者:刘依阳


想要知道语法陷阱,必须要先知道语法的规则

一、第一个例子:

(*(void(*)())0)();

乍一看可能有点难于理解,但只要我们仔细去分析,表达式结构也并不复杂

根据括号必须成对,以及贪心法的匹配规则可以得到:

( * ( void ( * ) ( ) ) 0 ) ( );

( * ( void ( * ) ( ) ) 0 ) ( );

( * ( void ( * ) ( ) ) 0 ) ( );

相信童鞋们很快理解了,其中:

void ( * ) ( )

指的是函数指针的类型声明,这里声明了一个指向返回值类型为void的函数的函数指针,函数指针指向的函数的地址为0地址,最后:

( * 0 ) ( ) ;

调用了函数指针所指向的那个函数,也就是内存中首地址为0处所存储的那个函数体


这里要说一下函数指针指针类型返回值,借用谭文波同学的例子:

float *g(),(*h)();

因为 ( ) 结合的优先级高于 * ,第一个声明float *g()也就是一个返回值类型为指向float值的指针的函数 ,而第二个则是声明一个函数指针h,这个函数指针h所指向的函数的返回值类型为float


还有指针数组指向数组的指针

指针数组:int * A[3]; —— 声明了一个大小为3的数组A,数组中的元素都是 int 类型的指针

指向数组的指针:int (*A)[3]; —— A是一个指针,指向一个长度为3、元素类型为 int 的指针


取值:* 和取址:&


PS:关于起别名typedef

typedef int Size;

Size定义为int的别名,Sizeint具有完全相同的含义,Size可以用于类型声明,类型转换等,它和int完全相同

typedef int * Type;
Type A;

上面的例子则是声明了 Type(int *) 的别名,而A是一个指向 int 值的指针

使用 typedef 的目的 or 好处:

  1. 为了使表达式更简洁,例如最开始的例子:(*(void(*)())0)();假如程序里很多地方要用到类型: void(*)() ,我们可以为这种类型起一个别名:typedef void (*A)();,当我们再要定义返回值类型为void类型的函数指针时只用写:A a;,那么最开始的例子就可以写作:(*(A)0)();
  2. 为了隐藏特定类型的实现,强调类型的使用目的
  3. 允许一种类型用于多个目的,同时使得每次使用给类型的目的明确

如:

 typedef int (*Function)(const char *, const char *);

该定义表示 Function 是一种指向函数的指针的类型的别名,要使用这种指针类型时只需直接使用 Function即可,不必每次把整个声明都写出来

void (*Signal(int,void(*)(int)))(int);typedef void (*HANDLER)(int);
HANDLER Signal(int,HANDLER);

上下两种声明意义是一致的


二、运算符的优先级

结合性和优先级

比如 a = b = 3; 这个表达式,就应该是 a = ( b = 3 ); 而不是 ( a = b ) = 3;,优先级就更好理解了

结合性的记忆方法:

1、单目运算符中除了自加和自减这两种依赖于顺序的运算符外,其他的全部都是自右向左结合的

2、双目运算符都是自左向右结合的

3、三目运算符是自右向左结合的

4、赋值运算符是自右向左结合的

三、要注意的的小坑

典型错误:

if(a[i] > max);max = x[i];

典型错误:

if(x[0] < 0)return
logrec.date=x[0];

典型错误:

switch(Expression){case value1:process1;case value2:process2;case value3:process3;
}

典型错误:

struct logrec{int date;int time;int code;
)
main(){//...do something
}

典型错误:

main(){getch;
}

练习 2 - 1

C语言允许初始化列表中出现多余的逗号,例如:

int days[] = { 31, 28 ,31 , 31,};

为什么这种特性是有用的?

答:保证每一行、每一个数组元素在语法上的相似性,自动化的程序设计工具才能够更方便地处理很大的初始化列表


第三章 语义陷阱

笔记本:《C陷阱与缺陷》

创建时间:2018/4/29 15:28:52                                                                                                                                   更新时间:2018/4/29 15:28:57

作者:刘依阳


一、指针与数组

四句话:

  1. 数组就是由指针实现的,下标运算就是指针的加运算,二者完全等价使用
  2. 二维数组就是数组元素为数组 ( 指向数组的指针 ) 的数组
  3. 指针加一通常并不是指向下一个内存地址,而是指向原地址所存储数据元结束地址的下一个地址
  4. 一定要记得初始化指针,不要让你的指针成为野指针

二、非数组的指针

两个程序:

#include<stdio.h>
#include<string.h>
#include <cstdlib>
int main(){char *s="abcd",*t="efgh",*r;r= (char *)malloc((strlen(s)+strlen(t)+1));if(!r){printf("%s\n","Fuck ! ,How did you go wrong ?");exit(1);}strcpy(r,s);strcat(r,t);printf("%s\n",r);/* 一段时间之后记得释放内存 */free(r);return 0;
}
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
int main(){char *p="KING-ERIC",*q;char a[]="KING-ERIC",*b,c[10];string x="KING-ERIC",y;q=p;b=a;//c=a;数组不可直接赋值给数组y=x;//q[4]=' ';//存放在常量区,不可修改b[4]=' ';y[4]=' ';printf("%c\t%s\t%d\t%d\t%d\t%d\n",p[4],q,sizeof(p),sizeof(q),strlen(p),strlen(q));printf("%c\t%s\t%d\t%d\t%d\t%d\n",*(a+4),b,sizeof(a),sizeof(b),strlen(a),strlen(b));printf("%c\t%c\t\t%d\t%d\t%d\t%d\n",x[4],y[4],sizeof(x),sizeof(y),x.size(),y.size());//string不是C语言内置数据,不能用printf输出printf("%c\t%s\t\t%d\t%d\t%d\t%d\n",x[4],x,sizeof(x),sizeof(y),x.length(),y.length());cout<<x<<endl<<y<<endl;return 0;
}

注意:char *a="KING-ERIC" 与 char a[]="KING-ERIC"的区别:

1、字符串存放的内存区域不同:前者存放在常量区,不可修改,后则存放在栈中,可以修改

2、变量 a 存放的内容不同:前者存放的是一个地址,而后者存放的是字符串 "abcdef" ,因此使用 sizeof 它们的结果是不同的,分别是 4 和 10

3、此外,关于new分配的对象数组的情形:因为是内存区中的修改,所以也是可以实现修改字符串的

三、作为参数的数组声明

将数组作为参数传到函数里,C语言会自动的将作为参数的数组声明转换成相应的指针声明,所以在传数组的时候只需要写数组名,不需要写大小

int strlen(char s[]) 等同于 int strlen(char *s) //都是将首地址传了进去

要注意的地方:

一、 空指针并非空字符串:NULL( 同nullptr )是个好东西,给一出生的指针一个安分的家~~~,它仅仅代表空值,也就是指向一个不被使用的地址(访问时输出 (null) 或没有输出)

在C语言中,NULL和0是完全相同的,但是为了明确语义,NULL用于指针和对象,0用于表示数值,在不同的语言中,NULL并非总是和0等同

二、 sizeof() 运算符下数组指针的特性

三、

不要写出这样的代码:

i=0;
while(i<n){x[i]=y[i++];
}

四、 防溢出

五、 main函数要有返回值


第四章 连接

笔记本:《C陷阱与缺陷》

创建时间:2018/4/29 20:47:14                                                                                                                                   更新时间:2018/4/29 20:47:06

作者:刘依阳


一、连接器

连接器的一个作用是处理命名冲突:这里先要明确一个概念—外部对象,外部对象就是指定义在所有函数体之外的对象 ( 和Java的类变量有点像 ) ,例如:

这里又会引出一个 extren 关键字,先来看一下它的用法:

假如一个程序中包含了语句: extern int a; 那么这个程序就必须在别的地方包括语句: int a; 这两个语句既可以在同一个源文件中,也可以位于不同的源文件中,当位于不同的源文件中时,容易发生同名现象,那么就难以处理了,我们应当避免外部变量同名重复定义

二、static修饰符

static 修饰符修饰的外部变量、函数,其作用域都仅限于当前源文件内部,如果一个变量或函数仅供同一个源文件中的其他函数调用,我们就应该将其声明为 static ,避免同名冲突

三、参数类型

四、头文件


第五章 库函数

笔记本:《C陷阱与缺陷》

创建时间:2018/4/29 22:07:27                                                                                                                                   更新时间:2018/4/29 22:07:37

作者:刘依阳


一、返回值类型为整型的 gerchar( )函数

二、读写操作

多数情况下,磁盘文件操作对流的读操作发生在流的开头,写操作发生在流的末尾,写后的流一般不可读 ( 末尾 ) ,而读后的流不适宜写 ( 会覆盖后面内容 ) ,所以读写交替时要用 fseek() 等重新定位到一个可读或可写的位置,或调用 fflush() ,这是指导性原则,本质上只能算是半双工,最好的办法就是简单化:读写打开不同的文件句柄,相关函数都会加锁,不要同时读写

FILE * fp;
struct record rec;
//... ...
//从fp读结构体rec,每次读一个
while (fread((char *)&rec, sizeof(rec), 1, fp) == 1)
{  /* 对rec执行某些操作 */  if (/* rec 必须被重新写入 */) {  fseek(fp, -(long)sizeof(rec), 1); /*因为要重新将rec写入到fp,要对文件指针向前回溯,所以是fseek的第二项是负数,后面的1是文件指针当前位置,表明回溯起点是文件中rec的尾部*/fwrite((char *)&rec, sizeof(rec), 1, fp);  /*rec写入缓冲区,等待写入。*/fseek(fp,0L,1);/*之所以要调用fseek,因为fwrite的数据只是写入到了缓冲区,而fseek函数中调用了fflush(因版本而异),这样才将缓冲区的内容输入写进fp。*//*其实这个指令看似什么也做,但是其使得磁盘文件中的数据改变了,并且使文件可以正常读取了*/}
}

三、缓冲区与内存分配

有一种技术叫做Copy On Write

Copy on write (COW) is an optimization strategy that avoids copying large sized objects.

In a lot of real world programs, a value is copied to another variable and often is never written to. In most languages other than C++, all large sized objects are actually references. When you copy an object, all you copy is a pointer (shallow copy semantics). In such languages, COW is implemented at the language/runtime level, and not in the standard library.

In C++, copies are deep copies by default (value semantics), thus assigning large structures and strings are expensive, because the entire data is duplicated.

To avoid this, one can make a system where a copy is always shallow, but when you modify a copied object, the underlying object is duplicated, and then the changes are applied to the new copy.

因此 C 语言实现通常都允许程序员进行实际的写操作之前缓存输出数据,这种控制能力一般是通过库函数 setbuf 实现的,如果 buf 是一个大小适当的字符数组,那么 setbuf (stdout, buf) ; 语句将通知输入/输出库,所有写入到 stdout 的输出都应该使用 buf 作为输出缓冲区,直到 buf 缓冲区被填满或者程序员主动调用 fflush ( 对于由写操作打开的文件,调用 flush 将导致输出缓冲区的内容被实际地写入该文件 ) 时 buf 缓冲区中的内容才实际写入到 stdout 中。缓冲区的大小由系统头文件 <stdio.h> 中的 BUFSIZ 定义

四、使用errno检测错误

很多库函数,特别是那些与操作系统有关的,当执行失败时会通过一个名称为 errno 的外部变量,通知程序该函数调用失败, errno 是记录系统的最后一次错误代码,它是一个定义在 errno.h 中的 int

查看错误代码 errno 是调试程序的一个重要方法,不同的值表示不同的含义,可以通过查看该值推测出错的原因,就像 Windows 里的错误代码一样

在调用库函数时,我们应该首先检测作为错误提示的返回值,确定程序执行已经失败,然后再检查 errno ,来搞清楚出错原因:

//调用库函数
if(返回的错误值)检查errno

五、库函数signal

所有 C 语言实现中都包括有 signal 库函数,作为捕获异常事件的一种方式:

#include<signal.h>
#include<stdio.h>
void sighandler(int);
int main()
{signal(SIGINT, sighandler);while(1){printf("开始休眠一秒钟...\n");sleep(1);}return(0);
}void sighandler(int signum)
{printf("捕获信号 %d,跳出...n", signum);exit(1);
}

按 CTRL + C 键跳出程序可以看到捕获的信号


第六章 预处理器

笔记本:《C陷阱与缺陷》

创建时间:2018/4/29 23:45:31                                                                                                                                   更新时间:2018/4/29 23:45:41

作者:刘依阳


一、宏定义中的空格

#include<stdio.h>
//#define f (x) ((x)-1)不调用的时候没事,调用的话编译不通过
#define f(x) ((x)-1)
int main(){printf("%d\t%d\n",f(5),f (5));//输出4        4return 0;
}

可以看到,程序中宏调用中的空格无所谓,但是放到宏定义中就有问题了

二、宏不是函数

#include<stdio.h>
#define abs(x) x>=0?x:-x
//#define abs(x) (((x)>=0)>(x):-(x))//宏定义应当严格书写括号
int main(){int a=5,b=10;printf("%d\t%d\t%d\n",abs(5),abs(5)+2,abs(a-b));//输出5       5       -15return 0;
}

三、宏不是语句

以断言 ( 调试程序时常用 ) 为例:

#define assert(e) if(!e) assert_error(_FILE,_LINE_)if(x > 0 && y > 0)assert(x > y);
elseassert(y > x);展开后变为:
if( x > 0 && y > 0)if(!(x > y)) assert_error("foo.c",37);elseif(!(y > x)) assert_error("foo.c",39);

断言assert宏正确的写法是:

#define assert(e) ((void)((e))||_assert_error(__FILE__,__LINE__)))y=distance(p,q);
assert(y>0)
x=sqrt(y);

四、宏不是类型定义

#define T1 struct foo *
typedef struct foo *T2;
T1 a, b;//声明1
T2 a, b;//声明2

分析可得到:

声明1等价于:struct foo *a, b; 一个是指向结构体foo类型的指针变量a,一个是结构体foo类型的变量b,显然与我们的预期不符
声明2等价于:struct foo *a; 和 struct foo *b;

五、宏的原理就是直接替换

在编译阶段之前,程序中的宏调用便会全部替换为宏定义


第七章 可移植性缺陷

笔记本:《C陷阱与缺陷》

创建时间:2018/4/30 0:30:56                                                                                                                                   更新时间:2018/4/30 0:31:01

作者:刘依阳


一、应对C语言标准变更

C语言在众多的硬件以及系统平台上都有相应的实现,这使得C程序可以方便的在不同的平台之间移植,但C语言的每个版本之间还是存在一些细微差别,为了解决这一问题,ANSI 国际标准化组织制订了 ANSI C 标准

二、标识符名称的限制

目前的C语言的标识符是对大小写敏感的,但是在以前的版本中ANSI C标准是保证必须能够通过前6个字符区分不同的外部名称,而且无视大小写,所以编译器禁止使用与库函数同名的标志,即使大小写不同也不行!

三、整数的大小

//字符长度受硬件特性影响
short
int
long int//历史遗留问题
long

四、有符号整数和无符号整数

整数的有无符号对程序的运行至关重要,它决定着一个八位字符的取值范围是从-128到127还是从0到255,而这一点又反过来影响到程序员对哈希表或转换表等的设计方式

五、移位运算符

两个问题:

在向右移位时,空出的位是由0填充,还是由符号位的副本填充? 答:符号位

移位操作的位数的取值范围是什么? 答:大于或等于0,小于被移位对象的位数

六、内存位置零

NULL指针并不指向任何对象,因此,除非是用于赋值或比较运算,出于其他任何目的使用NULL指针都是非法的

七、除法运算截断

八、取随机数

rand()%100;//取0到100之间的随机数,不同的版本也不一致

九、首先释放、然后重新分配

《C陷阱与缺陷》学习笔记相关推荐

  1. 第二行代码学习笔记——第六章:数据储存全方案——详解持久化技术

    本章要点 任何一个应用程序,总是不停的和数据打交道. 瞬时数据:指储存在内存当中,有可能因为程序关闭或其他原因导致内存被回收而丢失的数据. 数据持久化技术,为了解决关键性数据的丢失. 6.1 持久化技 ...

  2. 第一行代码学习笔记第二章——探究活动

    知识点目录 2.1 活动是什么 2.2 活动的基本用法 2.2.1 手动创建活动 2.2.2 创建和加载布局 2.2.3 在AndroidManifest文件中注册 2.2.4 在活动中使用Toast ...

  3. 第一行代码学习笔记第八章——运用手机多媒体

    知识点目录 8.1 将程序运行到手机上 8.2 使用通知 * 8.2.1 通知的基本使用 * 8.2.2 通知的进阶技巧 * 8.2.3 通知的高级功能 8.3 调用摄像头和相册 * 8.3.1 调用 ...

  4. 第一行代码学习笔记第六章——详解持久化技术

    知识点目录 6.1 持久化技术简介 6.2 文件存储 * 6.2.1 将数据存储到文件中 * 6.2.2 从文件中读取数据 6.3 SharedPreferences存储 * 6.3.1 将数据存储到 ...

  5. 第一行代码学习笔记第三章——UI开发的点点滴滴

    知识点目录 3.1 如何编写程序界面 3.2 常用控件的使用方法 * 3.2.1 TextView * 3.2.2 Button * 3.2.3 EditText * 3.2.4 ImageView ...

  6. 第一行代码学习笔记第十章——探究服务

    知识点目录 10.1 服务是什么 10.2 Android多线程编程 * 10.2.1 线程的基本用法 * 10.2.2 在子线程中更新UI * 10.2.3 解析异步消息处理机制 * 10.2.4 ...

  7. 第一行代码学习笔记第七章——探究内容提供器

    知识点目录 7.1 内容提供器简介 7.2 运行权限 * 7.2.1 Android权限机制详解 * 7.2.2 在程序运行时申请权限 7.3 访问其他程序中的数据 * 7.3.1 ContentRe ...

  8. 第一行代码学习笔记第五章——详解广播机制

    知识点目录 5.1 广播机制 5.2 接收系统广播 * 5.2.1 动态注册监听网络变化 * 5.2.2 静态注册实现开机广播 5.3 发送自定义广播 * 5.3.1 发送标准广播 * 5.3.2 发 ...

  9. 第一行代码学习笔记第九章——使用网络技术

    知识点目录 9.1 WebView的用法 9.2 使用HTTP协议访问网络 * 9.2.1 使用HttpURLConnection * 9.2.2 使用OkHttp 9.3 解析XML格式数据 * 9 ...

  10. 安卓教程----第一行代码学习笔记

    安卓概述 系统架构 Linux内核层,还包括各种底层驱动,如相机驱动.电源驱动等 系统运行库层,包含一些c/c++的库,如浏览器内核webkit.SQLlite.3D绘图openGL.用于java运行 ...

最新文章

  1. word自动消除html标签,清理Word生成HTML的冗余;清理与清除HTML标签
  2. 图片分类赛官方baseline解读!
  3. iTunes 降级安装 12.6
  4. 超好用的C#控制台应用模板
  5. oracle之单行函数之分组函数
  6. php数组有没有类似next方法,PHP 数组current跟next用法
  7. 4K屏幕+5500万像素摄像头,以成未来手机的一大趋势
  8. 交通运输部:预计五一假期全国客运量2.65亿人次
  9. 【报告分享】2020年618直播带货数据报告.pdf(附下载链接)
  10. python运维方法_Python运维开发基础09-函数基础【转】
  11. 计算机考试操作步骤,计算机考试操作步骤(精).doc
  12. 你是一名技术管理者还是项目管理者?
  13. SpringCloud之Zuul微服务网关 什么是Zuul微服务网关?
  14. PSCAD建立高压直流输电线路雷击模型--相关建模问题
  15. 【Decouple】《Improving Semantic Segmentation via Decoupled Body and Edge Supervision》
  16. NetBeans IDE12.3无法卸载
  17. 菜鸟笔记--函数基础
  18. Linux iptables 防火墙 添加删除 端口
  19. 【FacebookSDK学习笔记】登录Facebook
  20. 多媒体计算机技术在教学中的应用,多媒体计算机技术在教学中的应用分析

热门文章

  1. Linux服务器基本安全防范
  2. ESP32学习笔记(49)——RFID RC522使用
  3. 社交电商成为下一个风口,社交电商怎么做?
  4. java用calendr做个日历,生活日历NABCD需求分析
  5. 2020.4.23工作记录————Regsvr32加载控件失败:“请确保该二进制存储在指定路径中。。。”
  6. ISTQB- TA大纲
  7. 最新最全微光图像增强论文合集
  8. 群创15.6寸工业屏G156HCE-E01-15.6寸EDP接口
  9. uBuntu20.04安装Qt5.15.2出现qt.qpa.plugin: Could not load the Qt platform plugin “xcb“的问题解决
  10. 中文编程软件 习佳佳 1.82 版 介绍