NPUCTF HappyCheckInVerrification WP

题目信息

题目链接:https://pan.baidu.com/s/1c_Y0WJmzTdNVyYgjHmUI_Q

提取码: qhh8

Hint:关注西北工业大学信息安全协会微信公众号NWPUSEC


开始着手:

下载得到无后缀名的名叫zip的文件,把后缀名改成.zip,提示文件已损坏,故用16进制文件查看器检查文件(本文使用HxD)。

这是此文件的文件头,可见zip文件的文件头标记504B0304不在文件头部,反而是zip文件的目录结束标识(504B0506)在文件头。把从00000000-00000015的部分移动至文件尾,保存重新打开,就可以看到zip文件的文件了。此zip文件带有伪加密,可以通过搜索的方法搜索zip文件头标记504B0102,在其偏移5字节的地方改为00(要改两处)来去除伪加密。

保存解压即可获得一个mp4视频和一个二维码文件。

视频文件是喜闻乐见的黑人抬棺材的视频,但是在视频中有两个部分被植入了拨号音。截取这段音频降噪,使用dtmf2num工具识别拨号音,得到电话号码13418070885。

再来看二维码:

这是获取的二维码图像,可见二维码的三个定位符均被抹去,所以无法扫描。通过PS添加三个定位符,得到一个二维码。扫描得到以下文字:

flag{this_is_not_flag}三曳所諳陀怯耶南夜缽得醯怯勝數不知喝盧瑟侄盡遠故隸怯薩不娑羯涅冥伊盧耶諳提度奢道盧冥以朋罰所即栗諳蒙集皤夷夜集諳利顛呐寫無怯依奢竟#¥#%E68BBFE4BD9BE68B89E6A0BCE79A84E5A7BFE58ABFE59CA8E69C80E5908E32333333||254333254242254338254342254231254338254345254432254238254643254236254145254239254441254437254234254232254131254236254245253244253244254343254438254330254341254336254435…sadwq#asdsadasf faf$use$dasdasdafafa_$ba##se64$

this is not flag 后面的第一部分哪些诡异的文字,是“与佛论禅”编码。最前面加上“佛曰:”,得到一句话:“都说了这不是佛拉格了”。

第二部分是一段十六进制数,把它转成字符串,得到:“拿佛拉格的姿势在最后”。

第三段也是一串十六进制数,把它转成字符串,得到:%C3%BB%C8%CB%B1%C8%CE%D2%B8%FC%B6%AE%B9%DA%D7%B4%B2%A1%B6%BE%2D%2D%CC%D8%C0%CA%C6%D5 肉眼可见它是url编码,直接利用百度,得到一串字符:“没人比我更懂冠状病毒–特朗普”。

最后一段没啥花头了,就是看到了use base64,是时候把刚刚从拨号音得到的电话号码用BASE64编码发到公众号,得到一段诡异的音频。

这是一段sstv的音频,里面有个图,直接使用软件MMSSTV,播放这段音频给电脑听(推荐电脑内录),你就可以看到孙狗的笑容(天皇遗照)。flag就写在孙狗的胸上。

结束

服务器配置docker

OS requirements

  • Ubuntu Eoan 19.10
  • Ubuntu Bionic 18.04 (LTS)
  • Ubuntu Xenial 16.04 (LTS)

Uninstall old versions

SET UP THE REPOSITORY

$sudo apt-get remove docker docker-engine docker.io containerd runc

Install using the repository

1. Update the apt package index and install packages to allow apt to use a repository over HTTPS:

$ sudo apt-get update

$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

2. Add Docker’s official GPG key:

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Verify that you now have the key with the fingerprint 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88, by searching for the last 8 characters of the fingerprint.

$ sudo apt-key fingerprint 0EBFCD88

pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]

3. Use the following command to set up the stable repository. To add the nightly or test repository, add the word nightly or test (or both) after the word stable in the commands below. Learn about nightly and test channels. (x86_64 / amd64)

$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

INSTALL DOCKER ENGINE

 $ sudo apt-get update
 $ sudo apt-get install docker-ce docker-ce-cli containerd.io

以上 docker安装完毕

可以使用以下这句验证完整性

$ docker info
# 或者
$ sudo docker run hello-world
# 或者
$ docker version

docker常用命令

把服务器的80端口映射到docker容器的8000端口

$ docker run -d -p 80:8000 ctfd/ctfd

Docker 需要用户具有 sudo 权限,为了避免每次命令都输入sudo,可以把用户加入 Docker 用户组(官方文档)。

$ sudo usermod -aG docker $USER

