首页 文章 精选 留言 我的

精选列表

搜索[基础搭建],共10000篇文章
优秀的个人博客,低调大师

分布式基础,通俗易懂CAP?

分布式系统非常关注三个指标: ● 数据一致性 ● 系统可用性 ● 节点连通性与扩展性 关于一致性 数据“强一致性”,是希望系统只读到最新写入的数据,例如:通过单点串行化的方式,就能够达到这个效果。 关于session一致性,DB主从一致性,DB双主一致性,DB与Cache一致性,数据冗余一致性,消息时序一致性,分布式事务一致性,库存扣减一致性,详见文章《究竟啥才是互联网架构“一致性”》。 关于可用性 如果系统每运行100个时间单位,会有1个时间单位无法提供服务,则说系统的可用性是99%。 可用性和可靠性是比较容易搞混的两个指标,以一台取款机为例: ● 正确的输入,能够取到正确的钱,表示系统可靠 ● 取款机7*24小时提供服务,表示系统可用 保证系统高可用的方法是: ● 冗余 ● 故障自动转移 反向代理层,站点层,服务层,缓存层,数据库层各层保证系统高可用的方法,详见文章《究竟啥才是互联网架构“高可用”》。 关于连通性与扩展性 分布式系统,往往有多个节点,每个节点之间,都不是完全独立的,需要相互通信,当发生节点无法联通时,数据是否还能保持一致,系统要如何进行容错处理,是需要考虑的。 同时,连通性和扩展性紧密相关,想要加机器扩展性能,必须有良好的连通性。当一个节点脱离系统,系统就出现问题,往往意味着系统是无法扩展的。 反向代理层,站点层,服务层,缓存层,数据库层各层保证系统扩展性的方法,详见文章《究竟啥才是互联网架构“可扩展”》。 什么是CAP定理? CAP定理,是对上述分布式系统的三个特性,进行了归纳: ● 一致性( C onsistency) ● 可用性( A vailability) ● 分区容忍性( P artition Tolerance) 并且,定理指出,在系统实现时,这三者最多兼顾两点。 一致性,可用性,多节点扩展性三者只能取其二,既然加锁已经加上,常见的最佳工程架构实践是什么呢? 互联网,最常见的实践是这样的: ● 节点连通性,多节点扩展性,连通性异常的处理必须保证,满足P ● 一致性C与可用性A一般二选一 ● 选择一致性C,举例:传统单库水平切分,就是这类选型的典型 ● 选择可用性A,举例:双主库同步高可用,就是这类选型的典型 强一致很难怎么办? 单点串行化,虽然能保证“强一致”,但对系统的并发性能,以及高可用有较大影响,互联网的玩法,更多的是“最终一致性”,短期内未必读到最新的数据,但在一个可接受的时间窗口之后,能够读到最新的数据。 例如:数据库主从同步,从库上的数据,就是一个最终的一致。 总结 ● CAP可以理解为一致性,可用性,联通与扩展性 ● CAP三者只能取其二 ● 最常见的实践是AP+最终一致性 思路比结论重要。 原文发布时间为:2018-10-30 本文作者:58沈剑 本文来自云栖社区合作伙伴“架构师之路”,了解相关信息可以关注“架构师之路”。

优秀的个人博客,低调大师

Python基础系列-列表解析(list comprehension)

版权声明:如需转载,请注明转载地址。 https://blog.csdn.net/oJohnny123/article/details/81910866 列表解析(list comprehension) A2 = [i for i in A1 if i in A0] 其实等同于 A2 = [] for i in A1: if i in A0: A2.append(i) 所以就可以玩出很多花来了,包含列表的交集、差集等等。 代码: #!/usr/bin/python # -*- coding: UTF-8 -*- """ Created by liaoyangyang1 on 2018/2/28 下午9:26. """ A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5))) A1 = range(10) A2 = [i for i in A1 if i in A0] A3 = [A0[s] for s in A0] A4 = [i for i in A1 if i in A3] A5 = {i:i*i for i in A1} A6 = [[i,i*i] for i in A1] 执行结果: /Users/liaoyangyang/crc/codes-python/LearnPython/venv/bin/python /Users/liaoyangyang/crc/codes-python/LearnPython/test.py A0:{'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4} A1:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] A2:[] A3:[1, 3, 2, 5, 4] A4:[1, 2, 3, 4, 5] A5:{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} A6:[[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]] Process finished with exit code 0

优秀的个人博客,低调大师

JavaScript 基础--- (运算符/数组/函数/变量)

