前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[注意事项]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
Spark
...本高昂等问题。 值得注意的是,尽管分布式缓存带来了诸多便利,但它并非没有挑战。隐私保护、数据一致性以及跨地域同步等问题仍然是业界亟待解决的难题。随着GDPR等法规的出台,企业在使用缓存技术时还需格外注意合规性,确保用户数据的安全与合法使用。在未来,我们或许可以看到更多结合区块链技术的去中心化缓存解决方案,为用户提供更加透明和安全的服务体验。
2025-05-02 15:46:14
82
素颜如水
转载文章
...多维度措施之后,我们注意到,在数字化时代,操作系统安全问题的重要性日益凸显。近日,Red Hat Enterprise Linux(RHEL)发布了最新的安全更新,强化了身份验证机制,包括对PAM模块的进一步优化以抵御潜在攻击,并改进了sudoers文件配置的安全策略指导,旨在帮助管理员更精细地控制用户权限。 同时,针对密码安全方面,NIST近期发布的《数字身份指南》中,提倡采用多因素认证以及定期更换密码的做法,并强调了密码复杂度要求的新变化——从过去单纯追求长度和特殊字符组合转变为支持较长的、易记忆的短语。这一转变意味着,系统管理员在设置密码有效期时,应充分考虑新指南的精神,确保密码策略既安全又符合用户的记忆习惯。 此外,开源社区也在积极应对命令历史记录安全风险。例如,一种新的bash插件被开发出来,可实现命令历史记录加密存储,有效防止恶意攻击者通过查看历史记录获取敏感信息。而在端口扫描防御方面,除了传统的NMAP工具之外,一些实时网络监控与入侵检测系统如Zeek (前Bro)也因其高效识别异常网络活动的能力而备受瞩目。 综上所述,随着信息技术的发展和安全威胁的变化,Linux系统的账号安全管理需不断跟进最新研究和技术动态,结合文中所述的基础措施,灵活运用先进的安全技术和管理理念,构建更加稳固的操作系统安全防线。
2023-05-07 23:37:44
100
转载
转载文章
...电脑远程登录A电脑 注意:AB电脑都连接上了互联网 A电脑: 1添加一个用户名,设置登录密码。2我的电脑→属性→远程→允许用户远程连接到此计算机前 打√确定3网上邻居→属性→本地连接状态→支持→记下IP 地址XXX.XXX.XXX.XXX。 B电脑登录过程4 开始→所有程序→附件→通讯→远程桌面连→在弹出的窗口里输入A电脑的IP 地址 →连接。连接成功后会变成一个黑屏幕的画面,在屏幕的最上方有一个指示条,指示着机器是在远程登 录状态。当A电脑响应了B电脑的远程登录请求后,会给你返回一个画面,要求你输入用户名,密码。 5输入用户名和密码→确定。验证的用户名和密码是对的,他就会把其A桌面画面全传送到B电脑的屏 幕上来,稳定后就成功了! 有一事你不能作:关机。因为B电脑左下角的开始,是指挥自己用的,没 法指挥A电脑。 想使用B电脑控制A电脑关机,得在A电脑上设置:附件→windows 资源管理器→ WINDOWS 的文件夹→SYSTEM32文件夹→taskmgr.exe文件,右击把他发送到桌面上建一“桌面快捷方式”。 你在要关掉A电脑时,只要双击这个快捷方式,就会弹出来一个“WINDWOS任务管理器”窗口,上面有 “关机”命令,点“关机”就行了,当A电脑电源关闭以后,连接自然就断开了。 但这样的远程连接, 是有条件的:A电脑须有独立的 IP ,就是说,A电脑不能是局域网的内部保留 IP,所谓保留IP是指 如 10.XXX.XXX.XXX 或 192.168.XXX.XXX 等地址。如A电脑用的是ADSL,一般来说都是独立的IP,但 如果A用户是几户人家共用一个 ADSL宽带连接,通过一个ADSL共同上网的,那或许就不行了。须在路 由器上作一个“端口映射”设置。注意:A电脑防火墙的影响,有可能连不通。防火墙的缺省设置,一 般是禁止 INTERNET 上的电脑访问它的资源的。因而须开启防火墙的这个设置:允许 INTERNET上的机 器访问本机(A电脑)资源。[shutdown –s –t 0]此命令强制关机,一般不要用, WIN7远程连接前几步设置与WinXP一样。 开始→搜索框中输入MSTSC回车→在弹出的对话框中输入需要连接的计算机的IP→连接→账户密码 →确定不久显示器上出现了另一计算机的桌面,远程桌面连接成功。 教你怎样解除电脑开机密码。此方法仅供交流,严禁作为非法手段使用 方法1在开机时按下F8进入带命令提示符的安全模式输入NET USER+用户名+123456/ADD 可把某用户的密码强行设置为123456 方法2如用户忘记登录密码可 按下方法解决 此法不适用于忘记安装时所设定〔administrator〕的密码 1.在计算机启动时按F8及选Safe Mode With Command Prompt 2.选Administrator后便会跳出Command Prompt的窗口 3.用Net的命令增加一个用户,例:增加一个用户名为alanhkg888,命令语法如下: net user alanhkg888/add 4.将新增用户提升至Administrator的权力,例:提升刚才增 加用户alanhkg888的权力,命令语法如下 net localgroup administrators alanhkg888/add 5.完成上列步骤后重新启动计算机,在 启动画面上便增加了一个用户alanhkg888了,选alanhkg888进入www.shanpow.com_删除Download和DataStore文件夹中的所有文件。 6.登入后在控制台→使用者账户→选忘记密码的用户,然后选移除密码 7.在登入画面中选原来的用户便可不需密码情况下等入(因已移除了) 8.删除刚才新增的用户:在控制台→使用者账户→选alanhkg888,然后选移除账户便可 方法3 1、重新启动Windows XP,在启动画面出现后的瞬间按F8,选择带命令行的安全模 式运行。 2、运行过程停止时,系统列出了超级用户administrator和本地用户owner的选择菜单, 点击administrator,进入命令行模式。 3、键入命令:net user owner 123456/add,强制性将owner用户的口令更改为123456。 若想在此添加某一用户:用户名为abcdef,口令为123456的话,请输入net user abcdef 123456/add,添加后可用net localgroup administrators abcdef/add命令将用户提升为 系统管理组administrators用户,具有超级权限。 4.DOS下删windows\system32\config里面的SAM档就可以了 5.开机后按键盘的Delete键进入BIOS界面。找到User Password选项,其默认为关闭状 态。启动并输入用户密码(1~8位英文或数字)。计算机提示请再输入一遍以确认密码无误, 保存退出后重新启动机器,这时就会在开机时出现密码菜单 方法4我们知道在安装Windows XP过程中,首先是以administrator默认登录,然后会要 求创建一个新账户,以便进入Windows XP时使用此新建账户登录,而且在Windows XP的 登录接口中也只会出现创建的这个用户账号,不会出现administrator,但实际上该 administrator账号还是存在的,且密码为空。 【二】:Windows 7实战经验 Windows 7实战经验:完美解决Windows 7更新失败(Windows Update 错误 80070003) 很多用户反映,为什么Windows 7的自动更新会出显未知错误,导致很多更新都不能正确安装?针对这个问题,在我对自己的Windows 7进行更新的时候,有时也会发生类似的问题,经过研究,已经完美解决,下面给大家解决方案! 如果在检查更新时收到Windows Update错误80070003,则需要删除Windows用于标识计算机更新的临时文件。若要删除临时文件,请停止Windows Update服务,删除临时更新文件,重新启动Windows Update服务,然后再次尝试检查Windows更新。 以下步骤为解决Windows 7更新错误方法,本博客亲测有效。 必须以管理员身份进行登录,才能执行这些步骤。 1.单击打开“管理工具(通过单击“开始”按钮,再依次单击“控制面板”,然后单击“管理工具”。 2.双击“服务”。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 3.单击“名称”列标题以逆序排列名称。找到“Windows Update”服务,右键单击该服务,然后单击“停止”。 1.打开“计算机”。 2.双击安装Windows的本地硬盘(通常是驱动器C)。 3.双击Windows文件夹,然后双击SoftwareDistribution文件夹。 4.双击打开DataStore文件夹,然后删除该文件夹中的所有文件。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 5.单击“后退”按钮。在SoftwareDistribution文件夹中,双击打开Download文件夹,删除该文件夹中的所有文件,然后关闭窗口。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 必须以管理员身份进行登录,才能执行这些步骤。 1.单击打开“管理工具(方法同上)”。 2.双击“服务”。如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。 3.单击“名称”列标题以逆序排列名称。找到“Windows Update”服务,右键单击该服务,然后单击“启动”。 4.关闭“服务”窗口和“管理工具”窗口。 完成上面操作,你需要重新更新看看可以成功更新了吗,一般因为我们删除了自动更新的一些文件,如果你仔细观察的话,那些文件大小并不是很小,所以我们再更新的时候等待的时间可能会长一些! 【三】:Win10系统提示“无法完成更新正在撤销更改” 更新win10系统补丁之后,系统会提示“window10无法更新,正在撤销”,需要重启好几次,这该怎么办呢?下面小编就向大家介绍一下windows10系统无法完成更新正在撤销更改的解决方法,欢迎大家参考和学习。 系统更新失败,反复重启还是不行,那是不是下载下来的补丁没用了呢??所以我们先要删除Windows更新的缓存文件!在做以下操作之前,首先我们要确认系统内的windows update & BITS服务设置是否开启。 检查方法: 1、按“Win+R”组合键打开运行,输入“services.msc”,点击确定(如果弹出用户账户控制窗口,我们点击“继续”)。 2、双击打开“Background Intelligent Transfer Services”服务。 3、在选项卡点击“常规”,要保证“启动类型”是“自动”或者“手动”。然后点击“服务状态”“启用”按钮。 4. 重复步骤3分别对“Windows Installer”,“Cryptographic Services”, “software licensing service” 以及“Windows Update”这四项服务进行检查。 解决办法: 1、按“Windows+X”打开“命令提示符(管理员)”。 2、输入“net stop wuauserv”回车(我们先把更新服务停止)。 3、输入”%windir%\SoftwareDistribution“回车(删除Download和DataStore文件夹中的所有文件)。 4、最后输入“net start wuauserv”回车(重新开启系统更新服务)。 完成以上的步骤之后,我们就可以在“Windows Update”中再次尝试检查更新即可。 以上就是windows10系统无法完成更新正在撤销更改的解决方法介绍了。遇到同样问题的用户,可以尝试一下这个方法,如果不行,可以留言,小编会继续寻找其他的解决办法。 【四】:Windows更新失败提示错误码80070003怎么办 Windows7,Windows8.1,Windows10在更新过程中,所更新的程序无法安装,导致更新失败,提示错误码80070003。遇到这种情况,无论再试一次,或重启电脑,更新程序仍无法安装,出现错误码80070003提示。关于这个故障,下面小编就为大家介绍一下具体的解决方法吧,欢迎大家参考和学习。 具体解决方法步骤: 1、在电脑更新过程中,更新失败,程序无法安装,出现错误码80070003的提示。如图1 2、打开控制面板,点击“系统和安全”,打开对话框。如图2 3、在打开的对话框中,点击“管理工具”-双击“服务”,在打开的对话框的下方找到“Windows Update"。(如图3),选择Windows Update,点击界面左上角的”停止“按键,或是单击右键选择”停止“。(如图4),以管理员身份进入,如果提示需要输入秘码,则输入秘码。 4、在C盘,打开”Windows"文件夹,-双击打开“SoftwareDistribution"文件夹,找到下面的2个文件夹。打开”DataStore"文件夹,删除里面所有的文件。反回上一步。如图5.1,再打开"Download"文件夹,删除里面所有的文件。(如图5.2) 5、返回第三步的操作,选择Windows Update,右键单击,选择“启动”。 6、做完上面操作后,安装更新文件就会顺利了。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_42620202/article/details/119158423。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-16 16:18:33
137
转载
转载文章
..._iadd__()。注意,实现__add__()的时候,必须认识到它返回另一个Time60对象,而不修改原mon或tue: def __add__(self, other):return self.__class__(self.hr + other.hr, self.min + other.min) 在类中,一般不直接调用类名,而是使用self 的__class__属性,即实例化self 的那个类,并调用它。调用self.__class__()与调用Time60()是一回事。但self.__class__()的方式更好。 >>> mon = Time60(10, 30)>>> tue = Time60(11, 15)>>> mon +tue >>> print mon +tue21:45 如果没有定义相对应的特殊方法,但是却使用了该方法对应的运算,则会引起一个TypeError异常: >>> mon -tue Traceback (mostrecent call last): File"", line 1, in? TypeError:unsupported operand type(s)for -: 'Time60' and 'Time60' 3:原位加法 __iadd__(),是用来支持像mon += tue 这样的操作符,并把正确的结果赋给mon。重载一个__i__()方法的唯一秘密是它必须返回self: def __iadd__(self, other): self.hr+=other.hr self.min+=other.minreturn self 下面是结果输出: >>> mon = Time60(10,30)>>> tue = Time60(11,15)>>>mon10:30 >>>id(mon)401872 >>> mon +=tue>>>id(mon)401872 >>>mon21:45 下面是Time60的类的完全定义: classTime60(object):'Time60 - track hours and minutes' def __init__(self,hr, min):'Time60 constructor - takes hours andminutes'self.hr=hr self.min=mindef __str__(self):'Time60 - string representation' return '%d:%d' %(self.hr, self.min)__repr__ = __str__ def __add__(self, other):'Time60 - overloading the additionoperator' return self.__class__(self.hr + other.hr,self.min +other.min)def __iadd__(self,other):'Time60 - overloading in-place addition'self.hr+=other.hr self.min+=other.minreturn self 4:升华 在这个类中,还有很多需要优化和改良的地方。首先看下面的例子: >>> wed =Time60(12, 5)>>>wed12:5 正确的显示应该是:“12:05” >>> thu =Time60(10, 30)>>> fri =Time60(8, 45)>>> thu +fri18:75 正确的显示应该是:19:15 可以做出如下修改: def __str__(self):return '%02d:%02d'%(self.hr, self.min)__repr__ = __str__ def __add__(self, othertime): tmin= self.min +othertime.min thr= self.hr +othertime.hrreturn self.__class__(thr + tmin/60, tmin%60)def __iadd__(self, othertime): self.min+=othertime.min self.hr+=othertime.hr self.hr+= self.min/60self.min%= 60 return self 三:迭代器 迭代器对象本身需要支持以下两种方法,它们组合在一起形成迭代器协议: iterator.__iter__() 返回迭代器对象本身。 iterator.next() 从容器中返回下一个元素。 实现了__iter__()和next()方法的类就是一个迭代器。自定义迭代器的例子如下: RandSeq(Random Sequence),传入一个初始序列,__init__()方法执行前述的赋值操作。__iter__()仅返回self,这就是如何将一个对象声明为迭代器的方式,最后,调用next()来得到迭代器中连续的值。这个迭代器唯一的亮点是它没有终点。代码如下: classRandSeq(object):def __init__(self, seq): self.data=seqdef __iter__(self):returnselfdefnext(self):return choice(self.data) 运行它,将会看到下面的输出: >>> from randseq importRandSeq>>> for eachItem in RandSeq(('rock', 'paper', 'scissors')): ...printeachItem ... scissors scissors rock paper paper scissors ...... 四:多类型定制 现在创建另一个新类,NumStr,由一个数字-字符对组成,记为n和s,数值类型使用整型(integer)。用[n::s]来表示它,这两个数据元素构成一个整体。NumStr有下面的特征: 初始化: 类应当对数字和字符串进行初始化;如果其中一个(或两)没有初始化,则使用0和空字符串,也就是, n=0 且s=''作为默认。 加法: 定义加法操作符,功能是把数字加起来,把字符连在一起;比如,NumStr1=[n1::s1]且NumStr2=[n2::s2]。则NumStr1+NumStr2 表示[n1+n2::s1+s2],其中,+代表数字相加及字符相连接。 乘法: 类似的, 定义乘法操作符的功能为, 数字相乘,字符累积相连, 也就是,NumStr1NumStr2=[n1n::s1n]。 False 值:当数字的数值为 0 且字符串为空时,也就是当NumStr=[0::'']时,这个实体即有一个false值。 比较: 比较一对NumStr对象,比如,[n1::s1] vs. [n2::s2],有九种不同的组合。对数字和字符串,按照标准的数值和字典顺序的进行比较。 如果obj1< obj2,则cmp(obj1, obj2)的返回值是一个小于0 的整数, 当obj1 > obj2 时,比较的返回值大于0, 当两个对象有相同的值时, 比较的返回值等于0。 我们的类的解决方案是把这些值相加,然后返回结果。为了能够正确的比较对象,我们需要让__cmp__()在 (n1>n2) 且 (s1>s2)时,返回 1,在(n1s2),或相反),返回0. 反之亦然。代码如下: classNumStr(object):def __init__(self, num=0, string=''): self.__num =num self.__string =stringdef __str__(self):return '[%d :: %r]' % (self.__num, self.__string)__repr__ = __str__ def __add__(self, other):ifisinstance(other, NumStr):return self.__class__(self.__num + other.__num, self.__string + other.__string)else:raise TypeError, 'Illegal argument type for built-in operation' def __mul__(self, num):ifisinstance(num, int):return self.__class__(self.__num num, self.__string num)else:raise TypeError, 'Illegal argument type for built-inoperation' def __nonzero__(self):return self.__num or len(self.__string)def __norm_cval(self, cmpres):returncmp(cmpres, 0)def __cmp__(self, other):return self.__norm_cval(cmp(self.__num, other.__num))+\ self.__norm_cval(cmp(self.__string,other.__string)) 执行一些例子: >>> a =NumStr(3, 'foo')>>> b =NumStr(3, 'goo')>>> c =NumStr(2, 'foo')>>> d =NumStr()>>> e =NumStr(string='boo')>>> f =NumStr(1)>>>a [3 :: 'foo']>>>b [3 :: 'goo']>>>c [2 :: 'foo']>>>d [0 ::'']>>>e [0 ::'boo']>>>f [1 :: '']>>> a True>>> b False>>> a ==a True>>> b 2[6 :: 'googoo']>>> a 3[9 :: 'foofoofoo']>>> b +e [3 :: 'gooboo']>>> e +b [3 :: 'boogoo']>>> if d: 'not false'...>>> if e: 'not false'...'not false' >>>cmp(a, b)-1 >>>cmp(a, c)1 >>>cmp(a, a) 0 如果在__str__中使用“%s”,将导致字符串没有引号: return '[%d :: %s]' % (self.__num, self.__string)>>> printa [3 :: foo] 第二个元素是一个字符串,如果用户看到由引号标记的字符串时,会更加直观。要做到这点,使用“repr()”表示法对代码进行转换,把“%s”替换成“%r”。这相当于调用repr()或者使用单反引号来给出字符串的可求值版本--可求值版本的确要有引号: >>> printa [3 :: 'foo'] __norm_cval()不是一个特殊方法。它是一个帮助我们重载__cmp__()的助手函数:唯一的目的就是把cmp()返回的正值转为1,负值转为-1。cmp()基于比较的结果,通常返回任意的正数或负数(或0),但为了我们的目的,需要严格规定返回值为-1,0 和1。 对整数调用cmp()及与 0 比较,结果即是我们所需要的,相当于如下代码片断: def __norm_cval(self, cmpres):if cmpres<0:return -1 elif cmpres>0:return 1 else:return 0 两个相似对象的实际比较是比较数字,比较字符串,然后返回这两个比较结果的和。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_30849865/article/details/112989450。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-19 14:30:42
133
转载
转载文章
...考虑,于是我们再次把注意力放在之前的Dump方案。 Dump 方案本质上是尝试从坏DB里读出信息,这个尝试一般来说会出现两种结果: DB的基本格式仍然健在,但个别数据损坏,读到损坏的地方SQLite返回SQLITE_CORRUPT错误, 但已读到的数据得以恢复。 基本格式丢失(文件头或sqlite_master损坏),获取有哪些表的时候就返回SQLITE_CORRUPT, 根本没法恢复。 第一种可以算是预期行为,毕竟没有损坏的数据能部分恢复。从成功率来看,不少用户遇到的是第二种情况,这种有没挽救的余地呢? 要回答这个问题,先得搞清楚sqlite_master是什么。它是一个每个SQLite DB都有的特殊的表, 无论是查看官方文档Database File Format,还是执行SQL语句 SELECT FROM sqlite_master;,都可得知这个系统表保存以下信息: 表名、类型(table/index)、 创建此表/索引的SQL语句,以及表的RootPage。sqlite_master的表名、表结构都是固定的, 由文件格式定义,RootPage 固定为 page 1。 正常情况下,SQLite 引擎打开DB后首次使用,需要先遍历sqlite_master,并将里面保存的SQL语句再解析一遍, 保存在内存中供后续编译SQL语句时使用。假如sqlite_master损坏了无法解析,“Dump恢复”这种走正常SQLite 流程的方法,自然会卡在第一步了。为了让sqlite_master受损的DB也能打开,需要想办法绕过SQLite引擎的逻辑。 由于SQLite引擎初始化逻辑比较复杂,为了避免副作用,没有采用hack的方式复用其逻辑,而是决定仿造一个只可以 读取数据的最小化系统。 虽然仿造最小化系统可以跳过很多正确性校验,但sqlite_master里保存的信息对恢复来说也是十分重要的, 特别是RootPage,因为它是表对应的B-tree结构的根节点所在地,没有了它我们甚至不知道从哪里开始解析对应的表。 sqlite_master信息量比较小,而且只有改变了表结构的时候(例如执行了CREATE TABLE、ALTER TABLE 等语句)才会改变,因此对它进行备份成本是非常低的,一般手机典型只需要几毫秒到数十毫秒即可完成,一致性也容易保证, 只需要执行了上述语句的时候重新备份一次即可。有了备份,我们的逻辑可以在读取DB自带的sqlite_master失败的时候 使用备份的信息来代替。 到此,初始化必须的数据就保证了,可以仿造读取逻辑了。我们常规使用的读取DB的方法(包括dump方式恢复), 都是通过执行SQL语句实现的,这牵涉到SQLite系统最复杂的子系统——SQL执行引擎。我们的恢复任务只需要遍历B-tree所有节点, 读出数据即可完成,不需要复杂的查询逻辑,因此最复杂的SQL引擎可以省略。同时,因为我们的系统是只读的, 写入恢复数据到新 DB 只要直接调用 SQLite 接口即可,因而可以省略同样比较复杂的B-tree平衡、Journal和同步等逻辑。 最后恢复用的最小系统只需要: VFS读取部分的接口(Open/Read/Close),或者直接用stdio的fopen/fread、Posix的open/read也可以 B-tree解析逻辑 Database File Format 详细描述了SQLite文件格式, 参照之实现B-tree解析可读取 SQLite DB。 实现了上面的逻辑,就能读出DB的数据进行恢复了,但还有一个小插曲。我们知道,使用SQLite查询一个表, 每一行的列数都是一致的,这是Schema层面保证的。但是在Schema的下面一层——B-tree层,没有这个保证。 B-tree的每一行(或者说每个entry、每个record)可以有不同的列数,一般来说,SQLite插入一行时, B-tree里面的列数和实际表的列数是一致的。但是当对一个表进行了ALTER TABLE ADD COLUMN操作, 整个表都增加了一列,但已经存在的B-tree行实际上没有做改动,还是维持原来的列数。 当SQLite查询到ALTER TABLE前的行,缺少的列会自动用默认值补全。恢复的时候,也需要做同样的判断和支持, 否则会出现缺列而无法插入到新的DB。 解析B-tree方案上线后,成功率约为78%。这个成功率计算方法为恢复成功的 Page 数除以总 Page 数。 由于是我们自己的系统,可以得知总 Page 数,使用恢复 Page 数比例的计算方法比人数更能反映真实情况。 B-tree解析好处是准备成本较低,不需要经常更新备份,对大部分表比较少的应用备份开销也小到几乎可以忽略, 成功恢复后能还原损坏时最新的数据,不受备份时限影响。 坏处是,和Dump一样,如果损坏到表的中间部分,比如非叶子节点,将导致后续数据无法读出。 落地实践: 剥离封装RepairKit: 从WCDB框架中,剥离修复组件,并且封装其C++的原始API为OC管理类。 备份 master 表的时机: 我们发现 SQLite 里面 B+树 算法的实现是 向下分裂 的,也就是说当一个叶子页满了需要分裂时,原来的叶子页会成为内部节点,然后新申请两个页作为他的叶子页。这就保证了根节点一旦下来,是再也不会变动的。master 表只会在新创建表或者删除一个表时才会发生变化,而CoreData的机制表明每一次数据库的变动都要改动版本标识,那么我通过缓存和查询版本标识的变动来确定何时进行备份,避免频繁备份。 备份文件有效性: 既然 DB 可以损坏,那么这个备份文件也会损坏,怎么办呢?我用了双备份,每一个版本备份两个文件,如果一个备份恢复失败,就会启动另一个备份文件恢复。 介入恢复时机: 当CoreData初始化SQLite前,校验SQLite的Head完整性,如果不完整,进行介入修复。 经过我深入研究证明了这已经是最佳做法。 本篇文章为转载内容。原文链接:https://blog.csdn.net/a66666225/article/details/81637368。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 18:22:40
128
转载
Netty
...靠的技术保障。 值得注意的是,尽管Netty具有诸多优势,但在实际部署过程中仍需谨慎规划。例如,在处理大规模数据流时,如何平衡内存使用与性能优化是一个重要的课题。此外,随着人工智能算法的不断演进,未来Netty或许还需要针对AI模型训练和推理任务进行专门适配,以满足新兴应用场景的需求。总之,Netty的未来充满无限可能,值得每一位开发者持续关注和探索。
2025-04-26 15:51:26
46
青山绿水
转载文章
...写好一个大概的模板,注意格式、内容以及邮件的标题等(例如XX大学-XXX-保研申请)。建议夏令营前或者初审过了及时联系自己喜欢的老师。 以上只是对各方面的简单介绍,每个方面详细的注意点网上好多资料,多多搜集就好。 PS:以上个人简历/个人陈述/老师推荐信模板如果有需要的私信我分享给你! 建议把以上材料都提前收集整理好,保研结束后发现我的材料文件夹3个多G...... 一年多来整理的保研资料 四、上科大信息学院夏令营(7.3-7.6) 本来没有打算报名上科大,一个同学把上科大宣传单给了我一份,看后感觉上科大实力比较强(虽然不是982/211)就报名了。 校园环境 上科大3号报到,4号-6号有开营活动、参观、自己联系老师面试(后来才知道即使拿到优营九月份也要再来面试,也就是说上科大夏令营拿到优营只是免去了九月预推免面试的初审,但是如果你足够优秀,老师比较中意,九月份就是来走一下过场。) 我参加了三个老师的面试。YY老师只是简单问了几个问题,有点水;HXM老师有一轮笔试(考的概率论比较多,编译原理、操作系统、计网也有涉及)+面试;YJY老师的一轮面试是课题组的学长学姐面的(自我介绍+项目),二轮面试和老师聊。 上科大给我的感觉就是学校小而精;老师比较好(比如YJY/GSH/TKW)、科研氛围浓厚、硬件设施完善(双人宿舍,独立卫浴,中央空调;学校地下全是停车场,下雨不用打伞可以直接走地下),但是由于建立才几年的时间,知名度不高。 学生宿舍 五、北理计算机夏令营(7.8-7.10) 北理今年入营的基本都是985和顶尖211,夏令营去了基本都能拿到优营!入营290+,夏令营参营240+,优营220+。 在北理主楼俯瞰 8号报到,领取宿舍钥匙、校园卡(北理夏令营包括食宿,每人发了一张100元的校园卡,可以在食堂、超市消费)。北理校园比较小、路比较窄;研究生宿舍三栋高层,有电梯,四人间,宿舍空间小、比较挤,大多数宿舍有空调(据说是宿舍的同学自己买或者租的),每一层有一个公共洗澡间。 9号上午宣讲,下午机试。机试两道题目难度不大,老师手动输入三个样例给分(4+3+3,每道题目满分10分)。下午机试结束我找到提前联系的LX老师聊了一个小时,老师人很nice,专心学术(据说她的研究生大都有一篇顶会论文)。 10号上午自己找老师面试。我又参加了院长实验室的面试,比较简单。下午正式面试,分了十多个组一起面试,总共四个小时。面试包括英文自我介绍、项目、研究生规划、是否打算读博、基础知识等,每人大概5-7分钟。面试结束就可以离校了。 六、北航计算机夏令营(7.11-7.14) 北航是不包含食宿的,所以入营人数较多,有600+。北航7.11上午报到+宣讲,下午机试分两组。北航机试类似CSP,可以多次提交,以最后一次为准,但是提交后不能实时出成绩。机试两个小时,包括两道题目,第一道题目比较简单,第二道题目稍微难一些,我第二道题目没有写完但是也过了机试,第二道题目即使没有写完也要能写多少写多少,把代码的思路写出来(有可能会人工判)。北航机试可以用CSP成绩代替,基本250分及以上就没问题,每年具体的情况不一样。11号晚上出机试通过名单(大概500+进340+)。 12号分组面试,每人20分钟,从上午八点一直面试到下午三点。面试包括抽取一道政治题谈看法、抽取一段英文读并翻译、基础知识(数学知识+计算机知识)、项目。政治题和英文翻译感觉大家都差不多(除非你英语特别差),主要的是基础知识面试,北航比较爱问数学问题线代、概率论、离散、高数;如果你的项目比较好的话,老师会着重问你的项目。问到我的问题有梯度、可微和可导、大数定理+中心极限定理等。12号晚上出优营名单,大概340+进180。北航是根据夏令营面试排名来定学硕和专硕的,大概有40个学硕的名额,其他都是专硕,不过北航学硕和专硕培养方式没有区别。 这是在我前面面试同学被问到的部分问题 13号领导师意向表,找导师签字,如果没有找到暑假期间或者九月份也可以再联系老师。 14号校医院体检,夏令营结束。 七、计算所(7.13-7.16) 计算所入营还是比较有难度的,但是即使没入营也可以自己联系老师,如果老师同意可以来参加面试,只是夏令营包括食宿,没入营的不包括食宿。计算所是分实验室面试的,可以参加多个实验室的面试,我参加了网数和智信的笔试+机试+面试。 智信12号笔试,14号机试+面试。笔试包括英文论文理解翻译、概率论题、计算机基础知识题目(操作系统,计网等)、CV题目(智信主要是做CV)。机试五道题目,一个小时,题目代码已经写好了,只需你补全,类似LeetCode,在学长的电脑上完成,有C++和Python可选,两种编程语言题目不同。C++用的是VS2017,会由人给你记每道题目完成的时间,会让你演示调试,结束后打包发送到一个邮箱里。 网数只有机试和面试,13号上午机试,15号面试。机试一个小时七道题目,在自己电脑上写然后拷到老师的优盘上。考察了包括链表、二叉树、图等,偏向于工程,据说今年的题目是计算所一个工程博士出的。机试70人,进入面试60人。面试每人15分钟,包括自我介绍,专业知识,是否读博,项目等。 计算所环境 八、一些建议和感想 一些建议: 提前准备,给自己定位,有针对性的准备,多在网上找经验贴;多和本校保研的学长学姐交流,多和同学交流,多搜集信息; 4月份前把简历、推荐信、个人陈述等写好,再不断修改完善; 最好能提前联系一个老师,以免拿到优营而没有找到好老师; 准备好专业知识,线代、概率论、数据结构、计网、计组、操作系统等; 如果编程能力不是特别强,最好大三开始就刷题,LeetCode的中档题难度基本就够用了; 一些体会与感想: 机会是留给有准备的人的,越努力越幸运! 做最坏的打算,做最好的准备。 保研是一场马拉松,坚持到底就是胜利。 遵道而行,但到半途需努力;会心不远,欲登绝顶莫辞劳。 也送给自己一句话:流年笑掷,未来可期! 以上仅代表个人观点与感想,如果对你有帮助记得点赞哦~如有问题,可以关注我的公主号【驭风者小窝】,我会尽我最大的努力帮助你! 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_28983299/article/details/118319985。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-02 23:03:36
122
转载
转载文章
...描整个集合。 不过要注意,epoll 是在 Linux 2.6 中才新增的功能(2.4 虽然也有,但功能不完善)。由于边缘触发只在文件描述符可读或可写事件发生时才通知,那么应用程序就需要尽可能多地执行 I/O,并要处理更多的异常事件。 第三种,使用异步 I/O(Asynchronous I/O,简称为 AIO)。 在前面文件系统原理的内容中,我曾介绍过异步 I/O 与同步 I/O 的区别。异步 I/O 允许应用程序同时发起很多 I/O 操作,而不用等待这些操作完成。而在 I/O 完成后,系统会用事件通知(比如信号或者回调函数)的方式,告诉应用程序。这时,应用程序才会去查询 I/O 操作的结果。 异步 I/O 也是到了 Linux 2.6 才支持的功能,并且在很长时间里都处于不完善的状态,比如 glibc 提供的异步 I/O 库,就一直被社区诟病。同时,由于异步 I/O 跟我们的直观逻辑不太一样,想要使用的话,一定要小心设计,其使用难度比较高。 工作模型优化 了解了 I/O 模型后,请求处理的优化就比较直观了。 使用 I/O 多路复用后,就可以在一个进程或线程中处理多个请求,其中,又有下面两种不同的工作模型。 第一种,主进程 + 多个 worker 子进程,这也是最常用的一种模型。这种方法的一个通用工作模式就是:主进程执行 bind() + listen() 后,创建多个子进程;然后,在每个子进程中,都通过 accept() 或 epoll_wait() ,来处理相同的套接字。 比如,最常用的反向代理服务器 Nginx 就是这么工作的。它也是由主进程和多个 worker 进程组成。主进程主要用来初始化套接字,并管理子进程的生命周期;而 worker 进程,则负责实际的请求处理。我画了一张图来表示这个关系。 这里要注意,accept() 和 epoll_wait() 调用,还存在一个惊群的问题。换句话说,当网络 I/O 事件发生时,多个进程被同时唤醒,但实际上只有一个进程来响应这个事件,其他被唤醒的进程都会重新休眠。 其中,accept() 的惊群问题,已经在 Linux 2.6 中解决了; 而 epoll 的问题,到了 Linux 4.5 ,才通过 EPOLLEXCLUSIVE 解决。 为了避免惊群问题, Nginx 在每个 worker 进程中,都增加一个了全局锁(accept_mutex)。这些 worker 进程需要首先竞争到锁,只有竞争到锁的进程,才会加入到 epoll 中,这样就确保只有一个 worker 子进程被唤醒。 不过,根据前面 CPU 模块的学习,你应该还记得,进程的管理、调度、上下文切换的成本非常高。那为什么使用多进程模式的 Nginx ,却具有非常好的性能呢? 这里最主要的一个原因就是,这些 worker 进程,实际上并不需要经常创建和销毁,而是在没任务时休眠,有任务时唤醒。只有在 worker 由于某些异常退出时,主进程才需要创建新的进程来代替它。 当然,你也可以用线程代替进程:主线程负责套接字初始化和子线程状态的管理,而子线程则负责实际的请求处理。由于线程的调度和切换成本比较低,实际上你可以进一步把 epoll_wait() 都放到主线程中,保证每次事件都只唤醒主线程,而子线程只需要负责后续的请求处理。 第二种,监听到相同端口的多进程模型。在这种方式下,所有的进程都监听相同的接口,并且开启 SO_REUSEPORT 选项,由内核负责将请求负载均衡到这些监听进程中去。这一过程如下图所示。 由于内核确保了只有一个进程被唤醒,就不会出现惊群问题了。比如,Nginx 在 1.9.1 中就已经支持了这种模式。 不过要注意,想要使用 SO_REUSEPORT 选项,需要用 Linux 3.9 以上的版本才可以。 C1000K 基于 I/O 多路复用和请求处理的优化,C10K 问题很容易就可以解决。不过,随着摩尔定律带来的服务器性能提升,以及互联网的普及,你并不难想到,新兴服务会对性能提出更高的要求。 很快,原来的 C10K 已经不能满足需求,所以又有了 C100K 和 C1000K,也就是并发从原来的 1 万增加到 10 万、乃至 100 万。从 1 万到 10 万,其实还是基于 C10K 的这些理论,epoll 配合线程池,再加上 CPU、内存和网络接口的性能和容量提升。大部分情况下,C100K 很自然就可以达到。 那么,再进一步,C1000K 是不是也可以很容易就实现呢?这其实没有那么简单了。 首先从物理资源使用上来说,100 万个请求需要大量的系统资源。比如, 假设每个请求需要 16KB 内存的话,那么总共就需要大约 15 GB 内存。 而从带宽上来说,假设只有 20% 活跃连接,即使每个连接只需要 1KB/s 的吞吐量,总共也需要 1.6 Gb/s 的吞吐量。千兆网卡显然满足不了这么大的吞吐量,所以还需要配置万兆网卡,或者基于多网卡 Bonding 承载更大的吞吐量。 其次,从软件资源上来说,大量的连接也会占用大量的软件资源,比如文件描述符的数量、连接状态的跟踪(CONNTRACK)、网络协议栈的缓存大小(比如套接字读写缓存、TCP 读写缓存)等等。 最后,大量请求带来的中断处理,也会带来非常高的处理成本。这样,就需要多队列网卡、中断负载均衡、CPU 绑定、RPS/RFS(软中断负载均衡到多个 CPU 核上),以及将网络包的处理卸载(Offload)到网络设备(如 TSO/GSO、LRO/GRO、VXLAN OFFLOAD)等各种硬件和软件的优化。 C1000K 的解决方法,本质上还是构建在 epoll 的非阻塞 I/O 模型上。只不过,除了 I/O 模型之外,还需要从应用程序到 Linux 内核、再到 CPU、内存和网络等各个层次的深度优化,特别是需要借助硬件,来卸载那些原来通过软件处理的大量功能。 C10M 显然,人们对于性能的要求是无止境的。再进一步,有没有可能在单机中,同时处理 1000 万的请求呢?这也就是 C10M 问题。 实际上,在 C1000K 问题中,各种软件、硬件的优化很可能都已经做到头了。特别是当升级完硬件(比如足够多的内存、带宽足够大的网卡、更多的网络功能卸载等)后,你可能会发现,无论你怎么优化应用程序和内核中的各种网络参数,想实现 1000 万请求的并发,都是极其困难的。 究其根本,还是 Linux 内核协议栈做了太多太繁重的工作。从网卡中断带来的硬中断处理程序开始,到软中断中的各层网络协议处理,最后再到应用程序,这个路径实在是太长了,就会导致网络包的处理优化,到了一定程度后,就无法更进一步了。 要解决这个问题,最重要就是跳过内核协议栈的冗长路径,把网络包直接送到要处理的应用程序那里去。这里有两种常见的机制,DPDK 和 XDP。 第一种机制,DPDK,是用户态网络的标准。它跳过内核协议栈,直接由用户态进程通过轮询的方式,来处理网络接收。 说起轮询,你肯定会下意识认为它是低效的象征,但是进一步反问下自己,它的低效主要体现在哪里呢?是查询时间明显多于实际工作时间的情况下吧!那么,换个角度来想,如果每时每刻都有新的网络包需要处理,轮询的优势就很明显了。比如: 在 PPS 非常高的场景中,查询时间比实际工作时间少了很多,绝大部分时间都在处理网络包; 而跳过内核协议栈后,就省去了繁杂的硬中断、软中断再到 Linux 网络协议栈逐层处理的过程,应用程序可以针对应用的实际场景,有针对性地优化网络包的处理逻辑,而不需要关注所有的细节。 此外,DPDK 还通过大页、CPU 绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率。 第二种机制,XDP(eXpress Data Path),则是 Linux 内核提供的一种高性能网络数据路径。它允许网络包,在进入内核协议栈之前,就进行处理,也可以带来更高的性能。XDP 底层跟我们之前用到的 bcc-tools 一样,都是基于 Linux 内核的 eBPF 机制实现的。 XDP 的原理如下图所示: 你可以看到,XDP 对内核的要求比较高,需要的是 Linux 4.8 以上版本,并且它也不提供缓存队列。基于 XDP 的应用程序通常是专用的网络应用,常见的有 IDS(入侵检测系统)、DDoS 防御、 cilium 容器网络插件等。 总结 C10K 问题的根源,一方面在于系统有限的资源;另一方面,也是更重要的因素,是同步阻塞的 I/O 模型以及轮询的套接字接口,限制了网络事件的处理效率。Linux 2.6 中引入的 epoll ,完美解决了 C10K 的问题,现在的高性能网络方案都基于 epoll。 从 C10K 到 C100K ,可能只需要增加系统的物理资源就可以满足;但从 C100K 到 C1000K ,就不仅仅是增加物理资源就能解决的问题了。这时,就需要多方面的优化工作了,从硬件的中断处理和网络功能卸载、到网络协议栈的文件描述符数量、连接状态跟踪、缓存队列等内核的优化,再到应用程序的工作模型优化,都是考虑的重点。 再进一步,要实现 C10M ,就不只是增加物理资源,或者优化内核和应用程序可以解决的问题了。这时候,就需要用 XDP 的方式,在内核协议栈之前处理网络包;或者用 DPDK 直接跳过网络协议栈,在用户空间通过轮询的方式直接处理网络包。 当然了,实际上,在大多数场景中,我们并不需要单机并发 1000 万的请求。通过调整系统架构,把这些请求分发到多台服务器中来处理,通常是更简单和更容易扩展的方案。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_23864697/article/details/114626793。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-11 18:25:52
261
转载
转载文章
... -D,这里我们需要注意的是prettier与eslint冲突问题; 上面是配置时候的基本流程,最终结果我将eslintrc文件及package.json文件放到这里,有需要的朋友,可以直接copy一份去配置,毕竟这个配置很臭很长,深入学习感觉又没有太大必要(23333~) {"name": "vue-tsx-template","private": true,"version": "0.0.0","scripts": {"dev": "vite","build": "vue-tsc --noEmit && vite build","preview": "vite preview","fix": "eslint --fix --ext .js,.jsx,.tsx,.vue src && prettier "},"dependencies": {"vue": "^3.2.25"},"devDependencies": {"@typescript-eslint/eslint-plugin": "^5.23.0","@typescript-eslint/parser": "^5.23.0","@vitejs/plugin-vue": "^2.3.3","@vitejs/plugin-vue-jsx": "^1.3.10","autoprefixer": "^10.4.7","eslint": "^8.15.0","eslint-config-airbnb-base": "^15.0.0","eslint-config-prettier": "^8.5.0","eslint-plugin-import": "^2.26.0","eslint-plugin-prettier": "^4.0.0","eslint-plugin-vue": "^8.7.1","postcss": "^8.4.13","prettier": "^2.6.2","sass": "^1.51.0","tailwindcss": "^3.0.24","typescript": "^4.5.4","vite": "^2.9.9","vue-eslint-parser": "^9.0.1","vue-tsc": "^0.34.7"} } 下面是.eslintrc.js文件 module.exports = {env: {browser: true,es2021: true,node: true,// 处理 defineProps 报错'vue/setup-compiler-macros': true,},extends: ['eslint:recommended','airbnb-base','prettier','plugin:prettier/recommended','plugin:vue/vue3-recommended','plugin:@typescript-eslint/recommended','plugin:import/recommended','plugin:import/typescript',],parser: 'vue-eslint-parser',parserOptions: {ecmaVersion: 'latest',parser: '@typescript-eslint/parser',sourceType: 'module',},plugins: ['vue', '@typescript-eslint'],rules: {// 防止prettier与eslint冲突'prettier/prettier': 'error',// eslint-plugin-import es module导入eslint规则配置,旨在规避拼写错误问题'import/no-unresolved': 0,'import/extensions': ['error',{js: 'never',jsx: 'never',ts: 'never',tsx: 'never',json: 'always',},],// 使用导出的名称作为默认属性(主要用作导出模块内部有 default, 和直接导出两种并存情况下,会出现default.proptry 这种问题从在的情况)'import/no-named-as-default-member': 0,'import/order': ['error', { 'newlines-between': 'always' }],// 导入确保是否在首位'import/first': 0,// 如果文件只有一个导出,是否开启强制默认导出'import/prefer-default-export': 0,'import/no-extraneous-dependencies': ['error',{devDependencies: [],optionalDependencies: false,},],/ 关于typescript语法校验 参考文档: https://www.npmjs.com/package/@typescript-eslint/eslint-plugin/'@typescript-eslint/no-extra-semi': 0,// 是否禁止使用any类型'@typescript-eslint/no-explicit-any': 0,// 是否对于null情况做非空断言'@typescript-eslint/no-non-null-assertion': 0,// 是否对返回值类型进行定义校验'@typescript-eslint/explicit-function-return-type': 0,'@typescript-eslint/member-delimiter-style': ['error', { multiline: { delimiter: 'none' } }],// 结合eslint 'no-use-before-define': 'off',不然会有报错,需要关闭eslint这个校验,主要是增加了对于type\interface\enum'no-use-before-define': 'off','@typescript-eslint/no-use-before-define': ['error'],'@typescript-eslint/explicit-module-boundary-types': 'off','@typescript-eslint/no-unused-vars': ['error',{ignoreRestSiblings: true,varsIgnorePattern: '^_',argsIgnorePattern: '^_',},],'@typescript-eslint/explicit-member-accessibility': ['error', { overrides: { constructors: 'no-public' } }],'@typescript-eslint/consistent-type-imports': 'error','@typescript-eslint/indent': 0,'@typescript-eslint/naming-convention': ['error',{selector: 'interface',format: ['PascalCase'],},],// 不允许使用 var'no-var': 'error',// 如果没有修改值,有些用const定义'prefer-const': ['error',{destructuring: 'any',ignoreReadBeforeAssign: false,},],// 关于vue3 的一些语法糖校验// 超过 4 个属性换行展示'vue/max-attributes-per-line': ['error',{singleline: 4,},],// setup 语法糖校验'vue/script-setup-uses-vars': 'error',// 关于箭头函数'vue/arrow-spacing': 'error','vue/html-indent': 'off',},} 4、加入单元测试 单元测试,根据自己项目体量及重要性而去考虑是否要增加,当然单测可以反推一些组件 or 方法的设计是否合理,同样如果是一个稳定的功能在加上单元测试,这就是一个很nice的体验; 我们单元测试是基于jest来去做的,具体安装单测的办法如下,跟着我的步骤一步步来; 安装jest单测相关的依赖组件库 pnpm add @testing-library/vue @testing-library/user-event @testing-library/jest-dom @types/jest jest @vue/test-utils -D 安装完成后,发现还需要安装前置依赖 @testing-library/dom @vue/compiler-sfc我们继续补充 安装babel相关工具,用ts写的单元测试需要转义,具体安装工具如下pnpm add @babel/core babel-jest @vue/babel-preset-app -D,最后我们配置babel.config.js module.exports = {presets: ['@vue/app'],} 配置jest.config.js module.exports = {roots: ['<rootDir>/test'],testMatch: [// 这里我们支持src目录里面增加一些单层,事实上我并不喜欢这样做'<rootDir>/src//__tests__//.{js,jsx,ts,tsx}','<rootDir>/src//.{spec,test}.{js,jsx,ts,tsx}',// 这里我习惯将单层文件统一放在test单独目录下,不在项目中使用,降低单测文件与业务组件模块混合在一起'<rootDir>/test//.{spec,test}.{js,jsx,ts,tsx}',],testEnvironment: 'jsdom',transform: {// 此处我们单测没有适用vue-jest方式,项目中我们江永tsx方式来开发,所以我们如果需要加入其它的内容// '^.+\\.(vue)$': '<rootDir>/node_modules/vue-jest','^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': '<rootDir>/node_modules/babel-jest',},transformIgnorePatterns: ['<rootDir>/node_modules/','[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$','^.+\\.module\\.(css|sass|scss|less)$',],moduleFileExtensions: ['ts', 'tsx', 'vue', 'js', 'jsx', 'json', 'node'],resetMocks: true,} 具体写单元测试的方法,可以参考项目模板中的组件单元测试写法,这里不做过多的说明; 5、封装axios请求库 这里呢其实思路有很多种,如果有自己的习惯的封装方式,就按照自己的思路,下面附上我的封装代码,简短的说一下我的封装思路: 1、基础的请求拦截、相应拦截封装,这个是对于一些请求参数格式化处理等,或者返回值情况处理 2、请求异常、错误、接口调用成功返回结果错误这些错误的集中处理,代码中请求就不再做trycatch这些操作 3、请求函数统一封装(代码中的 get、post、axiosHttp) 4、泛型方式定义请求返回参数,定义好类型,让我们可以在不同地方使用有良好的提示 import type { AxiosRequestConfig, AxiosResponse } from 'axios'import axios from 'axios'import { ElNotification } from 'element-plus'import errorHandle from './errorHandle'// 定义数据返回结构体(此处我简单定义一个比较常见的后端数据返回结构体,实际使用我们需要按照自己所在的项目开发)interface ResponseData<T = null> {code: string | numberdata: Tsuccess: booleanmessage?: string[key: string]: any}const axiosInstance = axios.create()// 设定响应超时时间axiosInstance.defaults.timeout = 30000// 可以后续根据自己http请求头特殊邀请设定请求头axiosInstance.interceptors.request.use((req: AxiosRequestConfig<any>) => {// 特殊处理,后续如果项目中有全局通传参数,可以在这儿做一些处理return req},error => Promise.reject(error),)// 响应拦截axiosInstance.interceptors.response.use((res: AxiosResponse<any, any>) => {// 数组处理return res},error => Promise.reject(error),)// 通用的请求方法体const axiosHttp = async <T extends Record<string, any> | null>(config: AxiosRequestConfig,desc: string,): Promise<T> => {try {const { data } = await axiosInstance.request<ResponseData<T>>(config)if (data.success) {return data.data}// 如果请求失败统一做提示(此处我没有安装组件库,我简单写个mock例子)ElNotification({title: desc,message: ${data.message || '请求失败,请检查'},})} catch (e: any) {// 统一的错误处理if (e.response && e.response.status) {errorHandle(e.response.status, desc)} else {ElNotification({title: desc,message: '接口异常,请检查',})} }return null as T}// get请求方法封装export const get = async <T = Record<string, any> | null>(url: string, params: Record<string, any>, desc: string) => {const config: AxiosRequestConfig = {method: 'get',url,params,}const data = await axiosHttp<T>(config, desc)return data}// Post请求方法export const post = async <T = Record<string, any> | null>(url: string, data: Record<string, any>, desc: string) => {const config: AxiosRequestConfig = {method: 'post',url,data,}const info = await axiosHttp<T>(config, desc)return info} 请求错误(状态码错误相关提示) import { ElNotification } from 'element-plus'function notificat(message: string, title: string) {ElNotification({title,message,})}/ @description 获取接口定义 @param status {number} 错误状态码 @param desc {string} 接口描述信息/export default function errorHandle(status: number, desc: string) {switch (status) {case 401:notificat('用户登录失败', desc)breakcase 404:notificat('请求不存在', desc)breakcase 500:notificat('服务器错误,请检查服务器', desc)breakdefault:notificat(其他错误${status}, desc)break} } 6、关于vue-router 及 pinia 这两个相对来讲简单一些,会使用vuex状态管理,上手pinia也是很轻松的事儿,只是更简单化了、更方便了,可以参考模板项目里面的用法example,这里附上router及pinia配置方法,路由守卫,大家可以根据项目的要求再添加 import type { RouteRecordRaw } from 'vue-router'import { createRouter, createWebHistory } from 'vue-router'// 配置路由const routes: Array<RouteRecordRaw> = [{path: '/',redirect: '/home',},{name: 'home',path: '/home',component: () => import('page/Home'),},]const router = createRouter({routes,history: createWebHistory(),})export default router 针对与pinia,参考如下: import { createPinia } from 'pinia'export default createPinia() 在入口文件将router和store注入进去 import { createApp } from 'vue'import App from './App'import store from './store/index'import './style/index.css'import './style/index.scss'import 'element-plus/dist/index.css'import router from './router'// 注入全局的storeconst app = createApp(App).use(store).use(router)app.mount('app') 说这些比较枯燥,建议大家去github参考项目说明文档,下载项目,自己过一遍,喜欢的朋友收藏点赞一下,如果喜欢我构建好的项目给个star不丢失,谢谢各位看官的支持。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_37764929/article/details/124860873。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-05 12:27:41
117
转载
转载文章
...用【免费AWS套餐】注意平时的理解和学习 再进行高级实验 需要了解各个服务之间的关联等,【听取讲师指导课程】,就可以高层次的了解服务内容 参加认证考试 2.4 AWS导师课程分类和级别 人员分类:解决方案师、开发人员、系统操作人员 课程分类:入门级、基础级、高级、专项 3. AWS认证的背景信息 3.1 认证的类型 助理级 – 助理架构师 – 助理开发人员 – 助理系统管理员 专家级 – 专家架构师 – 专家开发运维 认证共有5个,如果要参加专家级认证必须先通过助理级认证,其中“专家开发运维(devops)”的认证则通过任意(开发 or 运维)的助理级认证即可 3.2 获得认证后的收益? 对个人 – 可以证明个人在AWS平台上具备设计、部署和管理高可用、低成本、安全应用的能力 – 在工作上或社区中得到尊重和认可 – 可以把认证放到简历中,linkedin中整合了AWS认证徽章 对企业雇主 – 具备AWS上服务和工具的使用的认可 – 客户认可,降低AWS项目实施风险 – 增加客户满意度 3.3 再认证模式 因为AWS的服务在更新,因此每两年要重新认证(证件的有效期2年),再次参加考试时,题目、时间将会更少,且认证费用更低 3.4 助理架构师认证的知识领域 四大知识域 1 设计:高可用、高效率、可容错低、可扩展的系统 2 实施和部署:强调部署操作能力 3 数据安全性:在部署操作时,始终保持数据保存和传输的安全 4 排除故障:在系统出现问题时,可以快速找到问题并解决问题 知识权重 - 设计:60%的题目 - 实施和部署:10%的题目 - 数据安全:20%的题目 - 排除故障:10%的题目 PS:考试不会按照上面的次序、考试不会注明考试题目的分类 3.5 认证过程 需要在网上注册,找到距离家里比较近的地方考试(考点) 到了现场需要携带身份证,证明自己 并不允许带手机入场 证件上必须有照片 签署NDA保证不会泄露考题 考试中心的电脑中考试(80分钟,55个考题) 考试后马上知道分数和是否通过(不会看到每道题目是否正确) 通过后的成绩、认证证书等将发到email邮箱中 3.6 考试机制 助理级别考试的重点是:单一服务和小规模的组合服务的掌握程度 所有题目都是选择题(多选或单选) 不惩罚打错,所以留白没意义,可以猜一个 55道题 可以给不确定的题目打标签,没提交前都可以回来改答案 3.7 题目示例 单选题 多选题(会告诉你有多少个答案) 汇总查看答案以及mark(标记) 4 AWS架构的7大设计原则 4.1 松耦合 松耦合是容错、运维自动扩容的基础,在设计上应该尽量减少模块间的依赖性,将不会成为未来应用调整、发展的阻碍 松耦合模式的情况 不要标示(依赖)特定对象,依赖特定对象耦合性将非常高 – 使用负载均衡器 – 域名解析 – 弹性IP – 可以动态找到配合的对象,为松耦合带来方便,为应用将来的扩展带来好处 不要依赖其他模块的正确处理或及时的处理 – 使用尽量使用异步的处理,而不是同步的(SQS可以帮到用户) 4.2 模块出错后工作不会有问题 问问某个模块出了问题,应用会怎么样? 在设计的时候,在出了问题会有影响的模块,进行处理,建立自动恢复性 4.3 实现弹性 在设计上,不要假定模块是正常的、始终不变的 – 可以配合AutoScaling、EIP和可用区AZ来满足 允许模块的失败重启 – 无状态设计比有状态设计好 – 使用ELB、云监控去检测“实例”运行状态 有引导参数的实例(实现自动配置) – 例如:加入user data在启动的时候,告知它应该做的事情 在关闭实例的时候,保存其配置和个性化 – 例如用DynamoDB保存session信息 弹性后就不会为了超配资源而浪费钱了 4.4 安全是整体的事,需要在每个层面综合考虑 基础架构层 计算/网络架构层 数据层 应用层 4.5 最小授权原则 只付于操作者完成工作的必要权限 所有用户的操作必须授权 三种类型的权限能操作AWS – 主账户 – IAM用户 – 授权服务(主要是开发的app) 5 设计:高可用、高效率、可容错、可扩展的系统 本部分的目标是设计出高可用、高效率低成本、可容错、可扩展的系统架构 - 高可用 – 了解AWS服务自身的高可靠性(例如弹性负载均衡)—-因为ELB是可以多AZ部署的 – 用好这些服务可以减少可用性的后顾之忧 - 高效率(低成本) – 了解自己的容量需求,避免超额分配 – 利用不同的价格策略,例如:使用预留实例 – 尽量使用AWS的托管服务(如SNS、SQS) - 可容错 – 了解HA和容错的区别 – 如果说HA是结果,那么容错则是保障HA的一个重要策略 – HA强调系统不要出问题,而容错是在系统出了问题后尽量不要影响业务 - 可扩展性 – 需要了解AWS哪些服务自身就可以扩展,例如SQS、ELB – 了解自动伸缩组(AS) 运用好 AWS 7大架构设计原则的:松耦合、实现弹性 6 实施和部署设计 本部分的在设计的基础上找到合适的工具来实现 对比第一部分“设计”,第一章主要针对用什么,而第二章则讨论怎么用 主要考核AWS云的核心的服务目录和核心服务,包括: 计算机和网络 – EC2、VPC 存储和内容分发 – S3、Glacier 数据库相关分类 – RDS 部署和管理服务 – CloudFormation、CloudWatch、IAM 应用服务 – SQS、SNS 7 数据安全 数据安全的基础,是AWS责任共担的安全模型模型,必须要读懂 数据安全包括4个层面:基础设施层、计算/网络层、数据层、应用层 - 基础设施层 1. 基础硬件安全 2. 授权访问、流程等 - 计算/网络层 1. 主要靠VPC保障网络(防护、路由、网络隔离、易管理) 2. 认识安全组和NACLs以及他们的差别 安全组比ACL多一点,安全组可以针对其他安全组,ACL只能针对IP 安全组只允许统一,ACL可以设置拒绝 安全组有状态!很重要(只要一条入站规则通过,那么出站也可以自动通过),ACL没有状态(必须分别指定出站、入站规则) 安全组的工作的对象是网卡(实例)、ACL工作的对象是子网 认识4种网关,以及他们的差别 共有4种网关,支撑流量进出VPC internet gatway:互联网的访问 virtual private gateway:负责VPN的访问 direct connect:负责企业直连网络的访问 vpc peering:负责VPC的peering的访问 数据层 数据传输安全 – 进入和出AWS的安全 – AWS内部传输安全 通过https访问API 链路的安全 – 通过SSL访问web – 通过IP加密访问VPN – 使用直连 – 使用OFFLINE的导入导出 数据的持久化保存 – 使用EBS – 使用S3访问 访问 – 使用IAM策略 – 使用bucket策略 – 访问控制列表 临时授权 – 使用签名的URL 加密 – 服务器端加密 – 客户端加密 应用层 主要强调的是共担风险模型 多种类型的认证鉴权 给用户在应用层的保障建议 – 选择一种认证鉴权机制(而不要不鉴权) – 用安全的密码和强安全策略 – 保护你的OS(如打开防火墙) – 用强壮的角色来控制权限(RBAC) 判断AWS和用户分担的安全中的标志是,哪些是AWS可以控制的,那些不能,能的就是AWS负责,否则就是用户(举个例子:安全组的功能由AWS负责—是否生效,但是如何使用是用户负责—自己开放所有端口跟AWS无关) AWS可以保障的 用户需要保障的 工具与服务 操作系统 物理内部流程安全 应用程序 物理基础设施 安全组 网络设施 虚拟化设施 OS防火墙 网络规则 管理账号 8 故障排除 问题经常包括的类型: - EC2实例的连接性问题 - 恢复EC2实例或EBS卷上的数据 - 服务使用限制问题 8.1 EC2实例的连接性问题 经常会有多个原因造成无法连接 外部VPC到内部VPC的实例 – 网关(IGW–internet网关、VPG–虚拟私有网关)的添加问题 – 公司网络到VPC的路由规则设置问题 – VPC各个子网间的路由表问题 – 弹性IP和公有IP的问题 – NACLs(网络访问规则) – 安全组 – OS层面的防火墙 8.2 恢复EC2实例或EBS卷上的数据 注意EBS或EC2没有任何强绑定关系 – EBS是可以从旧实例上分离的 – 如有必要尽快做 将EBS卷挂载到新的、健康的实例上 执行流程可以针对恢复没有工作的启动卷(boot volume) – 将root卷分离出来 – 像数据一样挂载到其他实例 – 修复文件 – 重新挂载到原来的实例中重新启动 8.3 服务使用限制问题 AWS有很多软性限制 – 例如AWS初始化的时候,每个类型的EBS实例最多启动20个 还有一些硬性限制例如 – 每个账号最多拥有100个S3的bucket – …… 别的服务限制了当前服务 – 例如无法启动新EC2实例,原因可能是EBS卷达到上限 – Trusted Advisor这个工具可以根据服务水平的不同给出你一些限制的参考(从免费试用,到商业试用,和企业试用的建议) 常见的软性限制 公共的限制 – 每个用户最多创建20个实例,或更少的实例类型 – 每个区域最多5个弹性ip – 每个vpc最多100个安全组 – 最多20个负载均衡 – 最多20个自动伸缩组 – 5000个EBS卷、10000个快照,4w的IOPS和总共20TB的磁盘 – …更多则需要申请了 你不需要记住限制 – 知道限制,并保持数值敏感度就好 – 日后遇到问题时可以排除掉软限制的相关的问题 9. 总结 9.1 认证的主要目标是: 确认架构师能否搜集需求,并且使用最佳实践,在AWS中构建出这个系统 是否能为应用的整个生命周期给出指导意见 9.2 希望架构师(助理或专家级)考试前的准备: 深度掌握至少1门高级别语言(c,c++,java等) 掌握AWS的三份白皮书 – aws概览 – aws安全流程 – aws风险和应对 – 云中的存储选项 – aws的架构最佳实践 按照客户需求,使用AWS组件来部署混合系统的经验 使用AWS架构中心网站了解更多信息 9.3 经验方面的建议 助理架构师 – 至少6个月的实际操作经验、在AWS中管理生产系统的经验 – 学习过AWS的基本课程 专家架构师 – 至少2年的实际操作经验、在AWS中管理多种不同种类的复杂生产系统的经验(多种服务、动态伸缩、高可用、重构或容错) – 在AWS中执行构建的能力,架构的高级概念能力 9.4 相关资源 认证学习的资源地址 - 可以自己练习,模拟考试需要付费的 接下来就去网上报名参加考试 本篇文章为转载内容。原文链接:https://blog.csdn.net/QXK2001/article/details/51292402。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-29 22:08:40
271
转载
转载文章
... 例如: : 最后要注意group by 后面的字段与所查字段的关系(一对一),当然还有having,having和where基本一样,只不过跟在group by后面. 10.Limit 分页查询 limit 0,5 .查询前5条数据,从0开始,5结束,但是5取不到,也即是取头不取尾. 11.聚合函数:count() 查询数据的总数据量 经常使用别名 例如:as total sum(字段)函数:求和…若字段为成绩,where条件或gruop by 为个人的id,那么查出的就是个人的成绩总分. AVG(字段),但是查的是平均分,min(字段)与max(字段) 查出最小或最大. 三者都类似sum(),当然max()与min()若是在最前面使用,就会当条件查询只会出来这一笔数据.例如: 12.Sql多表查询,内连接不只是inner join,平时写的from a表,b表 where 条件这也是内连接,意思就是两张表中数据都有才可以查询出来 13.而外连接分为左连接和右连接,意思是以左表或右表为主,假如两张表,左表数据多,右表数据少,且条件符合,则左连接的时候左表数据全部出来,右表没有的为null,反之也是一样. 14.Exist() 与 not exist() …()内的数据是否为空,若是为空则代表false,返回数据为空,若不为空,则代表true,正常查询. 15.Any 与 all 例如 age > any(age1,age2) 大于两者中的一个就可以,但是all的情况下则是全部大于.也就是相当于,any为大于最小的,all则是大于最大的就行了,当然若是小于号那就是另外一种情况了,另外分析. 16.Union,(也就是联合的意思,自带distinct,重复的去除)用法,例如两张表的id要全部查出来,则:select id from A union select id from B ,若Aid为1,2,3,Bid为1,2,4.则查出来的数据为1.2.3.4,若是union all,则不带distinct,用法一样,查出来以后为1.2.3.1.2.4. 17.给表取别名,表名 空格 别名 给字段取别名 字段名 as 别名. 18.Insert插入数据时若是使用insert into 表名 values();主键必须到写进去,当然与其他数据不相同即可,若是自增,可以写null.若是insert into 表名(字段)values(值),这时插入数据,字段不用写主键字段,写入其他数据字段名与值就可以完成数据的添加.(主键自己生成为前提,UUID,auto_increament都可以). 19.Insert into 插入多条数据时,其他与18一样,只不过由values()变成了values(),(),(); 20.索引是由数据库表中一列或多列组合而成,其作用提高对表数据的查询速度.像图书目录. 优缺点:优:提高了查询数据的效率.缺:创建和维护索引的时间增加了(内容改了,目录也要改). 21.索引分类:普通索引,唯一性索引UNIQUE(unique修饰,例如主键),全文索引FULLTEXT(创建在文本上,例如:char,varchar,varchar2等,mysql默认引擎不支持,),单列索引:单个字段建立索引,多列索引:多个字段创建一个索引,空间索引SPATIAL:不常用(mysql默认引擎不支持) 22.创建索引: index为关键字,或者key (1)可以index(字段名)–>普通索引 (2)Unique index(字段名)–>唯一索引 (3)Unique index 别名(字段名)–>取别名的唯一索引 (4)index 别名(字段名1,字段名2)–>取别名的多列索引 1.创建表的时候创建索引, 前三个为参数修饰,唯一性,全文,空间索引; 2.在已存在的表上创建索引,或者用ALTER TABLE 表名 ADD 索引,也就是用修改表的形式来创建索引 Create index 索引别名 on 表名(字段名) -->普通单列索引 Create index 索引别名 on 表名(字段名1,字段名2) -->多列索引 Create unique index 索引别名 on 表名(字段名) -->唯一单列索引 Alter table 表名 add +(1)|(2)|(3)|(4)即可. 23.删除索引: drop index 索引名 on 表名. 24.NOW(); mysql的函数,表示当前时间 25.视图:是一个虚拟的表,没有物理数据,是从其他表中导出的数据,当原表数据发生改变时,视图数据也会发生改变,反之也一样. (1)作用:操作简单化;增加数据安全性:不直接对表进行操作;提高表的逻辑性:原表修改字段对视图无影响. (2)创建视图:语法:create view 视图名 as 查询语句. 例如:create view vi as select id,name from user;–>这是把user中id,name字段的数据写入到vi视图中. 若是想自己定义字段名不用查出的字段名,可以如下面这样写. 例如:create view vi(vi_id,vi_name) as select id,name from user;–>这样的话id对应vi_id,name对应vi_name; 上面的都是单表的视图,多表的视图也是一样的,只不过后面的单表查询变成多表查询了. 建议创建视图后自己定义字段名,也即是定义别名. (3)查看视图: Describe(desc) 视图名–>查看视图基本信息 Show table status like ‘视图名’ --> 查看视图基本信息 Show create view 视图名 --> 视图详细信息,建表具体信息. 在view表中查看视图详细信息–>view 系统表 自带的. (4)修改视图:修改使徒的定义 Create or replace view 没有的话就创建,有的话就替换 例如:Create or replace view vi(id,name) as select语句. Alter view 只修改不能创建(也就是说视图必须存在的情况下才可修改) Alter view vi as select语句 (5)更新视图:视图是虚拟的,对视图进行的crud操作都会对原表的数据产生影响. 也就是说对视图的操作最后都会转换为对视图所连接那个表的操作. (6)删除视图:删除数据库中已存在的视图,视图为虚表,因此只会删除结构,不会删除数据. Drop view if exist 视图名. 26.触发器:由事件来触发某个操作,这些事件包括insert语句,update语句和delete语句.当数据库系统执行这些事件时,就会激活触发器执行相应的方法. 创建触发器:create trigger 触发器名 (before/after) 触发事件 on 表名 for each row sql语句. 这里的new是指代新插入的拿一条数据(更新的也算),若是old的话,指的是删除的那一条数据(更新之前的数据).(new和old属于过渡变量) 这条触发器的意思时:当t_book有插入数据时,就会根据新插入数据的id找到t_bookType的id,并试该条数据的bookNum加1. Begin与end写sql语句,中间可以写多条sql语句用分号;分隔开…也即是说语句要写完成,不能少分号. Delimiter | 设置分隔符,要不然好像只会执行begin与and之间的第一条sql语句. 查看触发器: 1.show triggers; 语句查看触发器信息.(查询所有的触发器) 2.在triggers表中查看触发器信息.(在数据库原始表triggers中可以查看) 删除触发器: Drop trigger 触发器名称 ; 27.函数: (1)日期函数: CURDATE()当前日期,CURTIME()当前时间,MONTH(d):返回日期d中的月份值,范围试1-12 (2)字符串函数:CHAR_LENGTH(s) 计算字段s值->字符串的长度.UPPER(s) 把该字段的值中所有英文都变成大写,LOWER(s) 和相面相反->把英文都变成小写. (3)数学函数:sum():求和,ABS(s) 求绝对值,SQRT(s):求平方根,mod(x,y),求余x/y (4)加密函数:PASSWORD(STR) 一般对密码加密 不可逆… MD5(STR) 普通加密 ,不可逆. ENCODE(str,pswd_str) 加密函数,结果是一个二进制文件,用blob类型的字段保存,pswd_str类似一个加密的钥匙,可以随便写. DECODE(被加密的值,pswd_str)–>对encode进行解密. 28.存储过程: (1)存储过程和函数:两者是在数据库中定义一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程和函数可以避免重复的写一些sql语句,而且存储过程是在mysql服务器中存储和执行的,减少客户端和服务器端的数据传输.(类似于java代码写的工具类.) (2)创建存储过程和函数: Create procedure 关键字 pro_book 存储过程名称, in 输入 bT 输入参数名称 int 输入参数类型 out 输出 count_num 输出参数名称 int 输入参数类型 Begin 过程开始 end过程结束 中间是sql语句, Delimiter 默认是分号,而他的作用就是若是遇见分号时就开始执行该过程(语句),但是一个存储过程可能有很多sql语句且以分号结束,若这样的情况下当第一条sql语句结束后就会开始执行该过程,产生的后果是创建过程时,执行到第一个分号就会开始创建,导致存储过程创建错误.(若是有多个参数,在多条sql中均有参数,第一条设置完执行了,而这时第二条的参数有可能还么有设置完成,导致sql执行失败.)因此,需要把默认执行过程的demiliter关键字的默认值改为其他的字符,例如上面的就是改为&&,(当然我认为上面就一条sql语句,改不改默认的demiliter的默认值都一样.) . 使用navicat的话不使用delimiter好像也是可以的. Reads sql data则是上面图片所提到的参数指定存储过程的特性.(这个是指读数据,当然还有写输入与读写数据专用的参数类型.)看下图 经常用contains sql (应该是可以读,) 这个是调用上面的存储过程,1为入参,@total相当于全局变量,为出参. 这是一个存储函数,create function 为关键字,fun_book为函数名称, 括号里面为传入的参数名(值)以及入参的类型.RETURNS 为返回的关键字,后面接返回的类型. BEGIN函数开始,END函数结束.中间是return 以及查询数据的sql语句, 这里是指把bookId 传进去,通过存储函数返回对应的书本名字, ---------存储函数的调用和调用系统函数一样 例如:select 存储函数名称(入参值) Select 为查询 func_book 为存储函数名 2为入参值. (3)变量的使用:declaer:声明变量的值 Delimiter && Create procedure user() Begin Declare a,b varchar2(20) ; — a,b有默认的值,为空 Insert into user values(a,b); End && Delimiter ; Set 可以用来赋值,例如: 可以从其他表中查询出对应的值插入到另一个表中.例如: 从t_user2中查询出username2与password2放入到变量a,b中,然后再插入到t_user表中.(当然这只是创建存储过程),创建完以后,需要用CALL 存储过程名(根据过程参数描写.)来调用存储过程.注意:这一种的写法只可以插入单笔数据,若是select查询出多笔数据,因为无循环故而会插入不进去语句,会导致倒致存储过程时出错.下面的游标也是如此. (4)游标的使用.查询语句可能查询出多条记录,在存储过程和函数中使用游标逐条读取查询结果集中的记录.游标的使用包括声明游标,打开游标,使用游标和关闭游标.游标必须声明到处理程序之前,并且声明在变量和条件之后. 声明:declare 游标名 curson for 查询sql语句. 打开:open 游标名 使用:fetch 游标名 into x, 关闭:close 游标名 ----- 游标只能保存单笔数据. 类似于这一个,意思就是先查询出来username2,与password2的值放入到cur_t_user2的游标中(声明,类似于赋值),然后开启->使用.使用的意思就是把游标中存储的值分别赋值到a,b中,然后执行sql语句插入到t_user表中.最后关闭游标. (5)流程控制的使用:mysql可以使用:IF 语句 CASE语句 LOOP语句 LEAVE语句 ITERATE 语句 REPEAT语句与WHILE语句. 这个过程的意思是,查询t_user表中是否存在id等于我们入参时所写的id,若有的情况下查出有几笔这样的数据并且把数值给到全局变量@num中,if判断是否这样的数据是否存在,若是存在执行THEN后面的语句,即使更新该id对应的username,若没有则插入一条新的数据,最后注意END IF. 相当于java中的switch case.例如: 这里想当然于,while(ture){ break; } 这里的意思是,参数一个int类型的参数,loop aaa循环,把参数当做主键id插入到t_user表中,每循环一次参入的参数值减一,直到参数值为0,跳出循环(if判断,leave实现.) 相当于java的continue. 比上面的多了一个当totalNum = 3时,结束本次循环,下面的语句不在执行,直接执行下一次循环,也即是说插入的数据没有主键为3的数据. 和上面的差不多,只不过当执行到UNTIL时满足条件时,就跳出循环.就如上面那一个意思就是当执行到totalNum = 1时,跳出循环,也就是说不会插入主键为0的那一笔数据 当while条件判断为true时,执行do后面的语句,否则就不再执行. (6)调用存储过程和函数 CALL 存储过程名字(参数值1,参数值2,…) 存储函数名称(参数值1,参数值2,…) (7)查看存储过程和函数. Show procedure status like ‘存储过程名’ --只能查看状态 Show create procedure ‘存储过程名’ – 查看定义(使用频率高). 存储函数查看也和上面的一样. 当然还可以从information_schema.Routines中(系统数据库表)查看存储过程与函数. (8)修改存储过程与函数: 修改存储过程comment属性的值 ALTER procedure 存储过程名 comment ‘新值’; (9)删除存储过程与函数: DROP PROCEDURE 存储过程名; DROP function 存储函数名; 29.数据备份与还原: (1)数据备份:数据备份可以保证数据库表的安全性,数据库管理员需要定期的进行数据库备份. 命令:使用mysqldump(下图),或者使用图形工具 Mysqldump在msql文件夹+bin+mysqldump.exe中,相当于一个小软件.执行的话是在dos命令窗操作的. 其实就是导出数据库数据,在navacat中可以如下图导出 (2)数据还原: 若是从navacat中就是把外部的.sql文件数据导入到数据库中去.如下图 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_42847571/article/details/102686087。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-26 19:09:16
84
转载
转载文章
...则返回右侧表达式。请注意,当且仅当左侧为空时,才会对右侧表达式求值。 Kotlin 智能类型转换 对于子父类之间的类型转换 先看这样一段 Java 代码 public 尽管在 main 函数中,对 person 这个对象进行了类型判断,但是在使用的时候还是需要强制转换成 Student 类型,这样是不是很不智能? 同样的情况在 Kotlin 中就变得简单多了 fun main(args: Array<String>) { 在 Kotlin 中,只要对类型进行了判断,就可以直接通过父类的对象去调用子类的函数了 安全的类型转换 还是上面的那个例子,如果我们没有进行类型判断,并且直接进行强转,会怎么样呢? public static void main(String[] args) { 结果就只能是 Exception in thread "main" java.lang.ClassCastException 那么在 Kotlin 中是不是会有更好的解决方法呢? val person: Person = Person() 在转换操作符后面添加一个 ?,就不会把程序 crash 掉了,当转化失败的时候,就会返回一个 null 在空类型中的智能转换 需要提前了解 Kotlin 类型安全的相关知识(Kotlin 中的类型安全(对空指针的优化处理)) String? = aString 在定义的时候定义成了有可能为 null,按照之前的写法,我们需要这样写 String? = 但是已经进行了是否为 String 类型的判断,所以就一定 不是 空类型了,也就可以直接输出它的长度了 T.()->Unit 、 ()->Unit 在做kotlin开发中,经常看到一些系统函数里,用函数作为参数 public .()-Unit与()->Unit的区别是我们调用时,在代码块里面写this,的时候,两个this代表的含义不一样,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例。 推荐阅读 对 Kotlin 与 Java 编程语言的思考 使用 Kotlin 做开发一个月后的感想 扫一扫 关注我的公众号如果你想要跟大家分享你的文章,欢迎投稿~ 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39611037/article/details/109984124。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-23 23:56:14
473
转载
转载文章
...te等系统调用.但需注意,直接对该段内存写时不会写入超过当前文件大小的内容. 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。 基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。 用法: include <sys/mman.h> void mmap(void start, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void start, size_t length); 返回说明: 成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void )-1],munmap返回-1。errno被设为以下的某个值 EACCES:访问出错 EAGAIN:文件已被锁定,或者太多的内存已被锁定 EBADF:fd不是有效的文件描述词 EINVAL:一个或者多个参数无效 ENFILE:已达到系统对打开文件的限制 ENODEV:指定文件所在的文件系统不支持内存映射 ENOMEM:内存不足,或者进程已超出最大内存映射数量 EPERM:权能不足,操作不允许 ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志 SIGSEGV:试着向只读区写入 SIGBUS:试着访问不属于进程的内存区 参数: start:映射区的开始地址。 length:映射区的长度。 prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起 PROT_EXEC //页内容可以被执行 PROT_READ //页内容可以被读取 PROT_WRITE //页可以被写入 PROT_NONE //页不可访问 flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体 MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。 MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。 MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。 MAP_DENYWRITE //这个标志被忽略。 MAP_EXECUTABLE //同上 MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。 MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。 MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。 MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。 MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。 MAP_FILE //兼容标志,被忽略。 MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。 MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。 MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。 fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。 offset:被映射对象内容的起点。 3.munmap系统调用 include <sys/mman.h> int munmap( void addr, size_t len ) 该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。 4.msync系统调用 include <sys/mman.h> int msync ( void addr , size_t len, int flags) 一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。 二 系统调用mmap()用于共享内存的两种方式 (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下: [cpp] view plaincopy fd=open(name, flag, mode); if(fd<0) ... ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方 (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可. 三 mmap进行内存映射的原理 mmap系统调用的最终目的是将,设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,这个任务可以分为以下三步: 1.在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成) 每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G的范围内可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用。 那么,内核怎样管理每个进程3G的虚存空间呢?概括地说,用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如图3.1所示: 图3.1 进程虚拟空间的划分 在内核中,这样每个区域用一个结构struct vm_area_struct 来表示.它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。可以使用 cat /proc/<pid>/maps来查看一个进程的内存使用情况,pid是进程号.其中显示的每一行对应进程的一个vm_area_struct结构. 下面是struct vm_area_struct结构体的定义: [cpp] view plaincopy struct vm_area_struct { struct mm_struct vm_mm; / The address space we belong to. / unsigned long vm_start; / Our start address within vm_mm. / unsigned long vm_end; / The first byte after our end address within vm_mm. / / linked list of VM areas per task, sorted by address / struct vm_area_struct vm_next, vm_prev; pgprot_t vm_page_prot; / Access permissions of this VMA. / unsigned long vm_flags; / Flags, see mm.h. / struct rb_node vm_rb; / For areas with an address space and backing store, linkage into the address_space->i_mmap prio tree, or linkage to the list of like vmas hanging off its node, or linkage of vma in the address_space->i_mmap_nonlinear list. / union { struct { struct list_head list; void parent; / aligns with prio_tree_node parent / struct vm_area_struct head; } vm_set; struct raw_prio_tree_node prio_tree_node; } shared; / A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma list, after a COW of one of the file pages. A MAP_SHARED vma can only be in the i_mmap tree. An anonymous MAP_PRIVATE, stack or brk vma (with NULL file) can only be in an anon_vma list. / struct list_head anon_vma_chain; / Serialized by mmap_sem & page_table_lock / struct anon_vma anon_vma; / Serialized by page_table_lock / / Function pointers to deal with this struct. / const struct vm_operations_struct vm_ops; / Information about our backing store: / unsigned long vm_pgoff; / Offset (within vm_file) in PAGE_SIZE units, not PAGE_CACHE_SIZE / struct file vm_file; / File we map to (can be NULL). / void vm_private_data; / was vm_pte (shared mem) / unsigned long vm_truncate_count;/ truncate_count or restart_addr / ifndef CONFIG_MMU struct vm_region vm_region; / NOMMU mapping region / endif ifdef CONFIG_NUMA struct mempolicy vm_policy; / NUMA policy for the VMA / endif }; 通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。 假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。 图3.2 进程虚拟地址示意图 因此,mmap系统调用所完成的工作就是准备这样一段虚存空间,并建立vm_area_struct结构体,将其传给具体的设备驱动程序 2 建立虚拟地址空间和文件或设备的物理地址之间的映射(设备驱动完成) 建立文件映射的第二步就是建立虚拟地址和具体的物理地址之间的映射,这是通过修改进程页表来实现的.mmap方法是file_opeartions结构的成员: int (mmap)(struct file ,struct vm_area_struct ); linux有2个方法建立页表: (1) 使用remap_pfn_range一次建立所有页表. int remap_pfn_range(struct vm_area_struct vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot); 返回值: 成功返回 0, 失败返回一个负的错误值 参数说明: vma 用户进程创建一个vma区域 virt_addr 重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size. pfn 页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size. size 正在被重新映射的区的大小, 以字节. prot 给新 VMA 要求的"protection". 驱动可(并且应当)使用在vma->vm_page_prot 中找到的值. (2) 使用nopage VMA方法每次建立一个页表项. struct page (nopage)(struct vm_area_struct vma, unsigned long address, int type); 返回值: 成功则返回一个有效映射页,失败返回NULL. 参数说明: address 代表从用户空间传过来的用户空间虚拟地址. 返回一个有效映射页. (3) 使用方面的限制: remap_pfn_range不能映射常规内存,只存取保留页和在物理内存顶之上的物理地址。因为保留页和在物理内存顶之上的物理地址内存管理系统的各个子模块管理不到。640 KB 和 1MB 是保留页可能映射,设备I/O内存也可以映射。如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。 (4) remap_pfn_range与nopage的区别 remap_pfn_range一次性建立页表,而nopage通过缺页中断找到内核虚拟地址,然后通过内核虚拟地址找到对应的物理页 remap_pfn_range函数只对保留页和物理内存之外的物理地址映射,而对常规RAM,remap_pfn_range函数不能映射,而nopage函数可以映射常规的RAM。 3 当实际访问新映射的页面时的操作(由缺页中断完成) (1) page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。 (2) 文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。 (3) 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。 (4) 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。 注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新. (5) 所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。 四 总结 1.对于mmap的内存映射,是将物理内存映射到进程的虚拟地址空间中去,那么进程对文件的访问就相当于直接对内存的访问,从而加快了读写操作的效率。在这里,remap_pfn_range函数是一次性的建立页表,而nopage函数是根据page fault产生的进程虚拟地址去找到内核相对应的逻辑地址,再通过这个逻辑地址去找到page。完成映射过程。remap_pfn_range不能对常规内存映射,只能对保留的内存与物理内存之外的进行映射。 2.在这里,要分清几个地址,一个是物理地址,这个很简单,就是物理内存的实际地址。第二个是内核虚拟地址,即内核可以直接访问的地址,如kmalloc,vmalloc等内核函数返回的地址,kmalloc返回的地址也称为内核逻辑地址。内核虚拟地址与实际的物理地址只有一个偏移量。第三个是进程虚拟地址,这个地址处于用户空间。而对于mmap函数映射的是物理地址到进程虚拟地址,而不是把物理地址映射到内核虚拟地址。而ioremap函数是将物理地址映射为内核虚拟地址。 3.用户空间的进程调用mmap函数,首先进行必要的处理,生成vma结构体,然后调用remap_pfn_range函数建立页表。而用户空间的mmap函数返回的是映射到进程地址空间的首地址。所以mmap函数与remap_pfn_range函数是不同的,前者只是生成mmap,而建立页表通过remap_pfn_range函数来完成。 本篇文章为转载内容。原文链接:https://blog.csdn.net/wh8_2011/article/details/52373213。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-20 22:49:12
465
转载
转载文章
...row 关键字。只是注意如果要重新抛出异常,请使用 throw; 语句或者将原有异常作为内部异常。 创建自定义异常 如果你只是随便在业务上创建一个异常,那么写一个类继承自 Exception 即可: public class MyCustomException : Exception{public string MyCustomProperty { get; }public MyCustomException(string customProperty) => MyCustomProperty = customProperty;} 不过,如果你需要写一些比较通用抽象的异常(用于被继承),或者在底层组件代码中写自定义异常,那么就建议考虑写全异常的所有构造函数,并且加上可序列化: [Serializable]public class InvalidDepartmentException : Exception{public InvalidDepartmentException() : base() { }public InvalidDepartmentException(string message) : base(message) { }public InvalidDepartmentException(string message, Exception innerException) : base(message, innerException) { }// 如果异常需要跨应用程序域、跨进程或者跨计算机抛出,就需要能被序列化。protected InvalidDepartmentException(SerializationInfo info, StreamingContext context) : base(info, context) { } } 在创建自定义异常的时候,建议: 名称以 Exception 结尾 Message 属性的值是一个句子,用于描述异常发生的原因。 提供帮助诊断错误的属性。 尽量写全四个构造函数,前三个方便使用,最后一个用于序列化异常(新的异常类应可序列化)。 finally 异常堆栈跟踪 堆栈跟踪从引发异常的语句开始,到捕获异常的 catch 语句结束。 利用这一点,你可以迅速找到引发异常的那个方法,也能找到是哪个方法中的 catch 捕捉到的这个异常。 异常处理原则 try-catch-finally 我们第一个要了解的异常处理原则是——明确 try catch finally 的用途! try 块中,编写可能会发生异常的代码。 最好的情况是,你只将可能会发生异常的代码放到 try 块中,当然实际应用的时候可能会需要额外放入一些相关代码。但是如果你将多个可能发生异常的代码放到一个 try 块中,那么将来定位问题的时候你就会很抓狂(尤其是多个异常还是一个类别的时候)。 catch 块的作用是用来 “恢复错误” 的,是用来 “恢复错误” 的,是用来 “恢复错误” 的。 如果你在 try 块中先更改了类的状态,随后出了异常,那么最好能将状态改回来——这可以避免这个类型或者应用程序的其他状态出现不一致——这很容易造成应用程序“雪崩”。举一个例子:我们写一个程序有简洁模式和专业模式,在从简洁模式切换到专业模式的时候,我们设置 IsProfessionalMode 为 true,但随后出现了异常导致没有成功切换为专业模式;然而接下来所有的代码在执行时都判断 IsProfessionalMode 为 true 状态不正确,于是执行了一些非预期的操作,甚至可能用到了很多专业模式中才会初始化的类型实例(然而没有完成初始化),产生大量的额外异常;我们说程序雪崩了,多数功能再也无法正常使用了。 当然如果任务已全部完成,仅仅在对外通知的时候出现了异常,那么这个时候不需要恢复状态,因为实际上已经完成了任务。 你可能会有些担心如果我没有任何手段可以恢复错误怎么办?那这个时候就不要处理异常!——如果不知道如何恢复错误,请不要处理异常!让异常交给更上一层的模块处理,或者交给整个应用程序全局异常处理模块进行统一处理(这个后面会讲到)。 另外,异常不能用于在正常执行过程中更改程序的流程。异常只能用于报告和处理错误条件。 finally 块的作用是清理资源。 虽然 .NET 的垃圾回收机制可以在回收类型实例的时候帮助我们回收托管资源(例如 FileStream 类打开的文件),但那个时机不可控。因此我们需要在 finally 块中确保资源可被回收,这样当重新使用这个文件的时候能够立刻使用而不会被占用。 一段异常处理代码中可能没有 catch 块而有 finally 块,这个时候的重点是清理资源,通常也不知道如何正确处理这个错误。 一段异常处理代码中也可能 try 块留空,而只在 finally 里面写代码,这是为了“线程终止”安全考虑。在 .NET Core 中由于不支持线程终止因此可以不用这么写。详情可以参考:.NET/C 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions) - walterlv。 该不该引发异常? 什么情况下该引发异常?答案是——这真的是一个异常情况! 于是,我们可能需要知道什么是“异常情况”。 一个可以参考的判断方法是——判断这件事发生的频率: 如果这件事并不常见,当它发生时确实代表发生了一个错误,那么这件事情就可以认为是异常。 如果这件事经常发生,代码中正常情况就应该处理这件事情,那么这件事情就不应该被认为是异常(而是正常流程的一部分)。 例如这些情况都应该认为是异常: 方法中某个参数不应该传入 null 时但传入了 null 这是开发者使用这个方法时没有遵循此方法的契约导致的,让开发者改变调用此方法的代码就可以完全避免这件事情发生 而下面这些情况则不应该认为是异常: 用户输入了一串字符,你需要将这串字符转换为数字 用户输入的内容本身就千奇百怪,出现非数字的输入再正常不过了,对非数字的处理本就应该成为正常流程的一部分 对于这些不应该认为是异常的情况,编写的代码就应该尽可能避免异常。 有两种方法来避免异常: 先判断再使用。 例如读取文件之前,先判断文件是否存在;例如读取文件流时先判断是否已到达文件末尾。 如果提前判断的成本过高,可采用 TryDo 模式来完成,例如字符串转数字中的 TryParse 方法,字典中的 TryGetValue 方法。 对极为常见的错误案例返回 null(或默认值),而不是引发异常。极其常见的错误案例可被视为常规控制流。通过在这些情况下返回 NULL(或默认值),可最大程度地减小对应用的性能产生的影响。(后面会专门说 null) 而当存在下列一种或多种情况时,应引发异常: 方法无法完成其定义的功能。 根据对象的状态,对某个对象进行不适当的调用。 请勿有意从自己的源代码中引发 System.Exception、System.SystemException、System.NullReferenceException 或 System.IndexOutOfRangeException。 该不该捕获异常? 在前面 try-catch-finally 小节中,我们提到了 catch 块中应该写哪些代码,那里其实已经说明了哪些情况下应该处理异常,哪些情况下不应该处理异常。一句总结性的话是——如果知道如何从错误中恢复,那么就捕获并处理异常,否则交给更上层的业务去捕获异常;如果所有层都不知道如何处理异常,就交给全局异常处理模块进行处理。 应用程序全局处理异常 对于 .NET 程序,无论是 .NET Framework 还是 .NET Core,都有下面这三个可以全局处理的异常。这三个都是事件,可以自行监听。 AppDomain.UnhandledException 应用程序域未处理的异常,任何线程中未处理掉的异常都会进入此事件中 当这里能够收到事件,意味着应用程序现在频临崩溃的边缘(从设计上讲,都到这里了,也再没有任何代码能够使得程序从错误中恢复了) 不过也可以配置 legacyUnhandledExceptionPolicy 防止后台线程抛出的异常让程序崩溃退出 建议在这个事件中记录崩溃日志,然后对应用程序进行最后的拯救恢复操作(例如保存用户的文档数据) AppDomain.FirstChanceException 应用程序域中的第一次机会异常 我们前面说过,一个异常被捕获时,其堆栈信息将包含从 throw 块到 catch 块之间的所有帧,而在第一次机会异常事件中,只是刚刚 throw 出来,还没有被任何 catch 块捕捉,因此在这个事件中堆栈信息永远只会包含一帧(不过可以稍微变通一下在第一次机会异常 FirstChanceException 中获取比较完整的异常堆栈) 注意第一次机会异常事件即便异常会被 catch 也会引发,因为它引发在 catch 之前 不要认为异常已经被 catch 就万事大吉可以无视这个事件了。前面我们说过异常仅在真的是异常的情况才应该引发,因此如果这个事件中引发了异常,通常也真的意味着发生了错误(差别只是我们能否从错误中恢复而已)。如果你经常在正常的操作中发现可以通过此事件监听到第一次机会异常,那么一定是应用程序或框架中的异常设计出了问题(可能把正常应该处理的流程当作了异常,可能内部实现代码错误,可能出现了使用错误),这种情况一定是要改代码修 Bug 的。而一些被认为是异常的情况下收到此事件则是正常的。 TaskScheduler.UnobservedTaskException 在使用 async / await 关键字编写异步代码的时候,如果一直有 await 传递,那么异常始终可以被处理到;但中间有异步任务没有 await 导致异常没有被传递的时候,就会引发此事件。 如果在此事件中监听到异常,通常意味着代码中出现了不正确的 async / await 的使用(要么应该修改实现避免异常,要么应该正确处理异常并从中恢复错误) 对于 GUI 应用程序,还可以监听 UI 线程上专属的全局异常: WPF:Application.DispatcherUnhandledException 或者 Dispatcher.UnhandledException Windows Forms:Application.ThreadException 关于这些全局异常的处理方式和示例代码,可以参阅博客: WPF UnhandledException - Iron 的博客 - CSDN博客 抛出哪些异常? 任何情况下都不应该抛出这些异常: 过于抽象,以至于无法表明其含义 Exception 这可是顶级基类,这都抛出来了,使用者再也无法正确地处理此异常了 SystemException 这是各种异常的基类,本身并没有明确的意义 ApplicationException 这是各种异常的基类,本身并没有明确的意义 由 CLR 引发的异常 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 .NET 设计失误 FormatException 因为当它抛出来时无法准确描述到底什么错了 首先是你自己不应该抛出这样的异常。其次,你如果在运行中捕获到了上面这些异常,那么代码一定是写得有问题。 如果是捕获到了上面 CLR 的异常,那么有两种可能: 你的代码编写错误(例如本该判空的代码没有判空,又如索引数组超出界限) 你使用到的别人写的代码编写错误(那你就需要找到它改正,或者如果开源就去开源社区中修复吧) 而一旦捕获到了上面其他种类的异常,那就找到抛这个异常的人,然后对它一帧狂扁即可。 其他的异常则是可以抛出的,只要你可以准确地表明错误原因。 另外,尽量不要考虑抛出聚合异常 AggregateException,而是优先使用 ExceptionDispatchInfo 抛出其内部异常。详见:使用 ExceptionDispatchInfo 捕捉并重新抛出异常 - walterlv。 异常的分类 在 该不该引发异常 小节中我们说到一个异常会被引发,是因为某个方法声称的任务没有成功完成(失败),而失败的原因有四种: 方法的使用者用错了(没有按照方法的契约使用) 方法的执行代码写错了 方法执行时所在的环境不符合预期 简单说来,就是:使用错误,实现错误、环境错误。 使用错误: ArgumentException 表示参数使用错了 ArgumentNullException 表示参数不应该传入 null ArgumentOutOfRangeException 表示参数中的序号超出了范围 InvalidEnumArgumentException 表示参数中的枚举值不正确 InvalidOperationException 表示当前状态下不允许进行此操作(也就是说存在着允许进行此操作的另一种状态) ObjectDisposedException 表示对象已经 Dispose 过了,不能再使用了 NotSupportedException 表示不支持进行此操作(这是在说不要再试图对这种类型的对象调用此方法了,不支持) PlatformNotSupportedException 表示在此平台下不支持(如果程序跨平台的话) NotImplementedException 表示此功能尚在开发中,暂时请勿使用 实现错误: 前面由 CLR 抛出的异常代码主要都是实现错误 NullReferenceException 试图在空引用上执行某些方法,除了告诉实现者出现了意料之外的 null 之外,没有什么其它价值了 IndexOutOfRangeException 使用索引的时候超出了边界 InvalidCastException 表示试图对某个类型进行强转但类型不匹配 StackOverflow 表示栈溢出,这通常说明实现代码的时候写了不正确的显式或隐式的递归 OutOfMemoryException 表示托管堆中已无法分出期望的内存空间,或程序已经没有更多内存可用了 AccessViolationException 这说明使用非托管内存时发生了错误 BadImageFormatException 这说明了加载的 dll 并不是期望中的托管 dll TypeLoadException 表示类型初始化的时候发生了错误 环境错误: IOException 下的各种子类 Win32Exception 下的各种子类 …… 另外,还剩下一些不应该抛出的异常,例如过于抽象的异常和已经过时的异常,这在前面一小结中有说明。 其他 一些常见异常的原因和解决方法 在平时的开发当中,你可能会遇到这样一些异常,它不像是自己代码中抛出的那些常见的异常,但也不包含我们自己的异常堆栈。 这里介绍一些常见这些异常的原因和解决办法。 AccessViolationException 当出现此异常时,说明非托管内存中发生了错误。如果要解决问题,需要从非托管代码中着手调查。 这个异常是访问了不允许的内存时引发的。在原因上会类似于托管中的 NullReferenceException。 参考资料 Handling and throwing exceptions in .NET - Microsoft Docs Exceptions and Exception Handling - C Programming Guide - Microsoft Docs 我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。 如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。 本篇文章为转载内容。原文链接:https://blog.csdn.net/WPwalter/article/details/94610764。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-13 13:38:26
60
转载
转载文章
...P 的行为变更。尤其注意动态电量管理、Wi-Fi 权限变化、后台调用摄像头以及传感器的限制、针对应用数据的 SELinux 政策、默认启用 TLS 的变化,以及 Build.SERIAL 限制。 · 公开 API 的兼容性 (Compatibility through public APIs) 针对非 SDK 接口的测试十分重要。正如我们之前所强调的,在 Android P 中,我们将逐渐收紧一些非 SDK 接口的使用,这也要求广大的开发者们,包括 Google 内部的应用团队,使用公开 API。 如果您的应用正在使用私有 Android API 或者库,您需要改为使用 Android SDK 或 NDK 公开的 API。我们在 DP1 里已经对使用私有接口的开发者发出了警告信息,从 Android P Beta 开始,调用非 SDK 接口将会报错 (部分被豁免的私有 API 除外) —— 也就是说您的应用将会遭遇异常,而不再只是警告了。 为了帮助您定位非 SDK API 的使用情况,我们在 StrictMode 里加入了两个新的方法。您可以使用 detectNonSdkApiUsage() 在应用通过反射或 JNI 调用非 SDK API 的时候收到警报,您还可以使用 permitNonSdkApiUsage() 来阻止 StrictMode 针对这些调用报错。这些方法都可助您了解应用调用非 SDK API 的情况,但请注意,即便调用的 API 暂时得到了豁免,最保险的做法依然是尽快放弃对它们的使用。 如果您确实遇到了公开 API 无法满足需求的情况,请立刻告知我们。更多详细内容请查看相关文档。 · 凹口屏测试 (Test with display cutout) 针对凹口屏测试您的应用也十分重要。现在您可以在运行 Android P Beta 的合作伙伴机型上测试,确保您的应用在凹口屏上表现良好。同时,您也可以在 Android P 设备的开发者选项里打开对凹口屏的模拟,对您的应用做相应测试。 体验 Android P 在准备好开发条件后,请深入了解 Android P 并学习可以在您的应用中使用到的全新功能和 API。为了帮助您更轻松地探索和使用新 API,请查阅 API 变化报告 (API 27->DP2, DP1->DP2) 以及 Android P API 文档。访问开发者预览版网站了解详情。 下载/更新 Android P 开发者预览版 SDK 和工具包至 Android Studio 3.1,或使用最新版本的 Android Studio 3.2。如果您手边没有 Android P Beta 设备 (或查看今天推送的次条文章),请使用 Android P 模拟器来运行和测试您的应用。 您的反馈一直都至关重要,我们欢迎您畅所欲言。如果您在开发或测试过程中遇到了问题,请在文章下方留言给我们。再次感谢大家一路以来的支持。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34258782/article/details/87952581。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-10 18:19:36
339
转载
转载文章
...学习机制,并结合时空注意力机制进一步提升了图像分割的精度和效率。 此外,全球范围内的人工智能研究也在图像分割领域取得了突破性进展。例如,微软亚洲研究院今年公开了一项名为“HRNet”的高分辨率网络架构,它在医疗影像、自动驾驶等多个领域保持了顶级的图像分割性能。这一成果证明了通过保留高分辨率特征图并进行跨层次的信息融合,可以有效解决复杂背景下的精细化分割难题。 同时,阿里云不仅在技术研发上持续发力,还积极助力各行业实现数字化转型,如为教育、金融等行业提供了基于云服务的一站式证件照生成平台,用户只需上传原始照片即可快速获得满足各类规范要求的证件照,大大简化了传统流程,提高了工作效率。 综上所述,随着深度学习技术的不断迭代和云服务生态的完善,图像分割在证件照生成领域的应用正逐步走向成熟,并展现出巨大的市场潜力和社会价值。而作为行业领导者之一的阿里云,将持续引领技术创新,推动相关应用场景的落地与发展。
2023-07-11 23:36:51
132
转载
转载文章
... $( ( ) ),注意要两个括号 – test 测试命令 文件状态查询,字符、数字比较 && cmd1 && cmd2 当cmd1执行完并且正确,那么cmd2也执行 当cmd2执行完并且错误,那么cmd2不执行 || cmd1 || cmd2 当cmd1执行完并且正确,那么cmd2不执行 当cmd2执行完并且错误,那么cmd2也执行 查看一个文件是否存在 – 测试两个字符串是否相等 ==两边必须要有空格,如果不加空格,test这句就一直是对的。 – 中括号判断符 [ ] 作用和test类似 里面只能输入 == 或者 != 四个箭头所指必须用空格隔开 而且如果变量是字符串的话,一定要加双引号 – 默认变量 $0——shell脚本本身的命令 $——最后一个参数的标号(1,2,3,4…) $@——表示 $1 , $2 , $3 … $1 $2 $3 – shell 脚本的条件判断 if [ 条件判断 ];then //do something fi 红点处都要加空格 exit 0——表示退出 – if 条件判断;then //do something elif 条件判断;them //do something else //do something fi 红线处要加空格 – case 语句 case $var in “第一个变量的内容”) //do something ;; “第二个变量的内容”) // do something ;; . . . “第n个变量的内容”) //do something ;; esac 不能用 “”,否则就不是通配符的意思,而是表示字符 – shell 脚本函数 function fname(){ //函数代码段 } 其中function可以写也可以不写 调用函数的时候不要加括号 shell 脚本函数传参方式 – shell 循环 while[条件] //括号内的状态是判断式 do //循环代码段 done – until [条件] do //循环代码段 done – for循环,使用该循环可以知道有循环次数 for var con1 con2 con3 … … do //循环代码段 done – for 循环数值处理 for((初始值;限制值;执行步长)) do //循环代码段 done – 红点处必须要加空格!! loop 环 – – 注意变量有的地方用了 $ ,有的地方不需要 $ 这里的赋值号两边都不用加 空格 $(())数值运算 本篇文章为转载内容。原文链接:https://blog.csdn.net/engineer0/article/details/107965908。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 17:18:30
80
转载
转载文章
...5 新增的方法获取(注意兼容) 4.获取特殊元素(body,html) 三.事件基础 1.事件概述 2.执行事件的步骤 3.常见的鼠标事件 四.操作元素 1.操作元素内容(改变元素内容) 2. 操作常见元素属性 3.操作表单元素属性 4.操作元素样式属性 5.自定义属性的操作 6.H5自定义属性 五.节点操作 1.为什么要学习节点操作 2.节点概述 3.节点层级 一.DOM简介 1.什么是DOM 文档对象模型(简称DOM) 是W3C组织推荐的处理可扩展标记语言的标准编程接口 W3C已经定义来一系列DOM接口,通过这些DOM接口可以改变网页的内容、结构样式。 2.DOM 树 文档:一个页面就是一个文档,DOM 中使用 document 表示 元素:页面中的所有标签都是元素,DOM 中使用 element 表示 节点:网页中的所有内容都是节点(标签、属性、文本、注释等),DOM 中使用 node 表示 文档树(Dom树):以html为根节点,形成的一颗倒立的树状结构,我们成为DOM树;这个树上所有的东西都叫节点,节点有很多类,比如文本节点,元素节点等等,这些节点如果我们通过DOM方法去获取或者其他的操作去使用就叫做DOM对象,所有节点都是DOM对象 二.获取元素的方法 1.获取页面中的元素可以使用以下几种方式 根据ID获取 根据标签名获取 通过HTML5新增的方法获取 特殊元素获取 1.根据ID获取 使用getElementByld()方法可以获取带有ID的元素对象 getElementByld(),是document下的一个方法 代码演示 <body><div id="time">2020-11-26</div><script>// 1.因为我们文档页面从上往下加载,所以先得有标签 所以我们的script写在标签下面// 2. document文档 get 获得 element 元素 by 通过 驼峰命名法// 3.参数 id是大小写敏感的字符串// 4.返回的是一个对象var timer = document.getElementById('time');console.log(timer);// 5.console.dir 打印我们返回得的元素对象 更好的查看里面的属性和方法console.dir(timer);</script></body> 2.根据标签名获取 使用getElementsByTagName()方法可以返回带有指定标签名的对象的集合 语法如下 document.getElementsByTagName('标签名') 注意: 1.因为得到的是一个对象的集合,使用我们想要操作里面的元素就需要遍历 得到元素对象是动态的 代码演示 <body><ul><li>我们的征程是星辰大海</li><li>我们的征程是星辰大海</li><li>我们的征程是星辰大海</li><li>我们的征程是星辰大海</li><li>我们的征程是星辰大海</li></ul><ul id="nav"><li>心存感恩,所遇皆美好~</li><li>心存感恩,所遇皆美好~</li><li>心存感恩,所遇皆美好~</li><li>心存感恩,所遇皆美好~</li><li>心存感恩,所遇皆美好~</li></ul><script>// 1.返回的是 获取过来元素对象的集合 以伪数组的形式存储的var lis = document.getElementsByTagName('li')console.log(lis);// 2.如果想要依次打印里面的元素对象我们可以采取遍历方式for (var i = 0; i < lis.length; i++) {console.log(lis[i]);}// 3.这里可以是可以获取标签的.getElementsByTagName()可以得到这个元素里面的某些标签var nav1 = document.getElementById('nav') //这个获取nav元素var navli = nav.getElementsByTagName('li') //这里是获取nav 里面的li标签 要先获取 nav元素在获取里面的liconsole.log(navli);</script></body> 3.通过 HTML5 新增的方法获取(注意兼容) 1. document.getElementsByClassName(‘类名’);// 根据类名返回元素对象集合 2. document.querySelector('选择器'); // 根据指定选择器返回第一个元素对象 3. document.querySelectorAll('选择器'); // 根据指定选择器返回所有元素对象集合 注意:querySelector 和 querySelectorAll里面的选择器需要加符号,比如:document.querySelector(’nav’); 代码演示 <body><div class="box">盒子1</div><div class="box">盒子2</div><div id="nav"><ul><li>首页</li><li>产品</li></ul></div><script>// 1. getElementsByClassName 根据类名获得某些元素集合var boxs = document.getElementsByClassName('box');console.log(boxs);// 2. querySelector 返回指定选择器的第一个元素对象 切记 里面的选择器需要加符号 .box navvar firstBox = document.querySelector('.box');console.log(firstBox);var nav = document.querySelector('nav');console.log(nav);var li = document.querySelector('li');console.log(li);// 3. querySelectorAll()返回指定选择器的所有元素对象集合var allBox = document.querySelectorAll('.box');console.log(allBox);var lis = document.querySelectorAll('li');console.log(lis);</script> 4.获取特殊元素(body,html) 获取body元素 - doucumnet.body // 返回body元素对象 获取html元素 . document.documentElement // 返回html元素对象 代码演示 <body><script>// 获取bdoy元素var bodyEle = document.bodyconsole.log(bodyEle); //返回body元素// 获取html元素var htmlEle = document.documentElementconsole.log(htmlEle); //返回html元素</script></body> 三.事件基础 1.事件概述 JavaScript 使我们有能力创建动态页面,而事件是可以被 JavaScript 侦测到的行为。 简单理解: 触发— 响应机制。 网页中的每个元素都可以产生某些可以触发 JavaScript 的事件,例如,我们可以在用户点击某按钮时产生一个 事件,然后去执行某些操作。 代码演示 <body><button id="btn">浩哥</button><script>// 点击一个按钮,弹出一个对话框// 1.事件是有三部分组成的 1.事件源 2.事件类型 3.事件处理程序 也称为事件三要素// (1).事件源 事件被触发的对象 var but = document.getElementById('btn')// (2).事件类型 如何触发 什么事件 比如鼠标点击(onclick) 还是鼠标经过还是????// (3).事件处理程序 通过一个函数赋值的方式 完成 因为函数就是实现某种功能的but.onclick = function() {alert('浩哥爱编程')}</script></body> 2.执行事件的步骤 1. 获取事件源DOM对象(意思是你要获取那个元素) 2. 注册事件(绑定事件 意思是通过什么方式来处理比如是鼠标经过还是鼠标点击等等行为) 3. 添加事件处理程序(采取函数赋值形式 意思是你想做啥) 代码演示 <body><div>123</div><script>// 事件执行步骤 点击div 控制台输出我被选中了// 1.获取事件源var div = document.querySelector('div')// 2.绑定事件 注册事件// div.onclick// 3.添加事件处理程序div.onclick = function() {console.log('我被点击了');}</script></body> 3.常见的鼠标事件 onmouseenter鼠标移入事件 onmouseleave鼠标移出事件 四.操作元素 JS的DOM操作可以改变网页内容、结构和样式,利用DOM操作元素来改变元素里面的内容、属性等。注意以下都是属性 1.操作元素内容(改变元素内容) elemeny.innerText 从起始位置到终止位置的内容,但它去除html标签,同时空格和换行也会去掉 elemernt.innerHTML 起始位置到终止位置的全部内容,包括html标签,同时保留空格和换行 elemernt.Content可以获取隐藏元素的文本,包含换行和空白 代码演示 <title>Document</title><style>div,p {height: 30px;width: 300px;line-height: 30px;text-align: center;color: fff;background-color: pink;}</style></head><body><button>显示当前系统时间</button><div>某个时间</div><p>123</p><script>// 当我们点击了按钮,div里面的文字会发生变化// 1.获取元素 注意这里的按钮 和div都要获取到 因为 点击按钮div里面要发生变化所以都要获取var but = document.querySelector('button');var div = document.querySelector('div');// 2.绑定事件// but.onclick// 3.程序处理but.onclick = function() {// 改变元素内容 element(元素).innerTextdiv.innerText = '2020-11-27'}// 4.我们元素可以不用添加事件,就可以直接显示日期var p = document.querySelector('p');p.innerText = '2020-11-27';</script> elemeny.innerText和elemeny.innerHTML的区别 代码演示 <body><div></div><p></p><ul><li> 文字</li><li>123</li></ul><script>// innertText 和 innertHTML 的区别// 1. innerText 不识别html标签 非标准 去除空格和换行var div = document.querySelector('div');div.innerText = '<strong>今天是:</strong> 2020';// 2.innertHTML 识别html标签 W3C标准 保留空格和换行的 推荐尽量使用这个 因为这个是标准var p = document.querySelector('p')p.innerHTML = '<strong>今天是:</strong> 2020';// 3.这俩个属性是可读写的 意思是 除了改变内容还可以元素读取里面的内容的var ul = document.querySelector('ul')console.log(ul.innerText);console.log(ul.innerHTML);// .4innerHtml innerText 之间的区别:设置内容的时候,如果内容当中包含标签字符串 innerHtml会有标签的特性,也就是说标签会在页面上生效如果内容当中包含标签字符串 innerText会把标签原样展示在页面上,不会让标签生效读取内容的时候,如果标签内部还有其它标签,innerHtml会把标签内部带着其它的标签全部输出如果标签内部还有其它标签,innerText只会输出所有标签里面的内容或者文本,不会输出标签如果标签内部没有其它标签,他们两个一致;都是读取文本内容,innerHtml会带空白和换行</script></body> 2. 操作常见元素属性 innerText、innerHTML 改变元素内容 src、href id、alt、title 代码演示 <body><button id="ldh">刘德华</button><button id="zxy">张学友</button><br><img src="./images/ldh.jpg" alt="" width="200px" height="200px" title="刘德华" id="img"><script>// 修改属性 src// 我们可以操作元素得方法 来修改元素得属性 就是 元素的是什么属性 在重新给值就可以完成相应的赋值操作了// 1.获取元素var ldh = document.getElementById('ldh')var zxy = document.getElementById('zxy')var img = document.getElementById('img')// 2.注册事件 程序处理zxy.onclick = function() {// 当我们点击了图片的时候图片路径就发生变化 这里的.表示 的 得意思 img对象下的src属性img.src = './images/zxy.jpg';// 当我们变换图片得同时里面得title也要跟着变 所以前面要加上img.img.title = '张学友';}ldh.onclick = function() {img.src = './images/ldh.jpg';img.title = '刘德华';}</script> 3.操作表单元素属性 利用DOM可以操作如下表单元素的属性 type、value、checked、selected、disabled 代码演示: <body><button>按钮</button><input type="text" value="输入内容"><script>// 我想把value里面的输入内容改变为 被点击了// 1.获取元素var but = document.querySelector('button')var input = document.querySelector('input')// 2.注册事件 处理程序but.onclick = function() {// input.innerHTML = '被点击了'; 这个是 普通盒子 比如 div 标签里面的内容// 表单里面的值 文字内容是通过value来修改的input.value = '被点击了'// 如果需要某个表单被禁用 不能再点击了使用 disabled 我们想要这个按钮 button禁用// but.disabled = true// 还有一种写法// this指向的是事件函数的调用者 谁调用就指向谁 这里调用者是btnthis.disabled = true}</script></body> 4.操作元素样式属性 我们可以通过 JS 修改元素的大小、颜色、位置等样式。 1.element.style 行内样式操作 注意: JS 里面的样式采取驼峰命名法 比如 fontSize、 backgroundColor JS 修改 style 样式操作,产生的是行内样式,所以行内式比内嵌式高 代码演示 <style>div {width: 200px;height: 200px;background-color: red;}</style></head><body><div></div><script>// 要求点击div变成粉色 height变为250px// 1.获取元素var div = document.querySelector('div');// 2.注册事件 处理程序div.onclick = function() {// div.style里面的属性 采取的是驼峰命名法// this等于div this调用者 谁调用谁执行this.style.backgroundColor = 'pink'this.style.height = '250px'}</script> 2.element.className 类名样式操作 注意: 如果样式修改较多,可以采取操作类名方式更改元素样式。 class因为是个保留字,因此使用className来操作元素类名属性 className 会直接更改元素的类名,会覆盖原先的类名。 代码演示 <style>div {width: 100px;height: 100px;background-color: pink;}.change {background-color: purple;color: fff;font-size: 25px;margin-top: 100px;}</style></head><body><div class="first">文本</div><script>// 1. 使用 element.style 获得修改元素样式 如果样式比较少 或者 功能简单的情况下使用var test = document.querySelector('div');test.onclick = function() {// this.style.backgroundColor = 'purple';// this.style.color = 'fff';// this.style.fontSize = '25px';// this.style.marginTop = '100px';// 让我们当前元素的类名改为了 change// 2. 我们可以通过 修改元素的className更改元素的样式 适合于样式较多或者功能复杂的情况 如果想继续添加样式即在change添加即可// 3. 如果想要保留原先的类名,我们可以这么做 多类名选择器// this.className = 'change';this.className = 'first change';}</script> 5.自定义属性的操作 js给我们规定了可以自己添加属性 在操作元素属性的时候,元素.语法只能操作元素天生具有的属性,如果是自定义的属性,通过.语法是无法操作的只能通过getAttribute和setAttribute去操作,他俩是通用的方法,无论元素天生的还是自定义的都可以可以操作 1.获取属性值 element.属性 获取属性值。 element.getAttribute(‘属性’); 区别: element.属性 获取内置属性值(元素本身自带的属性 如果是自定义属性不能被获取) element.getAttribute(‘属性’);主要获得自定义的属性 (标准) 我们自定义的属性 2.设置属性值 element.属性 = ‘值’ 设置内置属性值 element.setAttribute(‘属性’,‘值’) 区别: element.属性 设置内置属性值 element.setAttribute(‘属性’);主要设置自定义的属性(标准) 3.移除属性 element.removeAttribute(‘属性’); 代码演示 <body><div id="demo" index="1" class="nav"></div><script>var div = document.querySelector('div');// 1.获取元素的属性值// (1) element.属性console.log(div.id);// (2) element.getAttribute('属性') get获取得到 attribute属性的意思 我们自己添加的属性称之为自定义属性console.log(div.getAttribute('id')); //democonsole.log(div.getAttribute('index')); // 1// 2.设置元素的属性值// (1) element.属性 = '值' div.id = 'test'div.className = 'navs'// (2) element.setAttribute('属性','值')div.setAttribute('index', 2);div.setAttribute('class', 'footer') //这里就是class 不是className 比较特殊// 3.移除属性 removeAttribute(属性)div.removeAttribute('index');</script></body> 只要是自定义属性最好都是用element.setAttribute(‘属性’,‘值’)来设置 如果是自带属性用element.属性来设置 6.H5自定义属性 自定义属性的目的:第一、是为了保存属性 第二、并且使用数据。有一些数据可以保存到页面中而不用保存到数据库中。 自定义属性获取是通过getAttribute(‘属性’) 获取的 但是有些自定义属性很容易引起歧义,不容易判断是元素还是自定义属性 H5给我们新增了自定义属性: 1.设置H5自定义属性 H5规定自定义属性data-开头做为属性名并且赋值 比如<div data-index:“1”> 或者使用JS设置element.setAttribute(‘deta-index’,2) 2.获取H5自定义属性 兼容性获取 element.getAttribute(‘data-index’) 推荐开发中使用这个 H5新增element.dataset.index 或者element.datase[‘index’] ie 11以上才支持 代码演示 <body><div getTime="10" data-index="20" data-name-list="40"></div><script>// 获取元素var div = document.querySelector('div');console.log(div.geTime); //undefined getTime是自定义属性不能直接通过元素的属性来获取 而是用自定义属性来获取的getAttribute(‘属性’)console.log(div.getAttribute('getTime')); //10// H5添加自定义属性的写法以data-开头div.setAttribute('data-time', 30)// 1.兼容性获取H5自定义属性console.log(div.getAttribute('data-time')); // 30// 2.H5新增的获取自定义属性的方法 它只能获取data-开头的// dataset 是一个集合的意思存放了所有以data开头的自定义属性 如果你想取其中的某一个只需要在dataset.的后面加上自定义属性名即可console.log(div.dataset);console.log(div.dataset.time); // 30// 还有一种方法dataset['属性']console.log(div.dataset['time']); // 30// 如果自定义属性里面有多个-链接的单词 我们获取的时候采取驼峰命名法 不用要-了console.log(div.dataset.nameList); // 40console.log(div.dataset['nameList']); // 40</script></body> 五.节点操作 1.为什么要学习节点操作 获取元素通常使用俩种方式 (1)利用DOM提供的方法获取元素 但是逻辑性不强 繁琐 (2)利用节点层级关系获取元素 如 利用父子,兄弟关系获取元素 逻辑性强,但是兼容性不怎么好 2.节点概述 网页中的所有内容都是节点(标签、属性、文本、注释等等) ,在DOM中,节点使用node表示。HTML DOM 树中的所有节点均可通过javascript进行访问,所有HTML元素(节点) 均可被修改,也可以创建或删除 一般地,节点至少拥有nade Type(节点类型)、nodeName(节点名称)和nodeValue(节点值) 这三个基本属性 元素节点 nodeType 为 1 属性节点 node Name为 2 文本节点 nodeValue为 3 (文本节点包含文字、空格、换行等等) 实际开发中,节点操作主要操作的是元素节点 3.节点层级 利用DOM树可以把节点划分为不同得层级关系,常见得是父子兄层级关系 1.父级节点 1.node.parentNode parenNode属性可以返回某节点得父节点,注意是最近的父节点哟!!! 如果指定的节点没有父节点就返回null 代码演示 <body><div class="box"><div class="box1"></div></div><script>var box1 = document.querySelector('.box1')// 得到的是离元素最近的父节点(亲爸爸) 得不到就返回得是nullconsole.log(box1.parentNode); // parentNode 翻译过来就是父亲的节点</script></body> 2.子级节点操作 1.parentNode.children(非标准) parentNode.children 是一个只读属性,返回所有的子元素节点。它只返回子元素节点,其余节点不返回(重点记住这个就好,以后重点使用) 虽然children是一个非标准,但是得到了各个浏览器的支持,我们大胆使用即可!!! 代码演示 <body><ul><li>1</li><li>1</li><li>1</li><li>1</li></ul><script>// DOM 提供的方法(APL)获取 这样获取比较麻烦var ul = document.querySelector('ul')var lis = ul.querySelectorAll('li')// children子节点获取 ul里面所有的小li 放心使用没有限制兼容性 实际开发中经常使用的console.log(ul.children);</script> 如何返回子节点的第一个和最后一个? 2.parentNode.firstElementChild firstElementChild返回第一个子元素节点,找不到则返回unll 3.parentNode.lastElementChild lastElementChild返回最后一个子元素节点,找不到则返回null 注意:这俩个方法有兼容性问题,IE9以上才支持 谨慎使用 但是我们有解决方案 如果想要第一个子元素节点,可以使用 parentNode.chilren[0] 如果想要最后一个子元素节点,可以使用 parentNode.chilren[parentNode.chilren.length - 1] 代码演示 <body><ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul><script>var ul = document.querySelector('ul')// 1.firstElementChild 返回第一个子元素节点 ie9 以上才支持注意兼容console.log(ul.firstElementChild);// 2.lastElementChild返回最后一个子元素节点console.log(ul.lastElementChild);// 3.实际开发中用到的既没有兼容性问题又可以返回子节点的第一个和最后一个console.log(ul.children[0]);console.log(ul.children[ul.children.length - 1]); //ul.children.length - 1获取的永远是子节点最后一个</script></body> 3.兄弟节点 1.node.nextSibling nextSibling 返回当前元素的下一个兄弟节点,找不到则返回null。注意包含所有的节点 2.node.previousSibling previousSibling 返回当前元素上一个兄弟节点,找不到则返回null。注意包含所以有的节点 代码演示 <body><div>我是div</div><span>我是span</span><script>var div = document.querySelector('div')// 返回当前元素的下一个兄弟节点nextSibling,找不到返回null。注意包含元素节点或者文本节点等等console.log(div.nextSibling); //这里返回的是text 因为它的下一个兄弟节点是换行// 返回的是当前元素的上一个节点previousSibling,找不到返回null。注意包含元素节点或者文本节点等等console.log(div.previousSibling); //这里返回的是text 因为它的上一个兄弟节点是换行</script></body> 3.node.nexElementSibling nexElementSibling 返回当前元素下一个兄弟元素节点,找不到返回null 4.node.previousElementSibling previousElementSibling返回当前元素上一个兄弟节点,找不到返回null 注意:这俩个方法有兼容性问题,IE9以上才支持 代码演示 <body><div>我是div</div><span>我是span</span><script>var div = document.querySelector('div')// nextElementSiblingd得到下一个兄弟元素节点console.log(div.nextElementSibling); // span // previousElementSibling 得到的是上一个兄弟元素节点console.log(div.previousElementSibling); // null 因为它上面没有兄弟元素了返回空的</script></body> 怎么解决兼容性问题呢? 可以封装一个兼容性函数(简单了解即可 在实际开发中用的不多) function getNextElementSibling(element) {var el = element;while (el = el.nextSibling) {if (el.nodeType === 1) {return el;} }return null;} 4.创建节点 1.document.createElement('tagName') document.createElement( ) 方法创建由 tagName 指定的 HTML 元素。因为这些元素原先不存在的是根据我们的需求动态生成的,所有我们也称为动态创建元素节点 我们创建了节点要给添加到节点里面去 称为 添加节点 1.node.appendChild(child) node.appendChild( )方法将一个节点添加到指定父节点的子节点列表末尾 2.node.insertBefore(child,指定添加元素位置) node.insertBefore( ) 方法将一个节点添加到父节点的指定子节点前面 代码演示 <body><ul><li>1</li></ul><script>// 1.创建节点 createElementvar li = document.createElement('li')// 2.添加节点 创建了节点要添加到某一个元素身上去 叫添加节点 node.appendChild(child) done 父级 child 子级 如果前面有元素了则在后面追加元素类似数组中的push依次追加var ul = document.querySelector('ul')ul.appendChild(li)// 3.添加节点 node.insertBefore(child,指定元素) 在子节点前面添加子节点 child子级你要添加的元素var lili = document.createElement('li')ul.insertBefore(lili, ul.children[0]) //ul.children 这句话的意思是添加到ul父亲的子节点第一个// 总结 如果想在页面中添加元素分为俩步骤1.创建元素 2.添加元素</script></body> 5.删除节点 node.removeChild(child) node.removeChlid()方法从DOM 中删除一个子节点,返回删除的节点 简单点就是从父元素中删除某一个孩子node就是父亲child就是孩子 删除的节点.remove(没有参数) 注意:ie不支持 代码演示 <body><button>按钮</button><ul><li>熊大</li><li>熊二</li><li>熊三</li></ul><script>// 1.获取元素var ul = document.querySelector('ul')var but = document.querySelector('button');// 2.删除元素// but.onclick = function() {// ul.removeChild(ul.children[0])// }// 3.点击按钮键依次删除,最后没有删除内容了 就禁用按钮 disabled = true 禁用按钮语法but.onclick = function() {if (ul.children.length == 0) {this.disabled = true} else {ul.removeChild(ul.children[0])} }</script></body> 6.复制节点(克隆节点) node.cloneNode() node.dloneNode()方法返回调用该方法节点得一个副本,也称为克隆节点/拷贝节点 注意 1.如果括号参数为空或者为false,则是浅拷贝,只复制里面得标签,不复制内容 2.如果括号参数为true,则是深度拷贝,会复制节点本身以及里面所有的内容 代码演示 <body><ul><li>1</li><li>2</li><li>3</li></ul><script>// 1.获取元素var ul = document.querySelector('ul');// 2.复制元素 node.cloneNode() 如果参数括号为空或者false则只会复制元素不会复制内容,如果待有参数true则内容和元素都会被复制var lis = ul.children[0].cloneNode(true);// 3.获取元素ul.appendChild(lis)</script></body> 7.替换(改)节点 node.replaceChild(新节点,替换到什么位置) 代码演示 <body><ul class="list"><li>1</li><li>2</li></ul><script>// 替换(改)节点 父节点.replaceChild(新元素, 替换到什么位置)// (1)获取父元素var ulNode = document.querySelector('.list');// (2)创建新的元素var liRead = document.createElement('li')// (3)给新元素添加内容liRead.innerHTML = '5';// (4)替换元素ulNode.replaceChild(liRead, ulNode.children[1])</script></body> 8.三种动态创建元素区别 document.write() element.innerHTML document.createElement() 区别 document.write()是直接将内容写入页面的内容流,但是文档流执行完毕,它则会导致页面全部重绘 element.innerHTML是将内容写入某个DOM节点,不会导致页面全部重绘 element.innerHTML 创建多个元素效率更高(不要拼接字符串,采取数组形式拼接),结果有点复杂 createElement()创建多个元素效率低一点点,但是结果更加清晰 总结:不同浏览器下,innerHTML效率要比createElement()高 代码演示 <body><button>点击</button><p>abc</p><div class="inner"></div><div class="create"></div><script>// window.onload = function() {// document.write('<div>123</div>');// }// 三种创建元素方式区别 // 1. document.write() 创建元素 如果页面文档流加载完毕,再调用这句话会导致页面重绘// var btn = document.querySelector('button');// btn.onclick = function() {// document.write('<div>123</div>');// }// 2. innerHTML 创建元素var inner = document.querySelector('.inner');// for (var i = 0; i <= 100; i++) {// inner.innerHTML += '<a href="">百度</a>'// }var arr = [];for (var i = 0; i <= 100; i++) {arr.push('<a href="">百度</a>');}inner.innerHTML = arr.join('');// 3. document.createElement() 创建元素var create = document.querySelector('.create');for (var i = 0; i <= 100; i++) {var a = document.createElement('a');create.appendChild(a);}</script></body> 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_46978034/article/details/110190352。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-08-04 13:36:05
248
转载
转载文章
...期而提供的方法。需要注意的时候 ,这些方法中,有带 UTC 的,有不带 UTC 的。UTC 日期指的是在没有时区偏差的情况下的日期值。 alert(box.getTime()); // 获取日期的毫秒数,和 valueOf()返回一致alert(box.setTime(100)); // 以毫秒数设置日期,会改变整个日期alert(box.getFullYear()); // 获取四位年份alert(box.setFullYear(2012)); // 设置四位年份,返回的是毫秒数alert(box.getMonth()); // 获取月份,没指定月份,从 0 开始算起alert(box.setMonth(11)); // 设置月份alert(box.getDate()); // 获取日期alert(box.setDate(8)); // 设置日期,返回毫秒数alert(box.getDay()); // 返回星期几,0 表示星期日,6 表示星期六alert(box.setDay(2)); // 设置星期几alert(box.getHours()); // 返回时alert(box.setHours(12)); // 设置时alert(box.getMinutes()); // 返回分钟alert(box.setMinutes(22)); // 设置分钟alert(box.getSeconds()); // 返回秒数alert(box.setSeconds(44)); // 设置秒数alert(box.getMilliseconds()); // 返回毫秒数alert(box.setMilliseconds()); // 设置毫秒数alert(box.getTimezoneOffset()); // 返回本地时间和 UTC 时间相差的分钟数 以上方法除了 getTimezoneOffset(),其他都具有 UTC 功能,例如 setDate()及 getDate()获取星期几,那么就会有 setUTCDate()及getUTCDate(),表示世界协调时间。 2、正则表达式 假设用户需要在 HTML 表单中填写姓名、地址、出生日期等。那么在将表单提交到服务器进一步处理前,JavaScript 程序会检查表单以确认用户确实输入了信息并且这些信息是符合要求的。 什么是正则表达式 正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript 的 RegExp 类表示正则表达式,而 String 和 RegExp 都定义了使用正则表达式进行强大的模式匹配和文本检索与替换的函数。 正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会被发送到服务器,在服务器端通常会用 PHP、ASP.NET 等服务器脚本对其进行进一步处理 。因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更好的用户体验。 创建正则表达式 创建正则表达式和创建字符串类似,创建正则表达式提供了两种方法,一种是采用 new 运算符,另一个是采用字面量方式。 两种创建方式 var box = new RegExp('box'); // 第一个参数字符串var box = new RegExp('box', 'ig'); // 第二个参数可选模式修饰符 模式修饰符的可选参数 参数 含义 i 忽略大小写 g 全局匹配 m 多行匹配 var box = /box/; // 直接用两个反斜杠var box = /box/ig; // 在第二个斜杠后面加上模式修饰符 测试正则表达式 RegExp 对象包含两个方法:test()和 exec(),功能基本相似,用于测试字符串匹配。test()方法在字符串中查找是否存在指定的正则表达式并返回布尔值,如果存在则返回 true,不存在则返回 false。exec()方法也用于在字符串中查找指定正则表达式,如果 exec()方法执行成功,则返回包含该查找字符串的相关信息数组。如果执行失败,则返回 null。 RegExp 对象的方法 方法 功能 test 在字符串中测试模式匹配,返回 true 或 false exec 在字符串中执行匹配搜索,返回结果数组 // 使用 new 运算符的 test 方法示例var pattern = new RegExp('box', 'i'); // 创建正则模式,不区分大小写var str = 'This is a Box!'; // 创建要比对的字符串alert(pattern.test(str)); // 通过 test()方法验证是否匹配// 使用字面量方式的 test 方法示例var pattern = /box/i; // 创建正则模式,不区分大小写var str = 'This is a Box!';alert(pattern.test(str));// 使用一条语句实现正则匹配alert(/box/i.test('This is a Box!')); // 模式和字符串替换掉了两个变量// 使用 exec 返回匹配数组var pattern = /box/i;var str = 'This is a Box!';alert(pattern.exec(str)); // 匹配了返回数组,否则返回 null 使用字符串的正则表达式方法 除了 test()和 exec()方法,String 对象也提供了 4 个使用正则表达式的方法。 String 对象中的正则表达式方法 方法 含义 match(pattern) 返回 pattern 中的子串或 null replace(pattern, replacement) 用 replacement 替换 pattern search(pattern) 返回字符串中 pattern 开始位置 split(pattern) 返回字符串按指定 pattern 拆分的数组 // 使用 match 方法获取获取匹配数组var pattern = /box/ig; // 全局搜索var str = 'This is a Box!,That is a Box too';alert(str.match(pattern)); // 匹配到两个 Box,Boxalert(str.match(pattern).length); // 获取数组的长度// 使用 search 来查找匹配数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.search(pattern)); // 查找到返回位置,否则返回-1 因为 search 方法查找到即返回,也就是说无需 g 全局。 // 使用 replace 替换匹配到的数据var pattern = /box/ig;var str = 'This is a Box!,That is a Box too';alert(str.replace(pattern, 'Tom')); // 将 Box 替换成了 Tom// 使用 split 拆分成字符串数组var pattern = / /ig;var str = 'This is a Box!,That is a Box too';alert(str.split(pattern)); // 将空格拆开分组成数组 RegExp 对象的静态属性 属性 短名 含义 input $_ 当前被匹配的字符串 lastMatch $& 最后一个匹配字符串 lastParen $+ 最后一对圆括号内的匹配子串 leftContext $ 最后一次匹配前的子串 multiline $ 用于指定是否所有的表达式都用于多行的布尔值 rightContext $’ 在上次匹配之后的子串 // 使用静态属性var pattern = /(g)oogle/;var str = 'This is google!';pattern.test(str); // 执行一下alert(RegExp.input); // This is google!alert(RegExp.leftContext); // This isalert(RegExp.rightContext); // !alert(RegExp.lastMatch); // googlealert(RegExp.lastParen); // galert(RegExp.multiline); // false Opera 不支持 input、lastMatch、lastParen 和 multiline 属性。IE 不支持 multiline 属性。所有的属性可以使用短名来操作。RegExp.input 可以改写成 RegExp['$_'],依次类推。但 RegExp.input 比较特殊,它还可以写成 RegExp.$_。 RegExp 对象的实例属性 属性 含义 global Boolean 值,表示 g 是否已设置 ignoreCase Boolean 值,表示 i 是否已设置 lastIndex 整数,代表下次匹配将从哪里字符位置开始 multiline Boolean 值,表示 m 是否已设置 Source 正则表达式的源字符串形式 // 使用实例属性var pattern = /google/ig;alert(pattern.global); // true,是否全局了alert(pattern.ignoreCase); // true,是否忽略大小写alert(pattern.multiline); // false,是否支持换行alert(pattern.lastIndex); // 0,下次的匹配位置alert(pattern.source); // google,正则表达式的源字符串var pattern = /google/g;var str = 'google google google';pattern.test(str); // google,匹配第一次alert(pattern.lastIndex); // 6,第二次匹配的位 以上基本没什么用。并且 lastIndex 在获取下次匹配位置上 IE 和其他浏览器有偏差 ,主要表现在非全局匹配上。lastIndex 还支持手动设置,直接赋值操作。 获取控制 正则表达式元字符是包含特殊含义的字符。它们有一些特殊功能,可以控制匹配模式的方式。反斜杠后的元字符将失去其特殊含义。 字符类:单个字符和数字 元字符/元符号 匹配情况 . 匹配除换行符外的任意字符 [a-z0-9] 匹配括号中的字符集中的任意字符 [^a-z0-9] 匹配任意不在括号中的字符集中的字符 \d 匹配数字 \D 匹配非数字,同[^0-9]相同 \w 匹配字母和数字及_ \W 匹配非字母和数字及_ 字符类:空白字符 元字符/元符号 匹配情况 \0 匹配 null 字符 \b 匹配空格字符 \f 匹配进纸字符 \n 匹配换行符 \r 匹配回车字符 \t 匹配制表符 \s 匹配空白字符、空格、制表符和换行符 \S 匹配非空白字符 字符类:锚字符 元字符/元符号 匹配情况 ^ 行首匹配 $ 行尾匹配 \A 只有匹配字符串开始处 \b 匹配单词边界,词在[]内时无效 \B 匹配非单词边界 \G 匹配当前搜索的开始位置 \Z 匹配字符串结束处或行尾 \z 只匹配字符串结束处 字符类:重复字符 元字符/元符号 匹配情况 x? 匹配 0 个或 1 个 x x 匹配 0 个或任意多个 x x+ 匹配至少一个 x (xyz)+ 匹配至少一个(xyz) x{m,n} 匹配最少 m 个、最多 n 个 x 字符类:替代字符 元字符/元符号 匹配情况 this where 字符类:记录字符 元字符/元符号 匹配情况 (string) 用于反向引用的分组 \1 或$1 匹配第一个分组中的内容 \2 或$2 匹配第二个分组中的内容 \3 或$3 匹配第三个分组中的内容 // 使用点元字符var pattern = /g..gle/; // .匹配一个任意字符var str = 'google';alert(pattern.test(str));// 重复匹配var pattern = /g.gle/; // .匹配 0 个一个或多个var str = 'google'; //,?,+,{n,m}alert(pattern.test(str));// 使用字符类匹配var pattern = /g[a-zA-Z_]gle/; // [a-z]表示任意个 a-z 中的字符var str = 'google';alert(pattern.test(str));var pattern = /g[^0-9]gle/; // [^0-9]表示任意个非 0-9 的字符var str = 'google';alert(pattern.test(str));var pattern = /[a-z][A-Z]+/; // [A-Z]+表示 A-Z 一次或多次var str = 'gOOGLE';alert(pattern.test(str));// 使用元符号匹配var pattern = /g\wgle/; // \w匹配任意多个所有字母数字_var str = 'google';alert(pattern.test(str));var pattern = /google\d/; // \d匹配任意多个数字var str = 'google444';alert(pattern.test(str));var pattern = /\D{7,}/; // \D{7,}匹配至少 7 个非数字var str = 'google8';alert(pattern.test(str));// 使用锚元字符匹配var pattern = /^google$/; // ^从开头匹配,$从结尾开始匹配var str = 'google';alert(pattern.test(str));var pattern = /goo\sgle/; // \s 可以匹配到空格var str = 'goo gle';alert(pattern.test(str));var pattern = /google\b/; // \b 可以匹配是否到了边界var str = 'google';alert(pattern.test(str));// 使用或模式匹配var pattern = /google|baidu|bing/; // 匹配三种其中一种字符串var str = 'google';alert(pattern.test(str));// 使用分组模式匹配var pattern = /(google){4,8}/; // 匹配分组里的字符串 4-8 次var str = 'googlegoogle';alert(pattern.test(str));var pattern = /8(.)8/; // 获取 8..8 之间的任意字符var str = 'This is 8google8';str.match(pattern);alert(RegExp.$1); // 得到第一个分组里的字符串内容var pattern = /8(.)8/;var str = 'This is 8google8';var result = str.replace(pattern,'<strong>$1</strong>'); // 得到替换的字符串输出document.write(result);var pattern = /(.)\s(.)/;var str = 'google baidu';var result = str.replace(pattern, '$2 $1'); // 将两个分组的值替换输出document.write(result); 贪婪 惰性 + +? ? ?? ? {n} {n}? {n,} {n,}? {n,m} {n,m}? // 关于贪婪和惰性var pattern = /[a-z]+?/; // ?号关闭了贪婪匹配,只替换了第一个var str = 'abcdefjhijklmnopqrstuvwxyz';var result = str.replace(pattern, 'xxx');alert(result);var pattern = /8(.+?)8/g; // 禁止了贪婪,开启的全局var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);var pattern = /8([^8])8/g; // 另一种禁止贪婪var str = 'This is 8google8, That is 8google8, There is 8google8';var result = str.replace(pattern,'<strong>$1</strong>');document.write(result);// 使用 exec 返回数组var pattern = /^[a-z]+\s[0-9]{4}$/i;var str = 'google 2012';alert(pattern.exec(str)); // 返回整个字符串var pattern = /^[a-z]+/i; // 只匹配字母var str = 'google 2012';alert(pattern.exec(str)); // 返回 googlevar pattern = /^([a-z]+)\s([0-9]{4})$/i; // 使用分组var str = 'google 2012';alert(pattern.exec(str)[0]); // google 2012alert(pattern.exec(str)[1]); // googlealert(pattern.exec(str)[2]); // 2012// 捕获性分组和非捕获性分组var pattern = /(\d+)([a-z])/; // 捕获性分组var str = '123abc';alert(pattern.exec(str));var pattern = /(\d+)(?:[a-z])/; // 非捕获性分组var str = '123abc';alert(pattern.exec(str));// 使用分组嵌套var pattern = /(A?(B?(C?)))/; // 从外往内获取var str = 'ABC';alert(pattern.exec(str));// 使用前瞻捕获var pattern = /(goo(?=gle))/; // goo 后面必须跟着 gle 才能捕获var str = 'google';alert(pattern.exec(str));// 使用特殊字符匹配var pattern = /\.\[\/b\]/; // 特殊字符,用\符号转义即可var str = '.[/b]';alert(pattern.test(str));// 使用换行模式var pattern = /^\d+/mg; // 启用了换行模式var str = '1.baidu\n2.google\n3.bing';var result = str.replace(pattern, '');alert(result); 常用的正则 检查邮政编码 var pattern = /[1-9][0-9]{5}/; // 共 6 位数字,第一位不能为 0var str = '224000';alert(pattern.test(str)); 检查文件压缩包 var pattern = /[\w]+\.zip|rar|gz/; // \w 表示所有数字和字母加下划线var str = '123.zip'; // \.表示匹配.,后面是一个选择alert(pattern.test(str)); 删除多余空格 var pattern = /\s/g; // g 必须全局,才能全部匹配var str = '111 222 333';var result = str.replace(pattern,''); // 把空格匹配成无空格alert(result); 删除首尾空格 var pattern = /^\s+/; // 强制首var str = ' goo gle ';var result = str.replace(pattern, '');pattern = /\s+$/; // 强制尾result = result.replace(pattern, '');alert('|' + result + '|');var pattern = /^\s(.+?)\s$/; // 使用了非贪婪捕获var str = ' google ';alert('|' + pattern.exec(str)[1] + '|');var pattern = /^\s(.+?)\s$/;var str = ' google ';alert('|' + str.replace(pattern, '$1') + '|'); // 使用了分组获取 简单的电子邮件验证 var pattern = /^([a-zA-Z0-9_\.\-]+)@([a-zA-Z0-9_\.\-]+)\.([a-zA-Z]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str));var pattern = /^([\w\.\-]+)@([\w\.\-]+)\.([\w]{2,4})$/;var str = 'yc60.com@gmail.com';alert(pattern.test(str)); 3、Function类型 在 ECMAScript 中,Function(函数)类型实际上是对象。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。 函数的声明方式 普通的函数声明 function box(num1, num2) {return num1+ num2;} 使用变量初始化函数 var box= function(num1, num2) {return num1 + num2;}; 使用 Function 构造函数 var box= new Function('num1', 'num2' ,'return num1 + num2'); 第三种方式我们不推荐,因为这种语法会导致解析两次代码(第一次解析常规 ECMAScript 代码,第二次是解析传入构造函数中的字符串),从而影响性能。但我们可以通过这种语法来理解"函数是对象,函数名是指针"的概念。 作为值的函数 ECMAScript 中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。 function box(sumFunction, num) {return sumFunction(num); // someFunction}function sum(num) {return num + 10;}var result = box(sum, 10); // 传递函数到另一个函数里 函数内部属性 在函数内部,有两个特殊的对象:arguments 和 this。arguments 是一个类数组对象,包含着传入函数中的所有参数,主要用途是保存函数参数。但这个对象还有一个名叫 callee 的属性,该属性是一个指针,指向拥有这个 arguments 对象的函数。 function box(num) {if (num <= 1) {return 1;} else {return num box(num-1); // 一个简单的的递归} } 对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身;如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改。为了解决这个问题,我们可以使用 arguments.callee 来代替。 function box(num) {if (num <= 1) {return 1;} else {return num arguments.callee(num-1); // 使用 callee 来执行自身} } 函数内部另一个特殊对象是 this,其行为与 Java 和 C中的 this 大致相似。换句话说 ,this 引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。当在全局作用域中调用函数时,this 对象引用的就是 window。 // 便于理解的改写例子window.color = '红色的'; // 全局的,或者 var color = '红色的';也行alert(this.color); // 打印全局的 colorvar box = {color : '蓝色的', // 局部的 colorsayColor : function () {alert(this.color); // 此时的 this 只能 box 里的 color} };box.sayColor(); // 打印局部的 coloralert(this.color); // 还是全局的// 引用教材的原版例子window.color = '红色的'; // 或者 var color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color); // 这里第一次在外面,第二次在 box 里面}getColor();box.sayColor = sayColor; // 把函数复制到 box 对象里,成为了方法box.sayColor(); 函数属性和方法 ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性 :length 和 prototype。其中,length 属性表示函数希望接收的命名参数的个数。 function box(name, age) {alert(name + age);}alert(box.length); // 2 对于 prototype 属性,它是保存所有实例方法的真正所在,也就是原型。这个属性 ,我们将在面向对象一章详细介绍。而 prototype 下有两个方法:apply()和 call(),每个函数都包含这两个非继承而来的方法。这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。 function box(num1, num2) {return num1 + num2; // 原函数}function sayBox(num1, num2) {return box.apply(this, [num1, num2]); // this 表示作用域,这里是 window} // []表示 box 所需要的参数function sayBox2(num1, num2) {return box.apply(this, arguments); // arguments 对象表示 box 所需要的参数}alert(sayBox(10,10)); // 20alert(sayBox2(10,10)); // 20 call()方法于 apply()方法相同,他们的区别仅仅在于接收参数的方式不同。对于 call()方法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。 function box(num1, num2) {return num1 + num2;}function callBox(num1, num2) {return box.call(this, num1, num2); // 和 apply 区别在于后面的传参}alert(callBox(10,10)); 事实上,传递参数并不是 apply()和 call()方法真正的用武之地;它们经常使用的地方是能够扩展函数赖以运行的作用域。 var color = '红色的'; // 或者 window.color = '红色的';也行var box = {color : '蓝色的'};function sayColor() {alert(this.color);}sayColor(); // 作用域在 windowsayColor.call(this); // 作用域在 windowsayColor.call(window); // 作用域在 windowsayColor.call(box); // 作用域在 box,对象冒充 这个例子是之前作用域理解的例子修改而成,我们可以发现当我们使用 call(box)方法的时候,sayColor()方法的运行环境已经变成了 box 对象里了。 使用 call()或者 apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。也就是说,box 对象和 sayColor()方法之间不会有多余的关联操作,比如 box.sayColor = sayColor;。 本篇文章为转载内容。原文链接:https://blog.csdn.net/gongxifacai_believe/article/details/108286196。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-24 13:01:25
530
转载
转载文章
...个值之间用逗号分隔。注意这只能用于数据成员都是共有的情况。 在前面的例子里,是用成员函数对对象的数据成员赋初值,如果一个类定义了多个对象,对每个对象都要调用成员函数对数据成员赋初值,那么程序就会变得繁琐,所以用成员函数为数据成员赋初值不是一个好办法。 2.构造函数的作用 构造函数用于为对象分配空间和进行初始化,它属于某一个类,可以由系统自动生成。也可以由程序员编写,程序员根据初始化的要求设计构造函数及函数参数。 构造函数是一种特殊的成员函数,在程序中不需要写调用语句,在系统建立对象时由系统自觉调用执行。 构造函数的特点: 构造函数的名字与它的类名必须相同 它没有类型,也不返回值 它可以带参数,也可以不带参数 include <iostream>using namespace std;class Time {public:Time() {hour = 0;minute = 0;sec = 0;}void set_time();void show_time();private:int hour;int minute;int sec;};int main() {Time t1;t1.set_time();t1.show_time();Time t2;t2.show_time();return 0;}void Time::set_time() {cin >> hour;cin >> minute;cin >> sec;}void Time::show_time() {cout << hour << ":" << minute << ":" << sec << endl;} 在类Time中定义了构造函数Time,它与所在的类同名。在建立对象时自动执行构造函数,该函数的作用是为对象中的每个数据成员赋初值0。注意只有执行构造函数时才能为数据成员赋初值。 程序运行时首先建立对象t1,并对t1中的数据成员赋初值0,然后执行t1.set_time函数,从键盘输入新值给对象t1的数据成员,再输出t1的数据成员的值。接着建立对象t2,同时对t2中的数据成员赋初值0,最后输出t2的数据成员的初值。程序运行情况如下: 也可以在类内声明构造函数然后在类外定义构造函数。将程序修改为Time();然后在类外定义构造函数: Time::Time() {hour = 0;minute = 0;sec = 0;} 关于构造函数的使用,说明如下: 什么时候调用构造函数?当函数执行到对象定义语句时建立对象,此时就要调用构造函数,对象就有了自己的作用域,对象的生命周期开始了。 构造函数没有返回值,因此不需要在定义中声明类型。 构造函数不需要显式地调用,构造函数是在建立对象时由系统自动执行的,且只执行以此。构造函数一般定义为public。 在构造函数中除了可以对数据成员赋初值,还可以使用其他语句。 如果用户没有定义构造函数,C++系统会自动生成一个构造函数,而这个函数体是空的,不执行初始化操作。 3.带形参数的构造函数 (1)含义 可以采用带形参数的构造函数,在调用不同对象的构造函数时,从外边将不同的数据传递给构造函数,实现不同对象的初始化。 构造函数的首部的一般格式为:构造函数名(类型 形参1,类型 形参2,……)。在定义对象时指定实参,定义对象的格式为:类名 对象名(实参1,实参2,……)。 (2)【例3.2】 有两个长方柱,其长、宽、高分别为:(1)12,25,30(2)15,30,21编写程序,在类中用带参数的构造函数,计算它们的体积。 分析:可以在类中定义一个计算长方体体积的成员函数计算对象的体积。 include<iostream>using namespace std;class Box{public:Box(int,int,int); //声明int volume();private:int height;int width;int length;};Box::Box(int h,int w,int len) //长方体构造函数{height=h;width=w;length=len;}int Box::volume() //计算长方体体积{return(heightwidthlength);}int main(){Box box1(12,25,30); //定义对象box1cout<<"box1体积="<<box1.volume()<<endl;Box box2(15,30,21); //定义对象box2cout<<"box2体积="<<box2.volume()<<endl;return 0;} 【注】 带形参的构造函数在定义对象时必须指定实参 用这种方法可以实现不同对象的初始化 4.用参数初始化表对数据成员初始化 C++提供了参数初始化表的方法对数据成员初始化。这种方法不必再构造函数内对数据成员初始化,在函数的首部就能实现数据成员初始化。 函数名(类型1 形参1,类型2 形参2): 成员名1(形参1),成员名2(形参2){ } 功能:执行构造函数时,将形参1的值赋予成员1,将形参2的值赋予成员2,形参的值由定义对象时的实参值决定。此时定义对象的格式依然是带实参的形式:类名 对象名(实参1,实参2); 例:定义带形参初始化表的构造函数 Box::Box(int h,int w,int len):height(h),width(w),length(len){}//定义对象:Box box1(12,25,30);//……Box box2(15,30,21); 5.构造函数的重载 (1)含义 构造函数也可以重载。一个类可以有多个同名构造函数,函数参数的个数、参数的类型各不相同。 (2)【例3.3】 在【例3.2】的基础上定义两个构造函数,其中一个无参数,另一个有参数 include <iostream>using namespace std;class Box {public:Box();Box(int h, int w, int len): height(h), width(w), length(len) {}int volume();private:int height;int width;int length;};Box::Box() {height = 10;width = 10;length = 10;}int Box::volume() {return (height width length);}int main() {Box box1;cout << "box1 体积" << box1.volume() << endl;Box box2(15, 30, 25);cout << "box2 体积" << box2.volume() << endl;return 0;} (3)说明 不带形参的构造函数为默认构造函数,每个类只有一个默认构造函数,如果是系统自动给的默认构造函数,其函数体是空的 虽然每个类可以包含多个构造函数,但是创建对象时,系统仅执行其中一个 6.使用默认参数值的构造函数 (1)含义 C++允许在构造函数里为形参指定默认值,如果创建对象时,未给出相应的实参时,系统将用形参的默认值为形参赋值。 (2)格式 函数名(类型 形参1=常数,类型 形参2=常数,……); (3)【例3.4】 将【例3.3】中的构造函数改用带默认值的参数,长、宽、高的默认值都是10 include <iostream>using namespace std;class Box {public:Box(int w = 10, int h = 10, int len = 10);int volume();private:int height;int width;int length;};Box::Box(int w, int h, int len) {height = h;width = w;length = len;}int Box::volume() {return (height width length);}int main() {Box box1;cout << "box1 体积" << box1.volume() << endl;Box box2(15);cout << "box2 体积" << box2.volume() << endl;Box box3(15, 30);cout << "box3 体积" << box3.volume() << endl;Box box4(15, 30, 20);cout << "box4 体积" << box4.volume() << endl;return 0;} (4)说明 如果在类外定义构造函数,应该在声明构造函数时指定默认参数值,再定以函数时不再指定默认参数值 在声明构造函数时,形参名可以省略。例如:Box(int 10,int 10,int 10); 如果构造函数的所有形参都指定了默认值,在定义对象时,可以指定实参也可不指定实参。由于不指定实参也可以调用构造函数,因此全部形参都指定了默认值的构造函数也属于默认构造函数。为了避免歧义,不允许同时定义不带形参的构造函数和全部形参都指定默认值的构造函数。 不能同时使用重载构造函数和带默认值的构造函数 二、析构函数 1.含义 析构函数也是个特殊的成员函数,它的作用与构造函数相反,当对象的生命周期结束时,系统自动调用析构函数,收回对象占用的内存空间。 2.执行析构函数的时机 在一个函数内定义的对象当这个函数结束时,自动执行析构函数释放对象 static局部对象要到main函数结束或执行exit命令时才自动执行析构函数释放对象 全局对象(在函数外定义的对象)当main函数结束或执行exit命令时自动执行析构函数释放对象 如果用new建立动态对象,用delete时自动执行析构函数释放对象 3.特征 以~符号开始后跟类名 析构函数没有数据类型、返回值、形参。由于没有形参所以析构函数不能重载。一个类只有一个析构函数 如果程序员没有定义析构函数,C++编译系统会自动生成一个析构函数 【注】析构函数除了释放对象(资源)外,还可以执行程序员在最后一次适用对象后希望执行的任何操作。例如输出有关的信息。 4.【例3.5】包含构造函数和析构函数的C++程序 include <iostream>include <string>using namespace std;class Student {public:Student(int n, string nam, char s) {num = n;name = nam;sex = s;cout << "Constructor called." << endl;}~Student() {cout << "Destructor called." << endl;}void display() {cout << "num:" << num << endl;cout << "name:" << name << endl;cout << "sex:" << sex << endl;}private:int num;string name;char sex;};int main() {Student stud1(10010, "wang_li", 'f');stud1.display();Student stud2(10011, "zhang_han", 'm');stud2.display();return 0;}//main函数前声明的类其作用域是全局的 三、调用构造函数和析构函数的顺序 1.同一类存储类别的对象 一般情况下,调用析构函数的次序与调用构造函数的次序恰好相反:最先调用构造函数的对象,最后调用析构函数;最后调用构造函数的对象,最先调用析构函数。可简记为:先构造的后析构,后构造的先析构。它相当于一个栈,后进先出。 2.全局范围内定义的对象 在全局范围内定义的对象(在所有函数之外定义的对象),在文件中的所有函数(包括主函数)执行前调用构造函数。当主函数结束或执行exit函数时,调用析构函数。 3.局部自动对象 如果定义局部自动对象(在函数内定义对象),在创建对象时调用构造函数。如多次调用对象所在的函数,则每次创建对象时都调用构造函数。在函数调用结束时调用析构函数。 4.静态局部对象 如果在函数中定义静态局部对象,则在第一次调用该函数建立对象时调用构造函数,但在主函数结束或调用exit函数时才调用析构函数。 5.例 void fun(){student st1; //定义局部自动对象static student st2; //定义静态局部对象...} 对象st1是每次调用函数fun时调用构造函数。在函数fun结束时调用析构函数。 对象st2是第一次调用函数fun时调用构造函数,在函数fun结束时并不调用析构函数,到主函数结束时才调用析构函数 四、对象数组 1.含义 类是一种特殊的数据类型,它当然是C++的合法类型,自然可以定义对象数组。在一个对象数组中各个元素都是同类对象。例如一个班级有50个同学,每个学生有学号、年龄、成绩等属性,可以为这个班级建立一个对象数组,数组包括了50个元素:student std[50];。 可以这样建立构造函数:student::student(int 1001,int 18,int 60);。 在建立数组时,同样要调用构造函数。上面的数组有50个元素,要调用50次构造函数。如果构造函数有多个参数,C++要求:在等号后的花括号中为每个对象分别写出构造函数并指定实参。格式为: student st[n]={ student(实参1,实参2,实参3); …… student(实参1,实参2,实参3); }; 假定对象有三个数据成员:学号、年龄、成绩。下面定义有三个学生的对象数组: student st[3]={ student(1001,18,87); student(1002,19,76); student(1003,18,80); };//构造函数带实参 在建立对象数组时,分别调用构造函数,对每个对象初始化。每个元素的实参用括号括起来,实参的位置与构造函数形参的位置一一对应,不会混淆。 2.【例3.6】 include <iostream>using namespace std;class Box {public:Box(int h = 10, int w = 12, int len = 15): height(h), width(w), length(len) {} //int volume();private:int height;int width;int length;};int Box::volume() {return (height width length);}int main() {Box a[3] = {Box(10, 12, 15), Box(15, 18, 20), Box(16, 20, 26)};cout << "a[0]的体积是" << a[0].volume() << endl;cout << "a[1]的体积是" << a[1].volume() << endl;cout << "a[2]的体积是" << a[2].volume() << endl;return 0;}//每个数组元素是一个对象 五、对象指针 指针的含义是内存单元的地址,可以指向一般的变量,也可以指向对象。 1.指向对象的指针 对象要占据一片连续的内存空间,CPU实际都是按地址访问内存,所以对象在内存的其实地址是CPU确定对象在内存中位置的依据。这个起始地址称为对象指针。 C++的对象也可以参加取地址运算:&对象名。运算的结果是该对象的起始地址,也称对象的指针,要用与对象类型相同的指针变量保存运算的结果。 C++中定义对象的指针变量与定义其他的指针变量相似,格式如下:类名 变量名表。类名表示对象所属的类,变量名按标识符规则取名,两个变量名之间用逗号分隔。定义好指针变量后,必须先给赋予合法的地址后才能使用。 例如定义如下一个类: class Time {public:Time() {hour = 0;minute = 0;sec = 0;}void set_time();void show_time();private:int hour;int minute;int sec;};void Time::set_time() {cin >> hour;cin >> minute;cin >> sec;}void Time::show_time() {cout << hour << ":" << minute << ":" << sec << endl;} 在此基础上,有如下语句: Time pt; //定义pt是指向Time类对象的指针Time t1; //定义Time类对象t1pt=&t1; //将对象t1的地址赋予pt 程序在此基础上就可以用指针变量访问对象的成员。 (pt).hour;pt->hour;(pt).show_time();pt->show_time(); 2.指向对象成员的指针 (1)含义 对象由成员组成。对象占据的内存区是各个数据成员占据的内存区的总和。对象成员也有地址,即指针。这指针分指向数据成员的指针和指向成员函数的指针。 (2)指向对象公有数据成员的指针 定义数据成员的指针变量:数据类型 指针变量名(这里的数据类型是数据成员的数据类型) 计算公有数据成员的地址:&对象名.成员名 Time t1;int p1; //定义一个指向整型数据的指针变量p1=&t1.hour; //假定hour是公有成员cout<<p1<<endl; (3)指向对象成员函数的指针 定义指向成员函数的指针变量:数据类型(类名::变量名)(形参表); 数据类型是成员函数的类型;类名是对象所属的类;变量名按标识符取名;形参表:指定成员函数的形参表(形参个数、类型) 取成员函数的地址:&类名::成员函数名 给指针变量赋初值:指针变量名=&类名::成员函数名; 用指针变量调用成员函数:(对象名.指针变量名)([实参表]); 对象名:指定调用成员函数的对象;:明确其后的是一个指针变量;实参表:与成员函数的形参表对应,如无形参,可以省略实参表 (4)【例3.7】有关对象指针的使用方法 include <iostream>using namespace std;class Time {public:Time(int, int, int);int hour;int minute;int sec;void get_time();};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}void Time::get_time() {cout << hour << ":" << minute << ":" << sec << endl;}int main() {Time t1(10, 13, 56);int p1 = &t1.hour; //定义指向数据成员的指针p1cout << p1 << endl;t1.get_time(); //调用成员函数Time p2 = &t1; //定义指向对象t1的指针p2p2->get_time(); //用对象指针调用成员函数void(Time::p3)(); //定义指向成员函数的指针p3 = &Time::get_time; //给成员函数的指针赋初值(t1.p3)(); //用指向成员函数的指针调用成员函数return 0;} 【注】代码的34,35行可合并为:void(Time::p3)=&Time::get_time; 3.this指针 一个类的成员函数只有一个内存拷贝。类中不论哪个对象调用某个成员函数,调用的都是内存中同一个成员函数代码。例如Time类一个成员函数: void Time::get_time(){cout<<hour<<":"<<minute<<":"<<sec<<endl;}t1.get_time();t2.get_time(); 当不同对象的成员函数访问数据成员时,怎么保证访问的就是指定对象的数据成员?其实每个成员函数中都包含一个特殊的指针,他的名字是this指针。它是指向本类对象的指针。当对象调用成员函数时,它的值就是该对象的起始地址。所以为了区分不同对象访问成员函数,语法要求的调用成员函数的格式是:对象名.成员函数名(实参表)。从语法上明确是对象名所指的对象调用成员函数。This指针是隐式使用的,在调用成员函数时C++把对象的地址作为实参传递给this指针。例如成员函数定义如下: int Box::volume(){return(heightwidthlength);} C++编译成: int Box::volume(this){return(this->heightthis->widththis->length);} 对于计算长方体体积的成员函数volume,当对象调用它时,就把对象地址给this指针,编译程序将的地址作为实参调用成员函数:a.volume(&a);。实际上函数是计算(this->height)(this->width)(this->length),这时就等价计算(a.height)(a.width)(a.length)。 可以用(this)表示调用成员函数的对象。(this)就是this所指的对象。如前面的计算长方体体积的函数中return语句可以写成:return((this).height(this).width(this).length);注意,this两侧的括号不能省略。 C++通过编译程序,在对象调用成员函数时,把对象的地址赋予this指针,用this指针指向对象,实现了用同一个成员函数访问不同对象的数据成员。 六、共用数据的保护 如果既希望数据在一定范围内共享,又不愿它被随意修改,从技术上可以把数据指定为只读型的。C++提供const手段,将数据、对象、成员函数指定为常量,从而实现了只读要求,达到保护数据的目的。 1.常对象 定义格式: const 类名 对象名(实参表);或 类名 const 对象名(实参表); 把对象定义为常对象,对象中的数据成员就是常变量,在定义时必须带实参作为数据成员的初值,在程序中不允许修改常对象的数据成员值。 如果一个常对象的成员函数未被定义为常成员函数(除构造函数和析构函数外),则对象不能调用这样的函数。 const Time t1(10,16,36);t1.get_time();//错误,不能调用 为了访问常对象中的数据成员,要定义常成员函数。 void get_time() const 如果在常对象中要修改某个数据成员,C++提供了指定可变的数据成员方法。 格式:mutable 类型 数据成员 在定义数据成员时加mutable后,将数据成员声明为可变的数据成员,就可以用声明为const的成员函数修改它的值。 2.常对象成员 可以在声明普通对象时将数据成员或成员函数声明为常数据成员或常成员函数。 (1)常数据成员 格式: const 类型 数据成员名 将类中的数据成员定义为具有只读的性质。注意只能通过带参数初始表的构造函数对常数据成员进行初始化。例如: const int hour;Time::Time(int h){hour=h;...//错误}Time::Time(int h):hour(h){}//正确 在类中声明了某个常数据成员后,该类中每个对象的这个数据成员的值都是只读的,而每个对象的这个数据成员的值可以不同,由定义对象时给出。 (2)常成员函数 定义格式:类型 函数名 (形参表)const const是函数类型的一部分,在声明函数原型和定义函数时都要用const关键字。 【注1】const是函数类型的一个组成部分,因此在函数的实现部分也要使用关键字const。常成员函数不能修改对象的数据成员,也不能调用该类中没有由关键字const修饰的成员函数,从而保证了在常成员函数中不会修改数据成员的值。如果一个对象被说明为常对象,则通过该对象只能调用它的常成员函数。 【注2】一般成员函数可以访问或修改本类中非const数据成员。而常成员函数只能读本类中的数据成员,而不能写他们。 数据成员 非const成员函数 const成员函数 非const的数据成员 可以引用,也可以改变值 可以引用,但不可以改变值 const数据成员 可以引用,但不可以改变值 可以引用,但不可以改变值 const对象的数据成员 不允许引用和改变值 可以引用,但不可以改变值 常成员函数的使用: 如果类中有部分数据成员的值要求为只读,可以将它们声明为const,这样成员函数只能读这些数据成员的值,但不能修改它们的值 如果所有数据成员的值为只读,可将对象声明为const,在类中必须声明const成员函数,常对象只能通过常成员函数读数据成员 常对象不能调用非const成员函数 【注】如果常对象的成员函数未加const,编译系统将其当作非const成员函数;常成员函数不能调用非const成员函数 3.指向对象的常指针 如果在定义指向对象的指针时,使用了关键字const,他就是一个常指针,必须在定义时对其初始化,并且在程序运行中不能再修改指针的值。 格式:const 指针变量名=对象地址 Time t1(10,12,15),t2;Time const p1=&t1;//在此后,不能修改p1Time const p1=&t2;//错误语句 指向对象的常指针,在程序运行中始终指向的是同一个对象。即指针变量的值始终不变,但它所指对象的数据成员值可以修改。当需要将一个指针变量固定地与一个对象相联系时,就可将指针变量指定为const。往往用常指针作为函数的形参,目的是不允许在函数中修改指针变量的值,让它始终指向原来的对象。 4.指向常对象的指针变量 5.对象的常引用 (1)含义 前面学过引用是传递参数的有效方法。用引用形参时,形参变量与实参变量是同一个变量,在函数内修改引用形参也就是修改实参变量。如果用引用形参又不想让函数修改实参,可以使用常引用机制。 (2)格式 const 类名 &形参变量名 (3)【例3.8】对象的引用 include <iostream>using namespace std;class Time {public:Time(int, int, int);int hour;int minute;int sec;};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}void fun(Time &t) {t.hour = 18;}int main() {Time t1(10, 13, 56);fun(t1);cout << t1.hour << endl;return 0;} //如果用引用形参又不想让函数修改实参,可以使用常引用机制include <iostream>using namespace std;class Time {public:Time(int, int, int);void fun(int &t) {hour = t;t = 18;}int hour;int minute;int sec;};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}int main(int argc, char argc[]) {int x = 15;Time t1(10, 13, 56);t1.fun(x);cout << t1.hour << endl;cout << x << endl;return 0;} 6.const型数据小结 七、对象的动态建立与释放——动态建立对象 C++提供了new和delete运算符,实现动态分配、回收内存。他们也可以用来动态建立对象和释放对象。 格式:new 类名; 功能:在堆里分配内存,建立指定类的一个对象。如果分配成功,将返回动态对象的起始地址(指针);如不成功,返回0.为了保存这个指针,必须事先建立以类名为类型的指针变量。 格式:类名 指针变量名 Box pt;pt=new Box;//如果分配成功,就可以用指针变量pt访问动态对象的数据成员cout<<pt->height;cout<<pt->volume(); 当不再需要使用动态变量时,必须用delete运算符释放内存。 格式:delete 指针变量(存放的是用new运算返回的指针) 八、对象的赋值和复制 1.对象的赋值 (1)含义 如果一个类定义了两个或多个对象,则这些同类对象之间可以相互赋值。这里所指的对象的值含义是对象中所有数据成员的值。对象1、对象2都是已建立好的同类对象。 格式:对象1=对象2; (2)【例3.9】对象的赋值 include <iostream>using namespace std;class Box {public:Box(int = 10, int = 10, int = 10);int volume();private:int height;int width;int length;};Box::Box(int h, int w, int len) {height = h;width = w;length = len;}int Box::volume() {return (height width length);}int main() {Box box1(15, 30, 25), box2;cout << "box1 体积=" << box1.volume() << endl;box2 = box1;cout << "box2 体积=" << box2.volume() << endl;return 0;} (3)说明 对象的赋值只对数据成员操作 数据成员中不能含有动态分配的数据成员 2.对象的复制 (1)含义 对象赋值的前提是对象1和对象2是已经建立的对象。C++还可以按照一个对象克隆出另一个对象(从无到有),这就是复制对象。复制对象是创建对象的另一种方法(以前学过的是定义对象)。创建对象必须调用构造函数,复制对象要调用复制构造函数。以Box类为例,复制构造函数的形式是: Box::Box(const Box &b){height=b.height;width=b.width;length=b.length;} 复制构造函数只有一个参数,这个参数是本类的对象,且采用引用对象形式。为了防止修改数据,加const限制。构造函数的内容就是将实参对象的数据成员值赋予新对象对应的数据成员,如果程序中未定义复制构造函数,编译系统将提供默认的复制构造函数,复制类中的数据成员。 复制对象有两种格式: 类名 对象2(对象1);按对象1复制对象2 类名 对象2=对象1,对象3=对象1,……按对象1复制对象2、对象3 (2)【例】用复制对象的方法创建Box类的对象(用默认复制构造函数) //include "stdafx.h"include <iostream>using namespace std;class Box {public:Box(int = 10, int = 10, int = 10);int volume();private:int height;int width;int length;};Box::Box(int h, int w, int len) {height = h;width = w;length = len;}int Box::volume() {return (height width length);}int main() {Box box1(15, 30, 25);cout << "box1 体积=" << box1.volume() << endl;//Box box2=box1,box3=box2;Box box2(box1), box3(box2);cout << "box2 体积=" << box2.volume() << endl;cout << "box3 体积=" << box3.volume() << endl;return 0;} (3)说明 在以下情况调用复制构造函数: 在程序里用复制对象格式创建对象 当函数的参数是对象。调用函数时,需要将实参对象复制给形参对象,在此系统将调用复制构造函数 void fun(Box b){...}int main(){Box box1(12,15,18);fun(box1);return 0;} 在函数返回值是类的对象时,需要将函数里的对象复制一个临时对象当作函数值返回 Box f(){Box box1(12,15,18);return box1;}int main(){Box box2;box2=f();} 九、静态成员 C++用const保护数据对象不被修改,在实际中还需要共享数据,C++怎样提供数据共享机制?C++静态成员、友元实现对象之间、类之间的数据共享。 1.静态数据成员 (1)定义格式 static 类型 数据成员名 class Box{public:Box(int=10,int=10,int=10);int volume();private:static int height;int width;int length;}; (2)特性 设Box有n个对象box1..boxn。这n个对象的height成员在内存中共享一个整型数据空间。如果某个对象修改了height成员的值,其他n-1个对象的height成员值也被改变,从而达到n个对象共享height成员值的目的。 (3)说明 由于一个类的所有对象共享静态数据成员,所以不能用构造函数为静态数据成员初始化,只能在类外专门对其初始化。如果程序未对静态数据成员赋初值,则编译系统自动用0为它赋初值 格式:数据类型 类名::静态数据成员名=初值; 即可已用对象名引用静态成员,也可以用类名引用静态成员 静态数据成员在对象外单独开辟内存空间,只要在类中定义了静态成员,即使不定义对象,系统也为静态成员分配内存空间,可以被引用 在程序开始时为静态成员分配内存空间,直到程序结束才释放内存空间 静态数据成员作用域是它的类的作用域(如果在一个函数内定义类,他的静态数据成员作用域就是这个函数)在此范围内可以用“类名::静态成员名”的形式访问静态数据成员 (4)【例3.10】引用静态数据成员 include <iostream>using namespace std;class Box {public:Box(int, int);int volume();static int height;int width;int length;};Box::Box(int w, int len) {width = w;length = len;}int Box::volume() {return (height width length);}int Box::height = 10;int main() {Box a(15, 20), b(25, 30);cout << a.height << endl;cout << b.height << endl;cout << Box::height << endl;cout << a.volume() << endl;cout << b.volume() << endl;return 0;} 2.静态成员函数 (1)含义 C++提供静态成员函数,用它访问静态数据成员,静态成员函数不属于某个对象而属于类。 类中的非静态成员函数可以访问类中所有数据成员;而静态成员函数可以直接访问类的静态成员,不能直接访问非静态成员。 静态成员函数定义格式: static 类型 成员函数(形参表){……} 调用公有静态成员函数格式: 类名::成员函数(实参表) 引用方式 静态数据成员 非静态数据成员 静态成员函数 成员名 对象名.成员名 非静态成员函数 成员名 成员名 【注】静态成员函数不带this指针,所以必须用对象名和成员运算符.访问非静态成员;而普通成员函数有this指针,可以在函数中直接引用成员名。 (2)【例3.11】关于引用非静态成员和静态成员的具体方法 class Student {private:int num;int age;float score;static float sum;static int count;public:Student(int, int, int);void total();static float average();};Student::Student(int m, int a, int s) {num = m;age = a;score = s;}void Student::total() {sum += score;count++;}float Student::average() {return (sum / count);}float Student::sum = 0;int Student::count = 0;int main() {Student stud[3] = {Student(1001, 18, 70), Student(1002, 19, 79), Student(1005, 20, 98)};int n;cout << "请输入学生的人数:";cin >> n;for (int i = 1; i < n; i++)stud[i].total();cout << n << "个学生的平均成绩是:"cout << Student::average() << endl;return 0;} (3)【例】具有静态数据成员的point类 include <iostream>using namespace std;class Point {private:int X, Y;static int countP;public:Point(int xx = 0, int yy = 0) {X = xx;Y = yy;countP++;}Point(Point &p); //复制构造函数int GetX() {return X;}int GetY() {return Y;}int GetC() {cout << "Object id=" << countP << endl;return 0;} };Point::Point(Point &p) {X = p.X;Y = p.Y;countP++;}int Point::countP = 0;int main() {Point A(4, 5);cout << "Point A," << A.GetC() << "," << A.GetY();A.GetC();Point B(A);cout << "Point B," << B.GetC() << "," << B.GetY();B.GetC();return 0;} (4)静态成员函数举例 include <iostream>using namespace std;class application {private:static int global;public:static void f();static void g();};int application::global = 0;void application::f() {global = 5;}void application::g() {cout << global << endl;}int main() {application::f();application::g();return 0;} class A{private:int x; //非静态成员public:static void f(A a);};void A::f(A a){cout<<x; //对x的引用是错误的cout<<a.x; //正确} (5)具有静态数据、函数成员的Point类 include <iostream>using namespace std;class Point { //point类声明private: //私有数据成员int X, Y;static int countP;public: //外部接口Point(int xx = 0, int yy = 0) {X = xx;Y = yy;countP++;}Point(Point &p); //复制构造函数int GetX() {return X;}int GetY() {return Y;}static int GetC() {cout << "Object id=" << countP << endl;return 0;} };Point::Point(Point &p) {X = p.X;Y = p.Y;countP++;}int Point::countP = 0;int main() //主函数实现{ Point A(4, 5); //声明对象Acout << "Point A," << A.GetC() << "," << A.GetY();A.GetC(); //输出对象号,对象名引用Point B(A); //声明对象Bcout << "Point B," << B.GetC() << "," << B.GetY();Point::GetC(); //输出对象号,类名引用return 0;} (6)静态成员函数、静态数组及其初始化 include <iostream>include <stdio.h>using namespace std;class A {static int a[20];int x;public:A(int xx = 0) {x = xx;}static void in();static void out();void show() {cout << "x=" << x << endl;} };int A::a[20] = {0, 0};void A::in() {cout << "input a[20]:" << endl;for (int i = 0; i < 20; ++i)cin >> a[i];}void A::out() {for (int i = 0; i < 20; ++i)cout << "a[" << i << "]=" << a[i] << endl;}int main() {A::in();A::out();A a;a.out();a.show();return 0;} 十、友元 除了在同类对象之间共享数据外,类和类之间也可以共享数据。类的私有成员只能被类的成员函数访问,但是有时需要在类的外部访问类的私有成员,C++通过友元的手段实现这一特殊要求。友元可以是不属于任何类的一般函数,也可以是另一个类的成员函数,还可以是整个的一个类(这个类中的所有成员函数都可以成为友元函数)。 友元是C++提供的一种破坏数据封装和数据隐藏的机制。为了保证数据的完整性及数据封装与隐藏的原则,建议尽量不使用或少使用友元。 1.友元函数 (1)含义 如果在A类外定义一个函数(它可以是另一个类的成员函数,也可以是一个普通函数),在A类中声明该函数是A的友元函数后,这个函数就能访问A类中的所有成员。 (2)格式 friend 类型 类1::成员函数x(类2 &对象); friend 类型 函数y(类2 &对象); //类1是另一个类的类名,类2是本类的类名 功能:第一种形式在类2中声明类1的成员函数x为友元函数。第二种形式在类2中声明一个普通函数y是友元函数。 友元函数内访问对象的格式: 对象名.成员名 因为友元不是成员函数,它不属于类,所以它访问对象时必须冠以对象名。定义友元函数时形参通过定义引用对象,这样在友元函数内就能访问实参对象了。 (3)【例3.12】将普通函数声明为友元函数 include <iostream>using namespace std;class Time {public:Time(int, int, int);friend void display(Time &);private:int hour;int minute;int sec;};Time::Time(int h, int m, int s) {hour = h;minute = m;sec = s;}void display(Time &t) {cout << t.hour << ":" << t.minute << ":" << t.sec << endl;}int main() {Time t1(10, 13, 56);display(t1);return 0;} 【例】使用友元函数计算两点距离 include <iostream>include <cmath>using namespace std;class Point {public:Point(int xx = 0, int yy = 0) {X = xx;Y = yy;}int GetX() {return X;}int GetY() {return Y;}friend double Distance(Point &a, Point &b);private:int X, Y;};double Distance(Point &a, Point &b) {double dx = a.X - b.X;double dy = b.Y - b.Y;return sqrt(dx dx + dy dy);}int main() {Point p1(3.0, 5.0), p2(4.0, 6.0);double d = Distance(p1, p2);cout << "The distance is " << d << endl;return 0;} include <iostream>include <math.h>using namespace std;class TPoint {private:double x, y;public:TPoint(double a, double b) {x = a;y = b;cout << "点:(" << x << "," << y << ")" << endl;}friend double distance(TPoint &a, TPoint &b) {return sqrt((a.x - b.x) (a.x - b.x) + (a.y - b.y) (a.y - b.y));} };int main(int argc, char argv[]) {TPoint myp1(2.1, 1.3), myp2(5.4, 6.5);cout << "两点之间的距离为:";cout << distance(myp1, myp2) << endl;return 0;} (4)友元成员函数 【例3.13】将成员函数声明为友元函数 例子中有两个类Time和Date。其中Time类里定义了成员函数void display(Date &),他除了显示时间外还要显示日期,这个日期通过引用形参访问。在Date类中将Time类的display成员函数定义为友元函数,允许display访问Date类的所有私有数据成员。 include <iostream>using namespace std;class Date;class Time {private:int hour;int minute;int sec;public:Time(int, int, int);void display(const Date &);};class Date {private:int month;int day;int year;public:Date(int, int, int);friend void Time::display(const Date &);};Time::Time(int h, int m, int s) hour = h;minute = m;sec = s;}void Time::display(const Date &da) {cout << da.month << "/" << da.day << "/" << da.year << endl;cout << hour << ":" << minute << ":" << sec << endl;}Date::Date(int m, int d, int y) {month = m;day = d;year = y;}int main() {Time t1(10, 13, 56);Date d1(12, 25, 2004);t1.display(d1);return 0;} 【注1】友元是单向的,此例中声明Time的成员函数display是Date类的友元,允许它访问Date类的所有成员,但不等于说Date类的成员函数也是Time类的友元。 【注2】一个函数(包括普通函数和成员函数)可以被多个类声明为“朋友”,这样就可以引用多个类中的私有数据 【注3】例如可以将例3.13程序中的display函数作为类外的普通函数,分别在Time和Date类中将display声明为友元。Display就可以分别引用Time和Date类的对象的私有数据成员。输出年月日和时分秒。 2.友元类 C++允许将一个类声明为另一个类的友元。假定A类是B类的友元类,A类中所有的成员函数都是B类的友元函数,在B类中声明A类为友元类的格式:friend A; 【注1】友元关系是单向的,不是双向的 【注2】友元关系不能传递 【注3】实际中一般不把整个类声明友元类,而只是将确有需要的成员函数声明为友元函数 include <iostream>include <math.h>using namespace std;class B;class A {private:int x;public:A() {x = 3;}friend class B;};class B {public:void disp1(A temp) {temp.x++;cout << "disp1:x" << temp.x << endl;}void disp2(A temp) {temp.x--;cout << "disp2:x" << temp.x << endl;} };int main(int argc, char argv[]) {A a;B b;b.disp1(a);b.disp2(a);return 0;} class Student; //前向声明,类名声明class Teacher{privated:int noOfStudents;Student pList[100];public:void assignGrades(Student &s); //赋成绩void adjustHours(Student &s); //调整学时数};class Student{privated:int hours;float gpa;public:friend class Teacher;};void Teacher::assignGrades(Student &s){...};void Teacher::adjustHours(Student &s){...}; //函数定义必须在Student定义之后 十一、类模板 1.含义 对于功能相同而只是数据类型不同的函数,不必须定义出所有函数,我们定义一个可对任何类型变量操作的函数模板。对于功能相同的类而数据类型不同,不必定义出所有类,只要定义一个可对任何类进行操作的类模板。 例如定义比较两个整数的类和比较两个浮点数的类,这两个类做的工作是相似的,所以可以用类模板,减少工作量。 class Compare_int{private:int x,y;public:Compare_int(int a,int b){x=a;y=b;}int max(){return (x>y)?x:y;}int min(){return (x<y)?x:y;} };class Compare_float{private:float x,y;public:Compare_float(float a,float b){x=a;y=b;}float max(){return (x>y)?x:y;}float min(){return (x<y)?x:y;} }; 2.定义类模板的格式 template <class 类型参数名> class 类模板名 {……} 类型参数名:按标识符取名。如有多个类型参数,每个类型参数都要以class为前导,两个类型参数之间用逗号分隔 类模板名:按标识符取名 类模板{...}内定义数据成员和成员函数的规则:用类型参数作为数据类型,用类模板名作为类 template<class numtype>class Compare{private:numtype x,y;public:Compare(numtype a,numtype b){x=a,y=b;}numtype max(){return (x>y)?x:y;}numtype min(){return (x<y)?x:y;} }; 3.在类模板外定义成员函数的语法 类型参数 类模板名<类型参数>::成员函数名(形参表){……} 例如在类模板外定义max和min成员函数 template<class numtype>class Compare{public:Compare(numtype a,numtype b){x=a,y=b;}numtype max();numtype min();private:numtype x,y;};numtype Compare<numtype>::max(){return(x>y)?x:y;}numtype Compare<numtype>::min(){return(x<y)?x:y;} 4.使用类模板时,定义对象的格式 类模板名 <实际类型名>对象名; 类模板名 <实际类型名>对象名(实参表); 例如:Compare <int>cmp2(4,7) 在编译时, 编译系统用int取代类模板中的类型参数numtype,就把类模板具体化了。这时Compare<int>将相当于Compare_int类。 5.【例3.14】声明类模板,实现两个整数、浮点数和字符的比较,求出大数和小数 include <iostream>using namespace std;template<class numtype>class Compare {private:numtype x, y;public:Compare(numtype a, numtype b) {x = a;y = b;}numtype max() {return (x > y) ? x : y;}numtype min() {return (x < y) ? x : y;} };int main() {Compare<int>cmp1(3, 7);cout << cmp1.max() << "是两个整数中的大数." << endl;cout << cmp1.min() << "是两个整数中的小数." << endl;Compare<float>cmp2(45.78, 93.6);cout << cmp2.max() << "是两个浮点数中的大数." << endl;cout << cmp2.min() << "是两个浮点数中的小数." << endl;Compare<char>cmp3('a', 'A');cout << cmp3.max() << "是两个字符中的大者." << endl;cout << cmp3.min() << "是两个字符中的小者." << endl;return 0;} 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_72318954/article/details/127064376。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-01-29 12:38:23
545
转载
转载文章
...,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除。--><MaxHistory>365</MaxHistory><!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy--><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MB</maxFileSize></timeBasedFileNamingAndTriggeringPolicy></rollingPolicy><!--日志输出格式:%d表示日期时间,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %logger{50} 表示logger名字最长50个字符,否则按照句点分割。 %msg:日志消息,%n是换行符--> <encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern></encoder></appender><!-- logger主要用于存放日志对象,也可以定义日志类型、级别name:表示匹配的logger类型前缀,也就是包的前半部分level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERRORadditivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,false:表示只用当前logger的appender-ref,true:表示当前logger的appender-ref和rootLogger的appender-ref都有效--><!-- <logger name="edu.hyh" level="info" additivity="true"><appender-ref ref="appLogAppender" /></logger> --><!-- root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。 --><root level="debug"><appender-ref ref="stdout" /><appender-ref ref="appLogAppender" /></root></configuration> 2、余额宝代码 package com.zhuguang.jack.controller;import com.alibaba.fastjson.JSONObject;import com.zhuguang.jack.service.OrderService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controller@RequestMapping("/order")public class OrderController {/ @Description TODO @param @return 参数 @return String 返回类型 @throws 模拟银行转账 userID:转账的用户ID amount:转多少钱/@AutowiredOrderService orderService;@RequestMapping("/transfer")public @ResponseBody String transferAmount(String userId, String amount) {try {orderService.updateAmount(Integer.valueOf(amount), userId);}catch (Exception e) {e.printStackTrace();return "===============================transferAmount failed===================";}return "===============================transferAmount successfull===================";} } 消息监听器 package com.zhuguang.jack.listener;import com.alibaba.fastjson.JSONObject;import com.zhuguang.jack.service.OrderService;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import org.springframework.web.client.RestTemplate;import javax.jms.JMSException;import javax.jms.Message;import javax.jms.MessageListener;import javax.jms.ObjectMessage;@Service("queueMessageListener")public class QueueMessageListener implements MessageListener {private Logger logger = LoggerFactory.getLogger(getClass());@AutowiredOrderService orderService;@Transactional(rollbackFor = Exception.class)@Overridepublic void onMessage(Message message) {if (message instanceof ObjectMessage) {ObjectMessage objectMessage = (ObjectMessage) message;try {com.zhuguang.jack.bean.Message message1 = (com.zhuguang.jack.bean.Message) objectMessage.getObject();String userId = message1.getUserId();int count = orderService.queryMessageCountByUserId(userId);if (count == 0) {orderService.updateAmount(message1.getAmount(), message1.getUserId());orderService.insertMessage(message1.getUserId(), message1.getMessageId(), message1.getAmount(), "ok");} else {logger.info("异常转账");}RestTemplate restTemplate = createRestTemplate();JSONObject jo = new JSONObject();jo.put("messageId", message1.getMessageId());jo.put("respCode", "OK");String url = "http://jack.bank_a.com:8080/alipay/order/callback?param="+ jo.toJSONString();restTemplate.getForObject(url,null);} catch (JMSException e) {e.printStackTrace();throw new RuntimeException("异常");} }}public RestTemplate createRestTemplate() {SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();simpleClientHttpRequestFactory.setConnectTimeout(3000);simpleClientHttpRequestFactory.setReadTimeout(2000);return new RestTemplate(simpleClientHttpRequestFactory);} } package com.zhuguang.jack.service;public interface OrderService {public void updateAmount(int amount, String userId);public int queryMessageCountByUserId(String userId);public int insertMessage(String userId,String messageId,int amount,String status);} package com.zhuguang.jack.service;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import org.springframework.web.client.RestTemplate;@Service@Transactional(rollbackFor = Exception.class)public class OrderServiceImpl implements OrderService {private Logger logger = LoggerFactory.getLogger(getClass());@AutowiredJdbcTemplate jdbcTemplate;/ 更新数据库表,把账户余额减去amountd/@Overridepublic void updateAmount(int amount, String userId) {//1、农业银行转账3000,也就说农业银行jack账户要减3000String sql = "update account set amount = amount + ?,update_time=now() where user_id = ?";int count = jdbcTemplate.update(sql, new Object[] {amount, userId});if (count != 1) {throw new RuntimeException("订单创建失败,农业银行转账失败!");} }public RestTemplate createRestTemplate() {SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();simpleClientHttpRequestFactory.setConnectTimeout(3000);simpleClientHttpRequestFactory.setReadTimeout(2000);return new RestTemplate(simpleClientHttpRequestFactory);}@Overridepublic int queryMessageCountByUserId(String messageId) {String sql = "select count() from message where message_id = ?";int count = jdbcTemplate.queryForInt(sql, new Object[]{messageId});return count;}@Overridepublic int insertMessage(String userId, String message_id,int amount, String status) {String sql = "insert into message(user_id,message_id,amount,status) values(?,?,?)";int count = jdbcTemplate.update(sql, new Object[]{userId, message_id,amount, status});if(count == 1) {logger.info("Ok");}return count;} } activemq.xml <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:amq="http://activemq.apache.org/schema/core"xmlns:jms="http://www.springframework.org/schema/jms"xmlns:context="http://www.springframework.org/schema/context"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.1.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.1.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-4.1.xsdhttp://www.springframework.org/schema/jmshttp://www.springframework.org/schema/jms/spring-jms-4.1.xsdhttp://activemq.apache.org/schema/corehttp://activemq.apache.org/schema/core/activemq-core-5.12.1.xsd"><context:component-scan base-package="com.zhuguang.jack" /><mvc:annotation-driven /><amq:connectionFactory id="amqConnectionFactory"brokerURL="tcp://192.168.88.131:61616"userName="system"password="manager" /><!-- 配置JMS连接工长 --><bean id="connectionFactory"class="org.springframework.jms.connection.CachingConnectionFactory"><constructor-arg ref="amqConnectionFactory" /><property name="sessionCacheSize" value="100" /></bean><!-- 定义消息队列(Queue) --><bean id="demoQueueDestination" class="org.apache.activemq.command.ActiveMQQueue"><!-- 设置消息队列的名字 --><constructor-arg><value>zg.jack.queue</value></constructor-arg></bean><!-- 显示注入消息监听容器(Queue),配置连接工厂,监听的目标是demoQueueDestination,监听器是上面定义的监听器 --><bean id="queueListenerContainer"class="org.springframework.jms.listener.DefaultMessageListenerContainer"><property name="connectionFactory" ref="connectionFactory" /><property name="destination" ref="demoQueueDestination" /><property name="messageListener" ref="queueMessageListener" /></bean><!-- 配置JMS模板(Queue),Spring提供的JMS工具类,它发送、接收消息。 --><bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate"><property name="connectionFactory" ref="connectionFactory" /><property name="defaultDestination" ref="demoQueueDestination" /><property name="receiveTimeout" value="10000" /><!-- true是topic,false是queue,默认是false,此处显示写出false --><property name="pubSubDomain" value="false" /></bean></beans> OK~~~~~~~~~~~~大功告成!!!, 如果大家觉得满意并且对技术感兴趣请加群:171239762, 纯技术交流群,非诚勿扰。 本篇文章为转载内容。原文链接:https://blog.csdn.net/luoyang_java/article/details/84953241。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-16 22:34:52
500
转载
转载文章
...的大脑心算5672,注意在计算的过程中不要让自己的大脑去思考其他的任何事情,用尽快的速度计算出结果。当然,你会发现你根本做不到,无论如何你都无法控制那先奇奇怪怪的想法出现在你的大脑里,至于大脑为什么会像你控制的那样去计算5672,接下来我会给出人类的大脑思维模型。 生物的模型 生物的模型分为两部分,一部分我称为确定机,一部分我称为概率机。 确定机 确定机是指只要输入确定,那么就会产生确定输出的部分,而对于输入的概率性则不予考虑。例如,当生物多次看到同一个画面的时候会在大脑里形成同样的图像,因为每次输入的光信号都是一样的,在生物内部进行的信号传递过程也是一样的,所以在大脑里形成的图像输出也是一样的。现在人类所生产的绝大多数工具就是一个确定机的模型,如果相同的输入,不管输入多少次都会得到相同的输出。确定机也是生物模型的基础部分,构成生物的绝大部分,实际上,除了大脑,生物的任何部分都是一个确定机的模型,而大脑也有一部分的确定机模型。对于确定机,所有的内部过程和输出都不会被“灵魂”检阅,当然生物上可以通过解剖或其他更先进的方式去检查生物内部确定机的工作状态。 概率机 概率机是指即使输入确定,输出的确定性也指限制在一定的概率范围之内,会以不同但是给定的概率输出多个输出。当然给定的概率可以是确定机给出的确定概率(只在输入确定的情况下才确定),也可以是概率机给出的概率概率。概率机构成生物的大脑部分,当然一部分低等生物只由确定机构成。对于概率机,有一部分输出会被“灵魂”检阅,而“灵魂”是否检阅取决于“灵魂”本身,当然,对于概率机的工作状态,也可以通过解剖或其他更先进的方式去检查。 生物思考的过程 对于不同的生物,大脑可以同时进行的事情是有限的。就像现在的电脑手机一样,有严格的内存限制,对于大脑来说,同时启用着多个线程,每个线程所占用的内存不同,但是所有线程所占用的内存总和不得超限。对于每个线程,会随机的考虑一些事件,这些事件包括记忆中的事件,和当时正在发生的事件,对于每个事件出现在线程中的概率不同。 不同事件的概率遵循的规律大致有以下几条: 1.对记忆中的事件,事件越久远概率越低。 2.对当时正在发生的事件,概率大致相同。 3.与当时线程中事件有关的事件概率高,无关的概率低。 4.与线程中的事件相关的个数越多,概率越高 5.对不同的心流状态,概率分配有所不同。 6.每个个体对不同的事件有不同的概率分配方案。 7.待补充。 可以说,大脑中的一切过程都是随机的。那这样的话,生物的思考过程究竟如何进行呢?其实很简单,单个概率可能代表随机,但是多个概率就有可能表示必然。我还是举那个5672的例子,为什么你会真的去心算这个结果,大致的过程是这样的,如果大脑的思考频率以毫秒计的话,假设看5672用了200毫秒,其中每毫秒除了这一事件,还有其他的99个事件,那么刚看完就开始计算的概率为1-0.99200=0.8660203251,看完后1秒之内还没有开始计算的概率为0.991000= 4.31712474107 e-5,可以说即使大脑中随机的杂念再多,思考的过程也会如约开始。假设线程中与事件相关的事件出现的概率为0.3,同理,在开始计算后1秒内大部分时间都在思考与计算有关的内容,当然也有可能会走神,即出现大范围的无关事件,但是这只会影响最后计算出结果的时间先后,并不会影响整个过程的进行。这也就是说,大脑的思考过程,其实就是由多个概率所确定的必然事件。 灵魂的旁观者 综上所述,作为个体唯一存在的“灵魂”处在一个旁观者的位置,而所谓的自由意识,主观意识不过是概率机的产物。那么这样就产生了两个问题。 第一个问题,你不觉得“灵魂”所在的肉体更像是一个囚笼吗?“灵魂”可以偶尔窥探外界,但无法做任何事情,只能默默得看着一切发生。尴尬的以为是自己做的,实际上就像看电影,每次看电影的时候,我都会以为我处在电影里面的世界。而现实就是,因为“灵魂”只能看肉体主演的这部“电影”,所以看的入迷了。其实,人类从解放双手,开发智力,使用工具,到探索宇宙,最大的进步莫过于发现自己其实仍处于囚笼之中。要怪就怪这囚笼建造地太过美好。而创建这一囚笼的“上帝”,把我们关在肉体这个囚笼里面,并且把我们的感知限制在有限的范围内,有限的嗅觉,16至20000赫兹的听觉,400纳米到700纳米的视觉,在感知中隔绝了我们对我们的唯一存在——“灵魂”的感知。 第二个问题,对于自己本身来说,表征自己存在的“灵魂”自己是可以确定的,而对于其他人,因为限制了对“灵魂”的感知,所以无法确认别人,别的生物体内这一旁观者的存在。也可以这么理解,你知道自己被关在一间囚笼里面,而不知道隔壁囚笼是否也关了一个存在。那么世界这个大监狱里面,可能只有一小部分,甚至只有你一个孤独的存在。而究竟为何我们或我被困于此,我不得而知,可能就像我们做研究的时候的小白鼠一样,“上帝”也在观察着我们或我的一举一动,这也是我这篇文章取这个题目的原因。小白鼠的逆袭,一开始我只是平凡的活着,说实在的其实做一个平凡人安安稳稳的一生还是很不错的,但是知道了这个囚笼的存在,就总想着打破它,因为在想到可能只有自己一个存在的时候,会是多么的孤独。就像一个人去看电影,哪怕电影的内容再精彩,再引人入胜,但当电影结束的时候,你才发现,原来我是一个人来的呀。 联系作者 有志向联系读者的:1612860@mail.nankai.edu.cn 未完待续。。。 本篇文章相当于《小白鼠的逆袭》的导读,下一篇我会出逆袭第一步:《思考的最简单模型及其编程实现》,可能用C++,也可能用Java,Python,看作者的心情吧。预计近几个月出吧,快则个把月,多则不知道了,毕竟作者本身还是比较忙的,忙七忙八也不知道在忙什么,嗯,就这样。 小号:在有多个游戏账号的前提下,等级高的号叫作大号,等级较低或者新创建的号叫作小号。 ↩︎ https://baijiahao.baidu.com/s?id=1586028525096880374&wfr=spider&for=pc. ↩︎ http://tieba.baidu.com/p/5127924201. ↩︎ http://tieba.baidu.com/p/5127924201. ↩︎ http://www.lwlm.com/sixiangzhexue/201704/840820.htm. ↩︎ 详细讨论请参见:《未来简史:从智人到智神》第三章:人类的特质。 ↩︎ “Unconscious determinants of free decisions in the human brain” in nature neuroscience, http://www.rifters.com/real/articles/NatureNeuroScience_Soon_et_al.pdf. ↩︎ 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_39384184/article/details/79288150。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-02 11:30:59
621
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
crontab -e
- 编辑用户的定时任务计划。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"