Docker 是服务器—-客户端架构。命令行运行docker命令的时候,需要本机有 Docker 服务。如果这项服务没有启动,可以用下面的命令启动(官方文档)。

# service 命令的用法
$ sudo service docker start

# systemctl 命令的用法
$ sudo systemctl start docker

# 列出本机的所有 image 文件。
$ docker image ls

# 删除 image 文件
$ docker image rm [imageName]

# 拉取指定镜像
$ docker image pull library/hello-world

# 列出镜像
$ docker image ls

# 运行docker容器
$ docker container run hello-world

# 通过这句话可以体验ubuntu容器
$ docker container run -it ubuntu bash

#关闭容器
$ docker container kill [containID]

# 列出本机正在运行的容器
$ docker container ls

# 列出本机所有容器,包括终止运行的容器
$ docker container ls --all

# 移除容器
$ docker container rm [containerID]

# 使用help帮助理解参数
  • -p参数:容器的 3000 端口映射到本机的 8000 端口。
  • -it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。
  • koa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。
  • /bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。

面向“对象”【使用Python语言】

别想了,你找不到对象的

——Syntax Error:Object Not Found

面向对象,即Object-Oriented-Programming(简称OOP),是现在常用的一种软件开发方法。它把相关的属性和方法当作一个整体来看待,使程序的思路更贴近一种自然运行的模式。

面向对象有四个特征:

  • Abstraction(抽象)
  • Polymorphism(多态)
  • Inheritance(继承)
  • Encapsulation(封装)

我们分点来介绍这几个概念


Abstraction-抽象

何谓抽象,就是在研究我们的研究对象时,将我们关注的东西提取出来,忽视掉一些我们不关注的内容。

例如,每个人都有妈(不管是活的还是死的)。妈妈有很多“属性”,比如身高、体重、年龄等;也有很多“方法”(就是行为),比如唱,跳,Rap……。但是我们万一只想研究老妈的年龄以及会不会唱,跳,是否需要在写程序定义Mom这个类时把Mom所以可能具备的内容都写进去呢?当然不可能,那样工作量大还搞得代码又臭又长。我们只把我们研究的东西抽象到类中。

以下是对Mom类的抽象:

class Mom: #Mom类
    momAge = 30 #Mom类的一个属性,年龄

    #以下是Mom类的方法,包含唱和跳
    def sing(self): 
        print("Chick you are so beautiful!")
        return

    def dance(self):
        print("Wow,you can really dance~")
        return

顺便我们再抽象一个Dad类,研究老爹的年龄以及会不会Rap和打篮球

class Dad:
    dadAge = 30

    def rap(self):
        print("Cause I got a crush on you.")
        return

    def basketball(self):
        print("American school team Ace!")
        return

Encapsulation-封装

默认我们在类中声明的属性和方法是“公共的”,即可以直接在类外被直接调用,这就可能导致数据被类外的方法篡改。以上面的Mom类为例:

myMom = Mom() #以Mon类创建myMom对象
myMom.momAge = 25 #对象的momAge属性可以被直接更改

这就导致我们代码更难维护,容易出现bug。因此我们应该类里的属性设置为“私有的”,并且在类中提供特殊函数来访问/更改这些私有属性。这种行为叫做封装。

在python中,类中以两个下划线开头命名的属性和方法是私有的,无法在类外直接访问。例如:

__priv = 1 #这是私有数据成员
def __privFunc(self): #这是私有方法

把类的数据成员做成私有的了,外部就无法直接访问了。那么,我们要访问时就应该提供特殊的读取/修改函数,即getter/setter。

理论上,当你声明类的时候应该给每一个需要访问的属性定义getter函数,为每一个可能需要更改的属性定义setter函数。

此处推荐一个编码规范:getter/setter函数的名称,应该使用get/set+属性名称 来命名,并且使用小驼峰法。getter函数不应有其它处理内容,直接返回对应属性的值,setter函数应该利用函数传参的方法为私有属性赋值,并且返回空。例如:

def getMomAge(self):
    return self.__momAge

def setMomAge(self, age):
    self.__momAge = age
    return

如此,类内的属性就仅可以使用类中功能单一的getter和setter函数来访问了,避免在开发过程中不经意间出现的未预期的篡改行为。

现在将上文的Mom类和Dad类封装起来。

class Mom: #Mom类
    __momAge = 30
    
    #以下是Mom类的getter和setter
    def getMomAge(self):
        return self.__momAge

    def setMomAge(self, age):
        self.__momAge = age
        return

    def sing(self): 
        print("Chick you are so beautiful!")
        return

    def dance(self):
        print("Wow,you can really dance~")
        return