基本概念: 1.javascript的组成:三部分组成 (1)ECMAScript - ECMA : 欧洲计算机协会 - 有ECMA组织制定的js的语法,语句..... (2)BOM:- broswer object model: 浏览器对象模型 (3)DOM:- document object model:文档对象模型 2. js的原始类型(五个) - string: 字符串 *** var str = "abc"; - number:数字类型 *** var m = 123; - boolean:true和false *** var flag = true; - null *** var date = new Date(); *** 获取对象的引用,null表示对象引用为空 ,所有对象的引用也是object - undifined *** 定义一个变量,没有赋值 *** var aa; ** typeof(); 查看当前变量的数据类型 3.js的运算符 ** += : x+=y; ===> x=x+y; ** js里面不区分整数和小数 var j = 123; alert(j/1000*1000); // j/1000*1000 在java里面得到结果是 0 // 在js里面不区分整数和小数,123/1000=0.123 * 1000 = 123 ** 字符串的相加和相减的操作 var str = "123"; ** 如果相加时候,做是字符串连接 ** 如果相减,做的是相减的运算 * //字符串的操作 var str = "456"; //alert(str+1); //在java里面操作的结果是 4561 ,在js里面还是 4561 alert(str-1); //相减时候,执行减法的运算 * 提示NaN:表示不是一个数字 ** boolean类型也可以操作 *** 如果设置成true,相当于这个值是1 *** 如果设置成false,相当于这个值是0 ** == 和 === 区别 ** 做判断** == 比较的只是值 === 比较的是值和类型 ** 引入知识 直接向页面输出的语句(可以把内容显示在页面上) * document.write("aaa"); document.wirte("<hr/>"); ** 可以向页面输出变量,固定值和html代码 4.实现99乘法表 5.js的数组 * 什么是数组? - 使用变量,var m = 10; - java里面的数组 定义 int[] arr = {1,2,3}; * 定义方式(三种) 第一种: var arr = [1,2,3]; var arr = [1,"4",true]; 第二种:使用内置对象 Array对象 var arr1 = new Array(5); //定义一个数组,数组的长度是5 arr1[0] = "1"; 第三种:使用内置对象 Array var arr2 = new Array(3,4,5); //定义一个数组,数组里面的元素是3 4 5 * 数组里面有一个属性 length:获取到数组的长度 * 数组可以存放不同的数据类型的数据 6.js的函数 ** 在java里面定义方法 public 返回类型void /int 方法名(参数列表) { 方法体; 返回值; } public int add(int a,int b) { int sum = a+b; return sum; } ** 在js里面定义函数(方法)有三种方式 **** 函数的参数列表里面,不需要写var,直接写参数名称 第一种方式: **** 使用到一个关键字 function **** function 方法名(参数列表) { 方法体; 返回值可有可无(根据实际需要); } // **** 代码:使用第一种方式创建函数 function test() { alert("qqqqq"); } //调用方法 //test(); //定义一个有参数的方法 实现两个数的相加 function add1(a,b) { var sum = a+b; alert(sum); } //add1(2,3); //有返回值的效果 function add2(a,b,c) { var sum1 = a+b+c; return sum1; } alert(add2(3,4,5)); 第二种方式: **** 匿名函数 var add = function(参数列表) { 方法体和返回值; } **** 代码 //第二种方式创建函数 var add3 = function(m,n) { alert(m+n); } //调用方法 add3(5,6); 第三种方式:(了解) *** 动态函数 *** 使用到js里面的一个内置对象 Function var add = new Function("参数列表","方法体和返回值"); 7.js的全局变量和局部变量 ** 全局变量:在script标签里面定义一个变量,这个变量在页面中js部分都可以使用(没有 Var 的也是全局变量) - 在方法外部使用,在方法内部使用,在另外一个script标签使用 var ss == 'zs'; //全局变量 ** 局部变量:在方法内部定义一个变量,只能在方法内部使用(写在function里面的) - 如果在方法的外部调用这个变量,提示出错 var ss = 'zs'; //局部变量

优秀的个人博客,低调大师

Java并发编程笔记之基础总结(二)