class Dad:
    dadAge = 30

    #以下是Dad类的getter和setter
    def getDadAge(self):
        return self.__dadAge

    def setDadAge(self, age):
        self.__dadAge = age
        return

    def rap(self):
        print("Cause I got a crush on you.")
        return

    def basketball(self):
        print("American school team Ace!")
        return

这样就完成了对数据域的封装。如果要把方法也封装,就使用两个下划线开头的方法名吧。但是也只有类内方法可以调用它了。

ああああwr的python又又又又要更新らららら

学习了一段时间的requests库和urllib.request

想要查看源文件可以输入 :

#!/usr/bin/python3
#其他同理
import requsets
print(requests.__file__)

requests库和urllib.request主要是封装了http过程,也可以是ftp

那么从我一开始接触的urllib.request讲起吧。

urllib.request

通过Request创建一个请求请求信息(这里讲不很透彻,过一段时间再来)

Class urllib.request.Request(urldata=Noneheaders={}origin_req_host=Noneunverifiable=Falsemethod=None)

可以用字典填充请求头headers,method有’GET’,’POST’等

发送请求,返回一个response对象,可以对对象进行读取操作。response.read(),返回相应的代码,默认的编码格式应该是utf-8。

Classurllib.request.urlopen(urldata=None, [timeout, ]*cafile=Nonecapath=Nonecadefault=Falsecontext=None)

url为Request创建的请求对象,或者就是一个url字符串。

其他的一些参数暂时没用过,缺少了解,遇到再说。

对于req和response对象有一些操作
import urllib.request
DATA = b'some data'
req=urllib.request.Request(url=url,data=DATA,method='GET')
print(req.host)
print(req.type)
print(req.origin_req_host)
print(req.selector)
print(req.data)
print(req.unverifiable)
print(req.get_method())
print(req.get_full_url())
f = urllib.request.urlopen(req)
print(f.status)
print(f.reason)
print(f.info())
print(f.geturl())
print(f.getcode())
运行代码时会出现错误对应有HTTPError和URLError的封装

使用urllib.error库增加输出的可读性

from urllib.error import URLError,HTTPError
'''省略一些(堆)代码'''
try:
    response = urllib.request.urlopen(req)       
    except URLError as e:
        if hasattr(e,'reason'):
            print('ERROR! Reason :',e.reason)
    except HTTPError as e:
        if hasattr(e,'code'):
            print('ERROR! Reason :',e.code)
    else:
        print(response.read().decode('utf-8'))
        break
对于一些基础认证,urllib.request给出了一个封装
from urllib.request import HTTPBasicAuthHandler, build_opener,HTTPPasswordMgrWithDefaultRealm
from urllib import URLError

username = input("Type usr: ")
password = input("Type password: ")
url = input("Type url: ")
##创建一个对象,具体是啥我也不清楚
instance = HTTPPasswordMgrWithDefaultRealm() 
instance.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(instance)
opener = build_opener(auth_handler)
try:
    response = opener.open(url)
except URLError as e:
    print("URLError "+e)
html = response.read().decode('utf-8')

基础认证在请求头中给出也可,不一定要用这个封装。可以在urllib.request.Request()中添加请求头添加参数,例如添加 headers = {‘Authorization’:’Basic xxxxx’}。

可能中间会用到base64编码解码。可以使用base64库

import base64

password = input("Type your password : ")
password = password.strip()
base64KeyByte = base64.b64encode(password)
base64Key = base64KeyByte.decode('ascii')

更多的应用方法一定要看官网文档和源码

あああああああurllib.request怎么这么麻烦!!!!!!!!

这里推荐食用requests库,包吃包甜。

先上代码(害怕)

import requests
import json

#最基本不带参数的get请求
urls = 'https://www.baidu.com/'
url_params = {}
data = {'some': 'data'}
url_params = {
'wd':'aa',
'rsv_spt':'1',
'rsv_iqid':'0xe5064e4f000f8d53',
'issp':'1',
'f':'8',
'rsv_bp':'1',
'rsv_idx':'2',
'ie':'utf-8',
'tn':'baiduhome_pg',
'rsv_enter':'1',
'rsv_dl':'tb',
'rsv_sug3':'5',
'rsv_sug1':'2',
'rsv_sug7':'100',
'rsv_sug2':'0',
'inputT':'1046',
'rsv_sug4':'1133'
}

headers = {'content-type': 'application/json',
           'User-Agent': 'Mozilla/5.0 '\
            u'(X11; Ubuntu; Linux x86_64;'\
            u'\' rv:22.0) Gecko/20100101 Firefox/22.0'}
r = requests.get(url = urls,
                params = url_params,
                data = data,headers = headers)