一.线程中断 Java 中线程中断是一种线程间协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是需要被中断的线程根据中断状态自行处理。 1.void interrupt() 方法:中断线程,例如当线程 A 运行时,线程 B 可以调用线程 A 的 interrupt() 方法来设置线程 A 的中断标志为 true 并立即返回。设置标志仅仅是设置标志,线程 A 并没有实际被中断,会继续往下执行的。如果线程 A 因为调用了 wait 系列函数或者 join 方法或者 sleep 函数而被阻塞挂起,这时候线程 B 调用了线程 A 的 interrupt() 方法,线程 A 会在调用这些方法的地方抛出 InterruptedException 异常而返回。 2.boolean isInterrupted():检测当前线程是否被中断,如果是返回 true,否者返回 false,代码如下: public boolean isInterrupted() { //传递false,说明不清除中断标志 return isInterrupted(false); } 3.boolean interrupted():检测当前线程是否被中断,如果是返回 true,否者返回 false,与 isInterrupted 不同的是该方法如果发现当前线程被中断后会清除中断标志,并且该函数是 static 方法,可以通过 Thread 类直接调用。另外从下面代码可以知道 interrupted() 内部是获取当前调用线程的中断标志而不是调用 interrupted() 方法的实例对象的中断标志。 public static boolean interrupted() { //清除中断标志 return currentThread().isInterrupted(true); } 下面看一个线程使用 Interrupted 优雅退出的经典使用例子,代码如下: public void run(){ try{ .... //线程退出条件 while(!Thread.currentThread().isInterrupted()&& more work to do){ // do more work; } }catch(InterruptedException e){ // thread was interrupted during sleep or wait } finally{ // cleanup, if required } } 下面看一个根据中断标志判断线程是否终止的例子: /** * Created by cong on 2018/7/17. */ public class InterruptTest { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Runnable() { @Override public void run() { //如果当前线程被中断则退出循环 while (!Thread.currentThread().isInterrupted()) System.out.println(Thread.currentThread() + " hello"); } }); //启动子线程 thread.start(); //主线程休眠1s,以便中断前让子线程输出点东西 Thread.sleep(1); //中断子线程 System.out.println("main thread interrupt thread"); thread.interrupt(); //等待子线程执行完毕 thread.join(); System.out.println("main is over"); } } 运行结果如下: 如上代码子线程 thread 通过检查当前线程中断标志来控制是否退出循环,主线程在休眠 1s 后调用 thread 的 interrupt() 方法设置了中断标志,所以线程 thread 退出了循环。 总结:中断一个线程仅仅是设置了该线程的中断标志,也就是设置了线程里面的一个变量的值,本身是不能终止当前线程运行的,一般程序里面是检查这个标志的状态来判断是否需要终止当前线程。 二.理解线程上下文切换 在多线程编程中,线程个数一般都大于 CPU 个数,而每个 CPU 同一时刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行,CPU 资源的分配采用了时间片轮转的策略,也就是给每个线程分配一个时间片,在时间片内占用 CPU 执行任务。当前线程的时间片使用完毕后当前就会处于就绪状态并让出 CPU 让其它线程占用,这就是上下文切换,从当前线程的上下文切换到了其它线程。 那么就有一个问题让出 CPU 的线程等下次轮到自己占有 CPU 时候如何知道之前运行到哪里了? 所以在切换线程上下文时候需要保存当前线程的执行现场,当再次执行时候根据保存的执行现场信息恢复执行现场 线程上下文切换时机: 1.当前线程的 CPU 时间片使用完毕处于就绪状态时候; 2.当前线程被其它线程中断时候 总结:由于线程切换是有开销的,所以并不是开的线程越多越好,比如如果机器是4核心的,你开启了100个线程,那么同时执行的只有4个线程,这100个线程会来回切换线程上下文来共享这四个 CPU。 三.线程死锁 什么是线程死锁呢? 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去。 如上图,线程 A 已经持有了资源1的同时还想要资源2,线程 B 在持有资源2的时候还想要资源1,所以线程1和线程2就相互等待对方已经持有的资源,就进入了死锁状态。 那么产生死锁的原因都有哪些,学过操作系统的应该都知道死锁的产生必须具备以下四个必要条件。 1.互斥条件:指线程对已经获取到的资源进行排它性使用,即该资源同时只由一个线程占用。如果此时还有其它进行请求获取该资源,则请求者只能等待,直至占有资源的线程用毕释放。 2.请求并持有条件:指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新资源已被其其它线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己已经获取的资源。 3.不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其它线程抢占,只有在自己使用完毕后由自己释放。 4.环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,即线程集合{T0,T1,T2,···,Tn}中的 T0 正在等待一个 T1 占用的资源;T1 正在等待 T2 占用的资源,……Tn正在等待已被 T0 占用的资源。 下面通过一个例子来说明线程死锁,代码如下: /** * Created by cong on 2018/7/17. */ public class DeadLockTest1 { // 创建资源 private static Object resourceA = new Object(); private static Object resourceB = new Object(); public static void main(String[] args) { // 创建线程A Thread threadA = new Thread(new Runnable() { public void run() { synchronized (resourceA) { System.out.println(Thread.currentThread() + " get ResourceA"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get ResourceB"); synchronized (resourceB) { System.out.println(Thread.currentThread() + "get ResourceB"); } } } }); // 创建线程B Thread threadB = new Thread(new Runnable() { public void run() { synchronized (resourceB) { System.out.println(Thread.currentThread() + " get ResourceB"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get ResourceA"); synchronized (resourceA) { System.out.println(Thread.currentThread() + "get ResourceA"); } }; } }); // 启动线程 threadA.start(); threadB.start(); } } 运行结果如下: 下面分析下代码和结果,其中 Thread-0 是线程 A,Thread-1 是线程 B,代码首先创建了两个资源,并创建了两个线程。 从输出结果可以知道线程调度器先调度了线程 A,也就是把 CPU 资源让给了线程 A,线程 A 调用了 getResourceA() 方法,方法里面使用 synchronized(resourceA) 方法获取到了 resourceA 的监视器锁,然后调用 sleep 函数休眠 1s,休眠 1s 是为了保证线程 A 在执行 getResourceB 方法前让线程 B 抢占到 CPU 执行 getResourceB 方法。 线程 A 调用了 sleep 期间,线程 B 会执行 getResourceB 方法里面的 synchronized(resourceB),代表线程 B 获取到了 objectB 对象的监视器锁资源,然后调用 sleep 函数休眠 1S。 好了,到了这里线程 A 获取到了 objectA 的资源,线程 B 获取到了 objectB 的资源。线程 A 休眠结束后会调用 getResouceB 方法企图获取到 ojbectB 的资源,而 ObjectB 资源被线程 B 所持有,所以线程 A 会被阻塞而等待。而同时线程 B 休眠结束后会调用 getResourceA 方法企图获取到 objectA 上的资源,而资源 objectA 已经被线程 A 持有,所以线程 A 和 B 就陷入了相互等待的状态也就产生了死锁。 下面从产生死锁的四个条件来谈谈本案例如何满足了四个条件。 首先资源 resourceA 和 resourceB 都是互斥资源,当线程 A 调用 synchronized(resourceA) 获取到 resourceA 上的监视器锁后释放前,线程 B 在调用 synchronized(resourceA) 尝试获取该资源会被阻塞,只有线程 A 主动释放该锁,线程 B 才能获得,这满足了资源互斥条件。 线程 A 首先通过 synchronized(resourceA) 获取到 resourceA 上的监视器锁资源,然后通过 synchronized(resourceB) 等待获取到 resourceB 上的监视器锁资源,这就构造了持有并等待。 线程 A 在获取 resourceA 上的监视器锁资源后,不会被线程 B 掠夺走,只有线程 A 自己主动释放 resourceA 的资源时候,才会放弃对该资源的持有权,这构造了资源的不可剥夺条件。 线程 A 持有 objectA 资源并等待获取 objectB 资源,而线程 B 持有 objectB 资源并等待 objectA 资源,这构成了循环等待条件。 所以线程 A 和 B 就形成了死锁状态。 那么如何避免线程死锁呢? 要想避免死锁,需要破坏构造死锁必要条件的至少一个即可,但是学过操作系统童鞋应该都知道目前只有持有并等待和循环等待是可以被破坏的。 造成死锁的原因其实和申请资源的顺序有很大关系,使用资源申请的有序性原则就可以避免死锁,那么什么是资源的有序性呢,先看一下对上面代码的修改: // 创建线程B Thread threadB = new Thread(new Runnable() { public void run() { synchronized (resourceA) { System.out.println(Thread.currentThread() + " get ResourceB"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get ResourceA"); synchronized (resourceB) { System.out.println(Thread.currentThread() + "get ResourceA"); } }; } }); 运行结果如下: 如上代码可知修改了线程 B 中获取资源的顺序和线程 A 中获取资源顺序一致,其实资源分配有序性就是指假如线程 A 和 B 都需要资源1,2,3……n 时候,对资源进行排序,线程 A 和 B 只有在获取到资源 n-1 时候才能去获取资源 n。 总结:编写并发程序,多个线程进行共享多个资源时候要注意采用资源有序分配法避免死锁的产生。 四守护线程与用户线程 Java 中线程分为两类,分别为 Daemon 线程(守护线程)和 User 线程(用户线程),在 JVM 启动时候会调用 main 函数,main 函数所在的线程是一个用户线程,这个是我们可以看到的线程,其实 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程(严格说属于 JVM 线程)。 那么守护线程和用户线程有什么区别呢? 区别之一是当最后一个非守护线程结束时候,JVM 会正常退出,而不管当前是否有守护线程;也就是说守护线程是否结束并不影响 JVM 的退出。言外之意是只要有一个用户线程还没结束正常情况下 JVM 就不会退出。 那么 Java 中如何创建一个守护线程呢?代码如下: public static void main(String[] args) { Thread daemonThread = new Thread(new Runnable() { public void run() { } }); //设置为守护线程 daemonThread.setDaemon(true); daemonThread.start(); } 可知只需要设置线程的 daemon 参数为 true 即可。 下面通过例子来加深用户线程与守护线程的区别的理解,首先看下面代码: /** * Created by cong on 2018/7/17. */ public class UserThreadTest { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { public void run() { for(;;){} } }); //启动子线 thread.start(); System.out.print("main thread is over"); } } 运行结果如下: 如上代码在 main 线程中创建了一个 thread 线程,thread 线程里面是无限循环,运行代码从结果看 main 线程已经运行结束了,那么 JVM 进程已经退出了?从 IDE 的输出结侧上的红色方块说明 JVM 进程并没有退出,另外 Mac 上执行ps -eaf | grep java会输出结果,也可以证明这个结论。 这个结果说明了当父线程结束后,子线程还是可以继续存在的,也就是子线程的生命周期并不受父线程的影响。也说明了当用户线程还存在的情况下 JVM 进程并不会终止。 那么我们把上面的 thread 线程设置为守护线程后在运行看看会有什么效果,代码如下: /** * Created by cong on 2018/7/17. */ public class DaemonThreadTest { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { public void run() { for(;;){} } }); //设置为守护线程 thread.setDaemon(true); //启动子线 thread.start(); System.out.print("main thread is over"); } } 运行结果如下: 如上在启动线程前设置线程为守护线程,从输出结果可知 JVM 进程已经终止了,执行ps -eaf |grep java也看不到 JVM 进程了。这个例子里面 main 函数是唯一的用户线程,thread 线程是守护线程,当 main 线程运行结束后,JVM 发现当前已经没有用户线程了,就会终止 JVM 进程。 Java 中在 main 线程运行结束后,JVM 会自动启动一个叫做 DestroyJavaVM 线程,该线程会等待所有用户线程结束后终止 JVM 进程。 下面通过简单的 JVM 代码来证明这个结论,翻开 JVM 的代码,最终会调用到 JavaMain 这个函数: int JNICALL JavaMain(void * _args) { ... //执行Java中的main函数 (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); //main函数返回值 ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1; //等待所有非守护线程结束,然后销毁JVM进程 LEAVE(); } LEAVE 是 C 语言里面的一个宏定义,定义如下: #define LEAVE() do { if ((*vm)->DetachCurrentThread(vm) != JNI_OK) { JLI_ReportErrorMessage(JVM_ERROR2); ret = 1; } if (JNI_TRUE) { (*vm)->DestroyJavaVM(vm); return ret; } } while (JNI_FALSE) 上面宏的作用实际是创建了一个名字叫做 DestroyJavaVM 的线程来等待所有用户线程结束。 在 Tomcat 的 NIO 实现 NioEndpoint 中会开启一组接受线程用来接受用户的链接请求和一组处理线程负责具体处理用户请求,那么这些线程是用户线程还是守护线程呢?下面我们看下 NioEndpoint 的 startInternal 方法,源码如下: public void startInternal() throws Exception { if (!running) { running = true; paused = false; ... //创建处理线程 pollers = new Poller[getPollerThreadCount()]; for (int i=0; i<pollers.length; i++) { pollers[i] = new Poller(); Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true);//声明为守护线程 pollerThread.start(); } //启动接受线程 startAcceptorThreads(); } protected final void startAcceptorThreads() { int count = getAcceptorThreadCount(); acceptors = new Acceptor[count]; for (int i = 0; i < count; i++) { acceptors[i] = createAcceptor(); String threadName = getName() + "-Acceptor-" + i; acceptors[i].setThreadName(threadName); Thread t = new Thread(acceptors[i], threadName); t.setPriority(getAcceptorThreadPriority()); t.setDaemon(getDaemon());//设置是否为守护线程,默认为守护线程 t.start(); } } private boolean daemon = true; public void setDaemon(boolean b) { daemon = b; } public boolean getDaemon() { return daemon; } 如上代码也就是说默认情况下接受线程和处理线程都是守护线程,这意味着当 Tomact 收到 shutdown 命令后 Tomact 进程会马上消亡,而不会等处理线程处理完当前的请求。 总结:如果你想在主线程结束后 JVM 进程马上结束,那么创建线程的时候可以设置线程为守护线程,否则如果希望主线程结束后子线程继续工作,等子线程结束后在让 JVM 进程结束那么就设置子线程为用户线程。

优秀的个人博客,低调大师

Android 基础动画之补间动画详解

Android系统SDK为开发者提供了很多丰富的API去实现绚丽夺目的动画,动画也是学习掌握自定义控件的必不可少的内容。Android动画主要分为如下几类: View Animation: 视图动画(也叫补间动画:Tween Animation)在Android早期版本系统中就已经提供了,这种动画只能被用来设置View的动画。 Drawable Animation: 一般称为Frame动画、帧动画,这类动画可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样。 Property Animation: 属性动画,属性动画只对Android 3.0(API 11)以上版本的Android系统才有效, 这种动画可以设置给任何Object,包括那些还没有渲染到屏幕上的对象。这种动画是可扩展的,可以让你自定义任何类型和属性的动画。 下面就以上动画分类逐个分析(介于篇幅的原因我将动画分类写成不同的文章这样方便阅读): View Animation(补间动画): 补间动画可以在一个视图容器内执行一系列简单变换(具体的变换步骤有:位置、大小、旋转、透明度)。 假设现在有一个View对象,我们可以通过平移、旋转、缩放、透明度等API进行具体的操作。 补间动画的实现方式可以通过 XML或通过Android代码两种方式 去定义。使用XML文件这种编写方式去定义补间动画可能会更加快捷方便(因为XML本质就是一门标记语言、可读性强)。 另外,说到补间动画,就必须要提到Animation抽象类,Animation抽象类是所有补间动画类的基类,那么作为父类的Animation肯定提供了一些公共的属性供子类去使用: Animation常用属性 Animation抽象类的子类补间动画常见有以下四个子类,其子类对应的XML写法以及类信息如下图: 具体的补间动画 A:平移动画Translation xml方式实现 文件名:animator_translate.xml <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromXDelta="0" android:fromYDelta="0" android:toYDelta="0" android:toXDelta="200" android:duration="500" android:fillAfter="true"> </translate> 代码加载xml文件获取动画 //加载动画 Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_translate); //执行动画 testBtn.startAnimation(animation); 代码方式实现 TranslateAnimation translateAnimation = new TranslateAnimation(0,200,0,0); translateAnimation.setDuration(500);//动画执行时间 translateAnimation.setFillAfter(true);//动画执行完成后保持状态 //执行动画 testBtn.startAnimation(translateAnimation); 以上是平移动画的两种实现方式。平移动画需要注意的一些属性有: android:fromXDelta对应的就是TranslateAnimation(float fromXDelta, …) 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点) ;android:fromYDelta对应的就是TranslateAnimation(…, float fromYDelta, …) 起始点Y轴从标,平移的规律同上 ;android:toXDelta对应的就是TranslateAnimation(…, float toXDelta, …) 结束点X轴坐标,平移的规律同上 ;android:toYDelta对应的就是TranslateAnimation(…, float toYDelta) 结束点Y轴坐标,平移的规律同上 B:旋转动画Rotation xml方式实现 文件名:animator_rotation.xml <?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromDegrees="0" android:toDegrees="90" android:pivotX="50%" android:pivotY="50%" android:duration="500" android:fillAfter="true"> </rotate> 代码加载xml文件获取动画 Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_rotation); testBtn.startAnimation(animation); 代码方式实现 RotateAnimation rotateAnimation = new RotateAnimation(0,90,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f); rotateAnimation.setDuration(500); rotateAnimation.setFillAfter(true); testBtn.startAnimation(rotateAnimation); 以上是旋转动画的两种实现方式。旋转动画需要注意的一些属性有: android:fromDegrees对应的就是(RotateAnimation(float fromDegrees, …))这个属性代表的是:旋转开始角度,正代表顺时针度数,负代表逆时针度数 ;android:toDegrees对应的就是(RotateAnimation(…, float toDegrees, …))这个属性代表的是:旋转结束角度,正代表顺时针度数,负代表逆时针度数 ;android:pivotX对应的就是(RotateAnimation(…, float pivotX, …))这个属性代表的是:缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点) ;android:pivotY对应的就是(RotateAnimation(…, float pivotY))这个属性代表的是:缩放起点Y坐标,同上规律 C、缩放动画Scale xml方式实现 文件名:animator_scal.xml <?xml version="1.0" encoding="utf-8"?> <scale xmlns:android="http://schemas.android.com/apk/res/android" android:fromXScale="1" android:fromYScale="1" android:toYScale="2" android:toXScale="2" android:duration="500" android:pivotX="50%" android:pivotY="50%" android:fillAfter="true"> </scale> 代码加载xml文件获取动画 Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_scal); testBtn.startAnimation(animation); 代码方式实现 ScaleAnimation scaleAnimation = new ScaleAnimation(1,2,1,2,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f); scaleAnimation.setDuration(500); scaleAnimation.setFillAfter(true); testBtn.startAnimation(scaleAnimation); 以上是缩放动画的两种实现方式。缩放动画需要注意的一些属性有: android:fromXScale对应的就是(ScaleAnimation(float fromX, …)这个属性代表的是:初始X轴缩放比例,1.0表示无变化 ;android:toXScale对应的就是(ScaleAnimation(…, float toX, …)这个属性代表的是:结束X轴缩放比例 ;android:fromYScale对应的就是(ScaleAnimation(…, float fromY, …)这个属性代表的是:初始Y轴缩放比例 ;android:toYScale对应的就是(ScaleAnimation(…, float toY, …)这个属性代表的是:结束Y轴缩放比例 ;android:pivotX对应的就是(ScaleAnimation(…, float pivotX, …)这个属性代表的是:缩放起点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%做为初始点、50%p表示以当前View的左上角加上父控件宽高的50%做为初始点) ;android:pivotY对应的就是(ScaleAnimation(…, float pivotY)这个属性代表的是:缩放起点Y轴坐标,同上规律 D:透明度动画Alpha xml方式实现 文件名:animator_alpha.xml <?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="1" android:toAlpha="0.2" android:duration="500" android:fillAfter="true"> </alpha> 代码加载xml文件获取动画 Animation animation = AnimationUtils.loadAnimation(this, R.anim.animator_alpha); testBtn.startAnimation(animation); 代码方式实现 AlphaAnimation alphaAnimation = new AlphaAnimation(1,0.2f); alphaAnimation.setDuration(500); alphaAnimation.setFillAfter(true); testBtn.startAnimation(alphaAnimation); 以上是透明度动画的两种实现方式。透明度动画需要注意的是有个属性:android:fromAlpha对应的就是(AlphaAnimation(float fromAlpha, …) )这个属性代表的是:动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明) android:toAlpha对应的就是(AlphaAnimation(…, float toAlpha))这个属性代表的是:动画结束的透明度(0.0到1.0,0.0是全透明,1.0是不透明) AnimationSet动画集合的方式实现 (伪代码): 文件名:aset <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@[package:]anim/interpolator_resource" > <alpha android:fromAlpha="float" android:toAlpha="float" /> <scale android:fromXScale="float" android:toXScale="float" android:fromYScale="float" android:toYScale="float" android:pivotX="float" android:pivotY="float" /> <translate android:fromXDelta="float" android:toXDelta="float" android:fromYDelta="float" android:toYDelta="float" /> <rotate android:fromDegrees="float" android:toDegrees="float" android:pivotX="float" android:pivotY="float" /> </set> Java代码实现: ImageView view = (ImageView) findViewById(R.id.image); Animation animationSet = AnimationUtils.loadAnimation(this, R.anim.aset); //一些拓展API //开始动画集 animationSet.start(); //取消动画集 animationSet.cancel(); //判断当前动画集是否开始 animationSet.hasStarted(); //判断当前动画集是否结束 animationSet.hasEnded(); //重新开始当前动画集 animationSet.reset(); view.startAnimation(animationSet); 值得注意的是:补间动画执行之后并未改变View的真实布局属性。假设现在Activity中有一个 Button在屏幕上方,设置了平移动画移动到屏幕下方然后保持动画最后执行状态呆在屏幕下方。如果点击屏幕下方动画执行之后的Button是没有任何反应,而点击原来屏幕上方没有Button的地方却响应的是点击Button的事件,这一点是需要注意的。 这个问题是基于Android早期团队设计的时候带来的一个问题,那么谷歌Android团队后期针对这个问题肯定提供了解决方案...... 未完待续!!! 如果这篇文章对你有帮助,希望各位看官留下宝贵的star,谢谢。 Ps:著作权归作者所有,转载请注明作者, 商业转载请联系作者获得授权,非商业转载请注明出处(开头或结尾请添加转载出处,添加原文url地址),文章请勿滥用,也希望大家尊重笔者的劳动成果

优秀的个人博客,低调大师

高并发下Java多线程编程基础

Java线程同步与异步 线程池 无锁化的实现方案 分布锁的实现方案 分享的目的: 进一步掌握多线程编程和应用的技巧,希望对大家在平时的开发中应对高并发编程有所帮助 Java线程同步与异步 1. 同步相关的方法有 wait, notify, notifyAll 2. 关键字 synchronized 3. JDK锁的框架 AQS (AbstractQueuedSynchronizer) 4. AQS的实现类 java.util.concurrent.locks.ReentrantLockjava.util.concurrent.locks.ReentrantReadWriteLockjava.util.concurrent.CountDownLatchjava.util.concurrent.Semaphore 5. 例子——两个线程交替打印出100以内的奇数和偶数 主程序: 输出结果示例: ......... 思考: 读者可以用两种其它方法实现,加深自己对Java线程同步和互斥的理解用 ReentrantLock?还是用wait和notify ? 线程池 作用: 控制线程并发数量,一般用在控制单机并发度上, 也是实现流控的一种方案; 实现原理: 1. 参数含义 corePoolSize: 核心线程的数量, 在CPU密集型和IO密集型的任务中,这个参数的设置不太一样: 在CPU密集型的应用中: 通常这个参数被设置为: 机器cpu核数-1, 例如机器有4个核,这个参数就被设置为3, 这样做的即兼顾了最大的并发度,又兼顾了其它非重要的核心任务的执行; 在IO密集的任务中: 通常这个参数被设置为机器cpu核数*(1.5 - 3),具体情况还需要根据实际业务情况进行压测比较,然后再给出最优的值;maximumPoolSize: 最大核心线程的数量poolSize: 当前线程的数量 当用户向线程池中新提交一个线程的时候,会有如下情况: 情况1. 如果当前线程池中线程的数量小于corePoolSize, 就会创建一个新的线程, 并添加到线程池中; 情况2. 如果当前线程池中线程的数量等于corePoolSize, 并且等待队列中还没有满,则把当前用户添加的线程对象放在等待队列中; 情况3. 如果当前线程池中线程的数量大于等于corePoolSize并且小于maximunPoolSize,并且等待队列已经满,则创建一个新的线程,并添加到线程池中; 情况4. 如果当前线程池中线程的数量等于maximunPoolSize, 则会根据线程创建线程时候的拒绝策略,进行相应的处理; 2. java线程对象中run方法和start方法的区别: 2.1 线程对象直接调用run方法,JVM是不会有感知,是不会直接产生一个新的线程, 此时程序运行的方式依然是串行的;2.2 线程对象直接调用start方法,JVM才会有感知,会产生一个新的线程, 此时才会产生并发多线程;线程池正是充分利用了run方法和start的区别来实现线程的复用; 3. 线程池的核心代码 下面均是以jdk1.6的线程池的源码,jdk1.7和jdk1.8线程池实现在上有些变化,但核心思想不变,有兴趣可以自己去研究 提交线程的核心代码: 执行用户任务的核心代码: 无锁化的实现方案 用线程池的方案 1. netty的reactor线程模型,参考netty官方或网上相关的资料 2. 异地机房数据库之间的数据同步: 用表名+主键名做hash ,hash值相同的记录被写到同一个Kafka的Partition中去,假设一个Partition用一个线程进行消费, 这样不同线程之间写入目标数据库的时候,就不会存在数据库行锁的竞争关系,间接实现了无锁化的操作, 即线程之间并行,线程内部串行, 如下图所示; 用CAS的命令 1. JDK中各种类型值的原子操作 AtomicIntegerAtomicLongAtomicBoolean 2. jdk中各种锁的实现, 本质也是volitate变量+CAS java.util.concurrent.locks.ReentrantLockjava.util.concurrent.Semaphorejava.util.concurrent.CountDownLatch 分布锁的实现方案 1. tair incr和decr操作,相当于是乐观锁 2. Redis/memcache setNx命令 3. Zookeeper 充分利用watcher机制,创建临时结点,谁创建成功,谁就获得当前的锁 4. 数据库:利用数据库的行锁 // 加锁SQLupdate trade_base set status = 1 where trade_no=“XXX” and status = 0;// 解锁SQLupdate trade_base set status = 0 where trade_no=“XXX” and status = 1;注意trade_base表上一要有trade_no的列的唯一索引 当然具体用那种分布锁,还需要结合业务自身的需要,一般来说,在并发量不是别大,数据库完全可以扛得住的情况下,用数据库实现分布锁最快,最方便,而且性能的损失也非常地小;当然现在很多场景下,都是分库分表,并且加锁和解锁分别都只影响一行,对数据库来说,加锁和解锁的 sql也是非常轻量的sql操作,因此在性能损失上不用过多的担心;

优秀的个人博客,低调大师

爬虫入门之Scrapy 框架基础功能(九)

Scrapy是用纯Python实现一个为了爬取网站数据、提取结构性数据而编写的应用框架,用途非常广泛。 框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页内容以及各种图片,非常之方便。 Scrapy 使用了 Twisted(其主要对手是Tornado)多线程异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。 1 Scrapy架构图(绿线是数据流向) Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间通讯,信号、数据传递等。 Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排队,当引擎需要时,交还给引擎。 Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理. Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器), Item Pipeline(管道):负责处理Spider中获取到的Item,并进行后期处理(分析、过滤、存储等)地方. Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。 Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests) 2 Scrapy的运作流程 代码写好,程序开始运行… 引擎:Hi!Spider, 你要处理哪一个网站? Spider:老大要我处理xxxx.com。 引擎:你把第一个需要处理的URL给我吧。 Spider:给你,第一个URL是xxxxxxx.com。 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。 调度器:好的,正在处理你等一下。 引擎:Hi!调度器,把你处理好的request请求给我。 调度器:给你,这是我处理好的request 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载) 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的) Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。 引擎:Hi !管道我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。 管道``调度器:好的,现在就做! 注意!只有当调度器中不存在任何request了,整个程序才会停止,(也就是说,对于下载失败的URL,Scrapy也会重新下载。) 3 Scrapy的安装介绍 1、安装wheel pip3 install wheel 2、安装lxml pip3 install lxml-4.2.1-cp36-cp36m-win_amd64.whl 3、安装pyopenssl (已安装就不需要安装) 4、安装Twisted pip3 install Twisted-18.4.0-cp36-cp36m-win_amd64.whl 5、安装pywin32 pip3 install pypiwin32 6、安装scrapy pip3 install scrapy 或者pip3 install Scrapy-1.5.0-py2.py3-none-any.whl Scrapy框架官方网址:http://doc.scrapy.org/en/latest Scrapy中文维护站点:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html Windows 安装方式 Python 2 / 3 升级pip版本: pip install --upgrade pip 通过pip 安装 Scrapy 框架 pip install Scrapy Ubuntu 需要9.10或以上版本安装方式 Python 2 / 3 安装非Python的依赖 sudo apt-get install python-dev python-pip libxml2-dev libxslt1-dev zlib1g-dev libffi-dev libssl-dev 通过pip 安装 Scrapy 框架 sudo pip install scrapy 安装后,只要在命令终端输入 scrapy,提示类似以下结果,代表已经安装成功 具体Scrapy安装流程参考:http://doc.scrapy.org/en/latest/intro/install.html#intro-install-platform-notes里面有各个平台的安装方法 4 制作 Scrapy 爬虫 一共需要4步: 新建一个新的爬虫项目 scrapy startproject mySpider 进入该目录 cd mySpider scrapy genspider stack http://stackoverflow.com/ #创建一个爬虫stack 指定爬取路径 明确目标 (编写items.py):明确你想要抓取的目标(目标信息采用类与字段的方式实现) 制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页(实现主要逻辑地方,并返回item) 存储内容 (pipelines.py):设计管道存储爬取内容(接受item,一般重写几个常用的方法(open_spider,process_item,close_spider) 启动爬虫项目 命令启动: scrapy crawl spidername(爬虫名) 文件启动 from scrapy import cmdline # 方式一:注意execute的参数类型为一个列表 cmdline.execute('scrapy crawl spidername'.split()) # 方式二:注意execute的参数类型为一个列表 cmdline.execute(['scrapy', 'crawl', 'spidername']) 5 入门案例 一. 新建项目(scrapy startproject) 在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令: scrapy startproject mySpider 其中, mySpider 为项目名称,可以看到将会创建一个 mySpider 文件夹,目录结构大致如下: 下面来简单介绍一下各个主要文件的作用: scrapy.cfg :项目的配置文件 mySpider/ :项目的Python模块,将会从这里引用代码 mySpider/items.py :项目的目标文件 mySpider/pipelines.py :项目的管道文件 mySpider/settings.py :项目的设置文件 mySpider/spiders/ :存储爬虫代码目录 二、明确目标(mySpider/items.py) 我们打算抓取:http://bbs.tianya.cn/post-140-393968-1.shtml 网站里的邮箱。 打开mySpider目录下的items.py Item 定义结构化数据字段,用来保存爬取到的数据,类似dict,但是提供了一些额外的保护减少错误。 可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field的类属性来定义一个Item(可以理解成类似于ORM的映射关系)。 接下来,创建一个TianyaItem类,和构建item模型(model)。 import scrapy class TianyaItem(scrapy.Item): email = scrapy.Field() #只定义爬取email字段 三、制作爬虫 (spiders/itcastSpider.py) 爬虫功能要分两步: 1. 爬数据 在当前目录下输入命令 scrapy genspider mytianya "bbs.tianya.cn" #指定爬虫文件名 爬取的域名 打开 mySpider/spider目录里的 mytianya .py,默认增加了下列代码: import scrapy import re from tianya import items class MytianyaSpider(scrapy.Spider): name = 'mytianya' allowed_domains = ['bbs.tianya.cn'] start_urls = ['http://bbs.tianya.cn/post-140-393977-1.shtml'] #主要的逻辑模块 def parse(self, response): pass 其实也可以由我们自行创建itcast.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦 要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。 name = "":这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。 allow_domains = []是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。 start_urls = ():爬取的URL元组/列表。爬虫从这里开始抓取数据,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成. 将start_urls的值修改为需要爬取的第一个url. parse(self, response):解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下: 负责解析返回的网页数据(response.body),提取结构化数据(生成item) 生成需要下一页的URL请求。 修改parse()方法 def parse(self, response): html = response.body.decode() # ftsd@21cn.com email = re.compile(r"([A-Z0-9_]+@[A-Z0-9]+\.[A-Z]{2,4})", re.I) emailList = email.findall(html) mydict = [] for e in emailList: item = items.TianyaItem() item["email"] = e # mydict[e] = "http://bbs.tianya.cn/post-140-393977-1.shtml" mydict.append(item) return mydict 然后运行一下看看,在mySpider目录下执行: scrapy crawl mytianya 2.保存数据 scrapy保存信息的最简单的方法主要有四种,-o 输出指定格式的文件,,命令如下: scrapy crawl mytianya -o mytianya.json scrapy crawl mytianya -o mytianya.csv scrapy crawl mytianya -o mytianya.xml yield 在这里的作用: def parse(self, response): html = response.body.decode() # ftsd@21cn.com email = re.compile(r"([A-Z0-9_]+@[A-Z0-9]+.[A-Z]{2,4})", re.I) emailList = email.findall(html) mydict = [] for e in emailList: item = items.TianyaItem() item["email"] = e yield mydict #得到生成器对象,每循环一次,返回一个item

优秀的个人博客,低调大师

C++程序设计基础(2)变量

注:读《程序员面试笔记》笔记总结 1.知识点 (1)C++变量命名只能包含字母、数字、下划线,其中开头不能是数字;大小写敏感;习惯上变量用小写字母,常量、宏定义用大写字母。 (2)变量的作用域分为局部变量(函数内部定义),全局变量(函数外部定义)。 (3)关键字extern:在头文件总声明变量,并在前面加上extern,在源文件中定义变量,其他文件使用#include导入头文件,即可使用该变量。附详细链接https://blog.csdn.net/chenqiai0/article/details/8490665 1 //test.h 2 extern int age; 3 //test.cpp 4 #include"test.h" 5 int age = 10; //直接使用age=10是不行的 6 //test1.cpp 7 #include"test.h" 8 int main(int argc, char *argv[]) { 9 cout << age << endl; 10 getchar(); 11 return 0; 12 } 13 14 /*output 15 10 16 */ (4)关键字static:静态变量,首次执行时初始化,其后再执行将不再进行初始化,虽然可能是局部变量,但其生命周期是整个程序运行过程。 (5)关键字const:常量型变量,在头文件中直接定义和声明(不同于一般全局变量),其他文件通过#include使用(多个文件引用不会造成重复定义),如果在源文件中定义,则其作用域值是本源文件。 2.面试题 2.1简述i++和++i的区别 写出下面代码执行后i,j,m,n的值 1 int i=10,j=10; 2 int m=(i++)+(i++)+(i++); 3 int n=(++j)+(++j)+(++j); 知识点:++i是先自身加一,然后再参与赋值运算,i++是先参与赋值运算,然后再自身加一。 本题的特殊之处是不同的编译器会产生不同的结果,在VC编译器中,全部会先完成三个++j,然后再做加法运算,而在gcc编译器中当有两个操作数时就会进行加法运算。故结果如下: 1 #VC编译器 2 i=13,j=13,m=30,n=39;//13+13+13=39 3 #gcc编译器 4 i=13,j=13,m30,n=37; //12+12+13=37 2.2简述C++的类型转换操作符 在C语言中类型转换只需要通过变量前面加上变量类型,并且转换是双向的,这种方式对于简单类型可以,复杂数据类型就力不从心了。 C++提供了四种类型转换操作符:static_cast,dynamic_cast,const_cast,reinterpret_cast。 (1)static_cast可以完全替代C风格类型转换实现基本类型转换; 1 int i = 1; 2 double d = 1.5; 3 int d2i = static_cast<int>(d); 4 double i2d = static_cast<double>(i); 同时相关类(如父子关系)可以完成转换,但是如果父类指针本身指向父类对象,不存在安全问题,如果父类指针本身指向子类对象,则不存在安全问题; 1 class Base {}; 2 class Derived:public Base{}; 3 Base *b1 = new Base; //父类指针指向父类对象 4 Base *b2 = new Derived; //父类指针指向子类对象 5 Derived *b2d1 = static_cast<Derived *>(b1); //转换成功(不安全) 6 Derived *b2d2 = static_cast<Derived *>(b2); //转换成功(安全) (2)dynamic_cast:只能进行对象指针之间的转;转换结果可以使指针也可以是引用;转换时会进行类型检查(static_cast不会进行检查);只有当父类指针指向一个子类对象,并且父类中包含了虚函数,转换才会成功,否则返回空指针,引用的话抛出异常。 1 class Base { virtual void dummy() {}; }; 2 class Drived : public Base{}; 3 Base *b1 = new Base; 4 Base *b2 = new Drived;//父类指针指向子类对象,且父类中包含虚函数 5 //转换结果为指针 6 Drived *b2d1 = dynamic_cast<Drived*>(b1);//转换失败(返回NULL) 7 Drived *b2d2 = dynamic_cast<Drived*>(b2);//转换成功 8 //转换结果为引用 9 Drived &b2d3 = dynamic_cast<Drived &>(*b1);//转换失败(抛出异常) 10 Drived &b2d4 = dynamic_cast<Drived &>(*b2);//转换成功 (3)const_cast:可以在转换过程中增加或删除const属性。一般情况下,无法将常量指针直接赋值给普通指针,但通过const_cast可以移除常量指针的const属性,从而实现const指针到非const指针的转换。 1 class Test{}; 2 const Test *t1 = new Test; 3 Test *t2 = const_cast<Test *>(t1); (4)reinterpret_cast:可以将一种类型的指针直接转换成另一种类型的指针,无论两个类型之间是否有继承关系。此外还可以吧一个指针转换为一个整数,也可以把一个整数转换成一个指针。另外还经常用在不同函数指针之间的转换。 1 class A{}; 2 class B{}; 3 A *a = new A; 4 B *b = reinterpret_cast<B *>(a); //转换成功 2.3简述静态全局变量的概念 (1)在全局变量前面加上static关键字就成为的静态全局变量。 (2)静态全局变量通常在源文件中声明定义,作用域为本文件,(而全局变量一般头文件声明,源文件定义,通过extern关键字,其作用域为整个工程。) (3)如果在头文件中声明了静态全局变量,那么即使在没有初始化的情况下,也会被初始化为默认值,即相当于也被定义了,这时其他文件#include该头文件,相当于拷贝了一份,其初始值相同,后面各个文件中将互相不影响。 下面是静态全局变量的一个例子: 1 static int sgn;//声明定义静态全局变量 2 void increaseSG() { sgn++; } 3 int main(){ 4 sgn = 10; 5 cout << sgn << endl; 6 increaseSG(); 7 cout << sgn << endl; 8 getchar(); 9 return 0; 10 }

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册