'''
request.post(url)
request.put(url)
request.delete(url)
request.head(url)
request.options(url)
'''
# print(r.url)
# your url?key=value

print(r.encoding)                         #获取当前的编码
r.encoding = 'utf-8'                      #设置编码                    
# print(r.text)                             #以encoding解析返回内容。字符串方式的响应体,会自动根据响应头部的字符编码进行解码。
# print(r.content)                        #以字节形式(二进制)返回。字节方式的响应体,会自动为你解码 gzip 和 deflate 压缩。
print('--------r.headers---------')
print(r.headers)                        #以字典对象存储服务器响应头,但是这个字典比较特殊,字典键不区分大小写,若键不存在则返回None
print('--------r.status_code---------')
print(r.status_code)                     #响应状态码
print('-------r.raw----------')
print(r.raw)                             #返回原始响应体,也就是 urllib 的 response 对象,使用 r.raw.read()   
print('--------r.ok---------')
print(r.ok)
print('--------r.cookies---------')
print(r.cookies)                              #返回cookie
print('--------r.history---------')
print(r.history)                              #返回重定向信息
print('--------r.url---------')
print(r.url)
print('###########################')
print(r.text)
print('###########################')
#当然可以在请求是加上allow_redirects = false 阻止重定向
'''
ERROR此处有点搞不来              # 查看r.ok的布尔值便可以知道是否登陆成功
 #*特殊方法*#
# print('-------r.json()----------')
# print(r.json())                         #Requests中内置的JSON解码器,以json形式返回,前提返回的内容确保是json格式的,不然解析出错会抛异常
# print('--------r.raise_for_status()---------')
# print(r.raise_for_status())             #失败请求(非200响应)抛出异常
''' 

'''
##ERROR此处我搞不来
# r = requests.post('https://www.cnblogs.com/lanyinhao/p/9634742.html',
# data=json.dumps({'some': 'data'}))
# print(r.json())
'''
# print('--------r.text---------')
# print(r.text)

## 会话对象,能够跨请求保持某些参数
# s = requests.Session()
# s.auth = ('auth','passwd')
# s.headers = {'key':'value'}
# r = s.get('https://www.cnblogs.com/lanyinhao/p/9634742.html')

## 代理
# proxies = {'http':'ip1','https':'ip2' }
# requests.get('url',proxies=proxies)

Loading.*.*.*.*………/..\………

www-data

学习自某大佬

www-data 是 Debian/Ubuntu 上处理Web服务的用户/用户组。

发现服务器有奇怪的用户和用户组,www-data。看了home里面也没多出个用户,觉得有点奇怪(鸟叔没学多少系列)

查了一波发现了linux服务器里面很多程序和服务有对应的用户和用户组。

可以用命令 lastlog 查看用户情况。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
ubuntu@VM-49-193-ubuntu:~$ lastlog
Username Port From Latest
root 四 1月 1 08:00:10 +0800 1970
daemon **Never logged in**
bin **Never logged in**
sys **Never logged in**
sync **Never logged in**
games **Never logged in**
man **Never logged in**
lp **Never logged in**
mail **Never logged in**
news **Never logged in**
uucp **Never logged in**
proxy **Never logged in**
www-data **Never logged in**
backup **Never logged in**
list **Never logged in**
irc **Never logged in**
gnats **Never logged in**
nobody **Never logged in**
systemd-timesync **Never logged in**
systemd-network **Never logged in**
systemd-resolve **Never logged in**
systemd-bus-proxy **Never logged in**
syslog **Never logged in**
_apt **Never logged in**
lxd **Never logged in**
messagebus **Never logged in**
uuidd **Never logged in**
dnsmasq **Never logged in**
sshd **Never logged in**
ubuntu pts/0 42.244.62.236 三 4月 5 22:28:29 +0800 2017
mysql **Never logged in**
ftp **Never logged in**

因此,只要把所在的Web路径下的文件及子目录放心地交给www-data用户/用户组即可!

权限方面,采用默认方案即可,目录配755,文件配644,比777不知妥当到那里去了。

Dk的C/C++教程 类型转换

一个测试程序员到了酒吧,要了1杯酒。

一个测试程序员到了酒吧,要了1.67杯酒。

一个测试程序员到了酒吧,要了1杯凳子。

一个测试程序员到了酒吧,要了(int)1.233杯酒。

一个测试程序员到了酒吧,要了static_cast<int>(‘1’)杯酒。

——Introducing 类型转换

C语言有两种类型转换,显式类型转换和隐式类型转换。


隐式类型转换

隐式类型转换是一种由编译器按照数据类型的转换规则自动转换,无需程序员干预的自动进行的类型转换。

例如:

int a = 1.6777;
int b = 'A';

第一种出现了一个情况,定义了变量a为整数型,但是在初始化的时候,却为它赋值一个浮点数1.6777。在这种情况下,变量a的值变成了1,它出现了精度损失,这便是隐式类型转换可能会出现的问题。

第二种情况,把一个字符赋值给了整数型的变量b,此时其实也是进行了一次转换,把char类型的字符‘A’的ASCII码65赋值给了b。但是这种情况下并没有出现精度损失。可见,隐式类型转换也不一定会损失精度。

以上是出现在赋值时的隐式类型转换。咱再来看看在运算时进行的隐式类型转换。

int a = 2.4 + 3.6 + 1;

这一句出现了两种类型转换。

先看等号右边,2.4和3.6都是浮点数,1作为整数要和他们进行运算,必须隐式地转换为浮点数的1.0(为方便表示,我将记作1.0),然后再与其它两个浮点数计算。计算出的结果为7.0,是浮点数。然后就是上文讲的赋值时的隐式类型转换,把7.0转换为7赋值给a。

事实上,在计算时总有这种规矩:

HIGH
  ↑
double ← float
  ↑
long
  ↑
unsigned 
  ↑
int ← char, short
  ↑
LOW  

从下往上的箭头表示在它们混合运算时总会先隐式转换到最高类型等级再进行运算,从右往左的运算则是无论它们有没有进行混合运算都会先隐式转换至最左边的类型,即:

不同类型数据的运算 ==> 结果的类型
int + long ==> long
int + double ==> double
char + char ==> int
char + short ==> int
unsigned + int ==> unsigned

事实上,运算时的隐式转换因为都是往表示范围更大的方向进行,所以一般不会出现精度损失(但是可能得到非预期解,如unsigned a = 1,a-2的值就不会是-1而是一个极大的unsigned类型的整数)。

因为诸如此类的可能出现的精度损失等问题,写程序的时候应该尽量避免隐式类型转换而尽量使用显式类型转换。

显式类型转换

C语言中,要进行显示类型转换十分简单,在你想转换的变量之前加上(数据类型)的结构。例如:

double a = 3.14; //定义了一个浮点型
printf("%d",(int)a); //把变量a强制转换为int类型的数值再打印出来

这边的(int)a,就是C语言的强制类型转换。这边括号内的内容可以换成任何你想要转换成的类型。

//C语言的强制类型转换没花头,很容易就会了

【注意:以下仅限C++

就讲一个效果和C语言的强制类型转换作用相同的:static_cast<数据类型>(数据)。

static_cast<> 静态类型转换 例如:

auto a = 3.14; //定义一个浮点数
auto b = static_cast<int> (a); //强制转换为整形
std::cout << b;

注意:static_cast的尖括号内的值需要在编译器期确定!

Little Tip:你可以使用decltype()来取得已经定义好的变量的类型,然后用来转换类型或者定义新变量。如:

auto a {1}; //定义a为整形
auto b {1.2}; //定义b为浮点型
decltype(a) c {3}; //利用a的类型(int型)来定义c
auto d = static_cast<decltype(a)>(b); 把b强制转换成a的类型去赋值给d

So much for this.这次的附录就结束了~

Python小白爬虫

老八蜜汁小蜘蛛,问君吃否

def bilispider():
    import urllib.request
    import re

    hds = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWe\
    bKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36'}
    request = urllib.request.Request(url='https://www.bilibili.com/',headers=hds)
    data = urllib.request.urlopen(request).read().decode('utf-8')
    ##html = urllib.request.urlopen(request)
    ##data = html.read().decode('utf-8')
    target = '"author":(.*?),'
    author = re.compile(target).findall(data)
    print(author)

这个程序本来是用于看看bili首页热评的一些up猪,不过由于bilibili的更新,已经没有可以爬的东西了

用到两个re库 和urllib库,需要自己加轮子

hds用字典表示请求头的一个元素。urllib.request.Request(url=’https://www.bilibili.com/’,headers=hds)

  • 用于返回一个请求,前一个参数为url,后一个为请求头

data = urllib.request.urlopen(request).read().decode(‘utf-8’)

  • 通过 urllib.request.urlopen(request).read().decode(‘utf-8’) 发送请求并读取返回值,最后再以utf-8解码,得到网页的html

target = ‘”author”:(.*?),’

  • 这是一个正则表达式,稍后介绍

author = re.compile(target).findall(data)

  • 通过re模块的compile函数匹配字符串data

最后输出结果

Python正则表达式的一些知识

通用字符

\w 匹配任意一个字母、数字、或下划线

\W 匹配除字母、数字和下划线的任意一个字符

\d 匹配任意一个十进制

\D匹配除十进制外的任意一个字符

\s匹配任意一个空白字符(个人表示奇怪,可能我不明吧什么是空白字符)

\S匹配除空字符以外的任意一个其他字符

原子表

[ ]取任意一个进行匹配

[^ ]匹配除方括号内的其他任意字符

元字符

.匹配除换行符以外的任意字符

^匹配字符串的开始位置

$匹配字符串结束位置

*匹配0次、1次或多次前面的原子

?匹配0次或1次前面的原子

+匹配1次或多次前面的原子

{n}前面的原子恰好出现n次

{n,m}前面的原子至少出现n次,至多m次

丨模式选择”python丨php”

()模式单元符 用于从一个语句中选择出要的东西

边界限制符

^匹配字符串的开始

$匹配字符串的结束

模式修正符

I匹配时忽略大小写

M多行匹配

L做本地化识别匹配

U根据Unicode字符及解析字符

S让‘.’匹配包括换行符,即用了该模式修正后‘.’就可以匹配任意字符了

贪婪模式

.* 尽可能多的匹配

懒惰模式

.*? 尽可能少的匹配

匹配一个网站

一种表示方法

开头是http://或者https://当然也可以是别的什么的

都是字母 所以可以以 [a-zA-Z]*:// 表示

中间有很多符号可以使用.*?表示

结尾为.com或者cn 使用[.com|.cn]表示

最后结果为: ‘ [a-zA-Z]*:// .*?com| [a-zA-Z]*:// .*?cn’

Dk的C/C++教程 命名规则与驼峰命名法

还在使用x,y,z,a,b,c等无意义字符做变量名嘛?还因为把变量命名位g,s,b而遭到同学的嘲笑嘛?还因为不知道怎么命名变量能做的更直观嘛?

——Introducing Camel-Case:驼峰命名法

我们现在先来看看一个例子:

假如我们想要定义一个变量存储我们的名字,该怎么命名呢?

mylastname

构成这个变量名的所有字母都通过小写被粗暴地整合在了一起,很难一眼看清,对吧?这种变量命名方法明显地降低了代码的可读性。要不,我们拆分一下单词?

my_last_mane

显而易见地,这个变量名的可读性比上面那种高了很多,我们可以轻易地读出它的各个单词并且很清楚地知道它的意思。但是很显然,这不够紧凑,我们还得多打两个字符(吐槽下这下划线真的不是很好按)。

驼峰命名法

驼峰命名法的命名规格极为形象,通过大小写像驼峰一样变换来实现单词的区分,更方便直观地表示变量名。

那我们来看看驼峰命名法该怎么用:

myLastName

相比于前两种,驼峰命名法既直观又紧凑,保证了代码可读性的同时也兼顾了效率。

驼峰命名法的规则是这样的: 当变量名或函数名是由多个单词连结在一起,构成的唯一识别字时,每一节个单词的首字母都采用大写来区分,单词与单词之间不额外添加下划线。

而以第一个单词的首字母是否要大写为区分,我们又可以分成大驼峰法和小驼峰法。

大驼峰法:

包括第一个单词在内的所有单词的首字母全部大写,如:

  • MyLastName
  • DataBaseUser

小驼峰法:

第一个单词的首字母不大写,其余每个单词的首字母均大写,如:

  • myLastName
  • studentsAverageGrades

驼峰命名法就介绍到这里。


接下来再讲讲我使用的一种命名规则。

强烈建议使用英文命名!!!

变量的命名规则

变量名的命名,首先应该是这个变量要储存的内容的英文意思。借助你强大的英文水平,先把你想好的中文变量名翻译成英文,不会的话就借助翻译软件吧。

然后,如果你翻译出来的内容仅由1个英文单词构成,请直接以这个单词的完全小写的形式命名这个变量。如果是由多个单词组成的词组,应该使用小驼峰法的方式为这个变量命名。 记住,它应该是个名词或者名词性词组。 例如:

salary,tax,carSum,taxRate;

特殊地,给布尔类型的变量命名,建议使用 is + 属性名的方法,依照具体的语境,也可以用can,have,should等前缀代替is,同样使用小驼峰法。同时要记得避免使用否定词。例如:

isReal,isHigh;(√)
isNotTall,isError;(×)

常量的命名规则

常量一般是指使用const关键字修饰定义的数据,有时也只使用预处理宏#define定义的数据。

使用const关键字定义的常量

它的命名规则和变量类似,但是要使用大驼峰法,即第一个单词的首字母也要大写。例如:

Line,SavingAccount,Pi;

使用#define关键字宏定义的常量

它的所有字母都应该采用大写,同时,若是由多个单词组成的变量名,为了区分每个单词,在每两个单词之间应该添加下划线。例如:

PI,MAX_SIZE;

函数/方法的命名规则

函数的名称应该是一个动词或者一个动词词组。如果函数名仅由单个单词构成,那么请以这个单词的完全小写形式命名这个函数。如果这个函数名是一个动词词组,请以小驼峰法的方式命名这个函数。例如:

getArea,setPath,takeSteps;

名字空间的命名规则(C++)

对于名字空间,请以一个英文单词完成对它的命名,而且,应该使用它的完全小写形式。例如:

model,common;

结构、类(C++)的命名规则

结构体和类的名称应该是一个名词或者名词性词组。若使用单个单词命名,请首字母大写;若使用词组,应使用大驼峰法命名。例如:

Node,SomeClass;

但是,命名结构和类形成的实例(结构体和对象)时,应该依照一般的变量的命名法进行命名。

对于枚举类型(以enum关键词声明)

同样建议使用名词或者名词性词组,以首字母大写或者大驼峰法命名。

而对于枚举类型的枚举常量,建议以全部大写字母+下划线分割单词的方式命名,同时应尽量使用共同的类型名作为前缀。

例如:

enum Color { COLOR_RED, COLOR_GREEN, COLOR_BLUE };
enum ColorType { COLOR_TYPE_WARM, COLOR_TYPE_COLD };

额外再提几个用于命名的关键字以及技巧,让代码更直观

  • get应该用在直接取得已设定值的函数中。
  • set应该用在直接为变量设定值的函数中。
  • compute应该用在用于进行计算的函数中。
  • find应该用在用于查找的函数中。
  • init/initialize应该使用在用于初始化的函数中。
  • 在需要标识序号的变量名中,使用No + 序号比较合适。
  • 布尔变量的命名应使用is,has等前缀,并且尽量避免否定词。
  • 循环变量建议设置为i,j,k,并且建议优先使用i而把j,k用于循环嵌套。
  • 对于缩略词我们有两种态度:1、对于那种缩写了人家可能就看不懂了的,比如cmd,init,请不要用缩写代替他们。2、对于那种缩写认识展开别人就看不懂的,比如HTML,CPU,老老实实写缩写。

另外,对于自己写的头文件的命名,推荐使用大驼峰命名法。例如

MyClass.h
CppFunction.h

OK,有关命名方法先写这么多。

Dk的C/C++教程 Aloha World!

“Aloha!”是夏威夷俚语里的问候语,那么多编程教程写的都是”Hello World”,咱来整点不一样的~

先来看一段代码吧~

#include <stdio.h>
int main()
{
    printf("Aloha World!"); //你好,世界
    return 0;
}

把它写在编辑器里,再保存为后缀名为.c的文件,它就是C语言源代码的文件啦

咱先不来编译运行它,咱来逐行分析这段代码:

  • 第一行:include的意思是包括,它把一个名字叫stdio.h的头文件包括进来了。stdio,我们把它展开一点,就是Standard I/O,I/O就是input/output的意思。.h,是C语言库文件的后缀名。综合来看,这一行的意思就是把C语言的标准输入输出库包含到了这个C语言源文件内。它之下的代码便可以使用stdio.h中提到的各种函数等事先定义好的内容。
  • 第二行:这是C语言运行的起点,main函数的第一行。main是个特殊的函数,C语言的代码都是从main函数开始运行的。前面的int代表的是main函数的返回值类型,int代表整数型。特别说明,依照最新的C11标准,main函数的返回值类型必须是int类型。之后的括号是main函数的参数列表,这边不给main函数设置参数,所以留空。有时,你也会见到参数列表中写明关键字void,它等同于参数列表里面啥也不写。
  • 第三行和第六行是一对花括号,它框定了一个程序块,块中的代码即为main函数的函数体。
  • 第四行我们可以看到一个函数,叫printf,为了方便记忆,我们把它展开以下,就是print-function,就是“打印函数”,它是预先在库文件stdio.h内定义的C语言自带函数,直接就可以拿来用,用途是向当前控制台打印出字符。那么我们看这个函数的参数列表,只有一个元素,就是被双引号包围的一句话”Aloha World!”。在C语言中,被双引号包围的字符被称为字符串。相信嘤文好的读者肯定已经猜到这句话的作用了(\( ̄︶ ̄*\)),我们先不说破。
  • 第四行有一串额外的东西,//后面跟着的字符,代表的是这一行的注释,编译器读到两个斜杠之后,便会忽视之后的内容了。换句话说,编译器不会编译被注释的内容。
  • 第五行我们终于知道返回值在说个啥了。返回值返回值,就是return value嘛,这个return语句的作用,就是把后面的0,返回到函数调用的地方去。注意,这个0就是int类型的数据了。但是,main函数有点特殊,在main函数完成return操作之后,该C语言程序就会自动关闭了。也就是说,在这个C语言程序中,没有东西能调用得了main函数,那么它是做什么用的呢?当main函数返回0的时候,它告诉操作系统,这个程序已经运行完毕并安全退出了,如果遇到运行时错误,程序还没有来得及运行到return语句就被迫中止,main函数就会返回一个非0的数给操作系统,告诉它这个软件异常退出了。因此,这个return语句显得格外重要。

那么大家看到这里,一定已经知道了这段代码的作用:往当前窗口打印(即输出)一串字符串:Aloha World!

那么我们要怎么编译这段代码才能让它成为一个能被操作系统运行的程序呢?以Windows环境为例,先把上面的那段代码用你的代码编辑器(比如VSCode或者甚至记事本)保存为.c文件(姑且假设为Aloha.c),然后在你存放这个源代码文件的文件夹的空白处,按住键盘上的Shift键并单击右键,选择“在此处打开Powershell 窗口”项,然后在窗口内输入

gcc Aloha.c -o Aloha

我们来解释一下,gcc是调用C语言编译器的命令,后面跟着的Aloha.c是我们写好的C语言源代码文件,代表要把Aloha.c的代码编译成可执行文件(就是我们能运行的软件)。后面的-o 参数,代表编译生成的可执行文件的名字。当我们按下回车之后,你可以看到在当前文件夹下多了一个Aloha.exe文件,它就是我们编译出的程序。

那么既然我们已经成功编译好了源代码,现在就来运行一下我们的程序看看。继续在Powershell窗口输入

.\Aloha.exe

看到了嘛,它下面就输出了我们刚刚分析完的Aloha World!

这就是你的第一个C语言程序。

(对于配置好VSCode的读者,在你写完代码并且保存好之后,你只需要单击Code Runner的运行按钮,源代码的编译以及编译完的程序的运行都会一次性地完成,你可以直接看到输出结果。)

以下为针对注释的补充材料:

//这是行注释
/*
这是块注释
*/

编译器将会忽视注释内的一切东西。注释只是方便代码的阅读者的理解。换言之,一般的代码的阅读者是计算机,但是注释的阅读者是程序员。

注意:以下针对C++学习者

任何合法的C语言代码都是合法的C++语言代码。要想输出”Aloha World”,除了利用上面提供的函数输出方法,C++还提供了流式输出方法,请看代码:

#include <iostream>
int main()
{
    std::cout << "Aloha World!";
    return 0;
}

有几个不同点:

  1. 第一行包含的头文件,不在是stdio.h而是iostream.h了,在新标准的C++中,头文件的后缀名变得“可以省略”了。iostream是C++的流式输入输出库,提供了全新的流输入输出方式。
  2. 第五行不再使用函数而是使用std名字空间下的C++标准输出流来实现字符串的输出了(有关名字空间的概念,我将在讲解变量作用域的地方详细解释)。”<<“为流插入运算符,他能把后面的字符串插入到前面的cout标准输出流中,使后面的字符串能够输出在控制台上。

请注意:

  • C++的源代码文件,后缀名应该为.cpp
  • C++代码的编译器,应该使用g++而非gcc

如此,你就得到了你的第一个C++程序,程序的效果和C语言版一致。

最后的运行效果都是一样的,如下图:

Okay,那么这一章就到这里了。

Good luck,and good night~

Dk的C/C++教程 配置编译环境啦

本节来讲讲怎么在本地配置C/C++编译环境,还是以三大桌面操作系统加以区分。

Windows环境

C/C++的编译环境搭建,可以参考我之前在本站的文章《从0开始配置Visual Studio Code的C/C++编译环境》来配置MinGW提供的环境。本文中使用的编译器均为gcc,文本编辑器为VSCode,IDE为CLion。

Linux环境(以Ubuntu为例)

如果你用的是Ubuntu Linux(其他发行版也大同小异),可以在命令行中输入

sudo apt-get install build-essential
sudo apt-get install gdb

来安装编译器和调试器。

macOS环境

如果你用的是macOS,更简单,去Mac App Store下载安装苹果的开发套件Xcode就可以正常使用了。

总之,当你在命令行窗口中输入gcc -v的时候能正确显示版本号和gcc的相关讯息时,你就算安装成功了(如下图所示,终端为Windows Terminal)。