进程控制块
进程控制块:PCB是操作系统管理控制进程运行所有的信息集合,主要包括进程描述信息、进程控制和管理信息、资源分配清单和处理机相关信息等,是进程实体的一部分,进程存在的唯一标志。
PCB是进程在内存中的静态存在方式,因此进程的静态描述符必须保证一个进程在获得CPU并重新进入运行态时,能够精确的接着上次运行的位置继续运行,相关的程序段、数据以及CPU现场信息都要保存下来,CPU的现场信息主要包括内部寄存器和堆栈的基本数据。
进程上下文
进程上下文:当程序执行了系统调用或中断而进入内核态时,进程切换现场就称为进程上下文,包含了一个进程所具有的全部信息,一般包括:进程控制块(PCB)、有关程序段和相应的数据集。
进程堆栈
进程的堆栈:内核在创建进程的时候,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。进程用户栈和内核栈的切换:当进程因中断或系统调用而陷入内核态运行时,进程所使用的堆栈也要从用户栈转到内核栈。进程进入内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,完成用户栈向内核栈的转换;当进程从内核态恢复到用户态之行时,在内核态之行的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器,实现内核栈和用户栈的互转。那么,我们知道从内核转到用户态时用户栈的地址是在陷入内核的时候保存在内核栈里面的,但是在陷入内核的时候,我们是如何知道内核栈的地址的呢?关键在进程从用户态转到内核态的时候,进程的内核栈总是空的。这是因为,当进程在用户态运行时,使用的是用户栈,当进程陷入到内核态时,内核栈保存进程在内核态运行的相关信息,但是一旦进程返回到用户态后,内核栈中保存的信息无效,会全部恢复,因此每次进程从用户态陷入内核的时候得到的内核栈都是空的。所以在进程陷入内核的时候,直接把内核栈的栈顶地址给堆栈指针寄存器就可以了。
Linux创建进程是通过子进程复制父进程所拥有的资源来实现的。现代Linux通过写时复制、共享数据等方法优化这一过程,提高创建子进程的效率。
在Linux中,进程创建实际上是通过do_fork函数处理的。do_fork函数的功能相对简单(kernel/fork.c):
从上可得task_struct进程控制块与进程地址空间的联系:
在task_struct结构体内的struct mm_struct成员执行内存区描述符的指针。在进程描述符中,还应该存储进程空间的页表信息,和将逻辑地址转换成页号和页内偏移地址所需的相关信息。
通过总结可以得到:进程的创建的系统调用clone fork vfork都是调用do_fork实现的,而do_fork在做了一些参数检查之后。调用了copy_process函数,copy_process函数在进行安全性检查之后,使用dup_task_struct复制父进程的结构体。对新进程描述符的一些标志信息和时间信息进行初始化,之后将父进程的所有进程信息拷贝到子进程空间,包括IO、文件、内存信息等。然后,设置新进程的pid,将新进程加入进程调度队列中。子进程的eax设置为0,父进程则返回新进程的pid,所以在fork调用中,子进程返回的是0,父进程返回的是新进程的pid。详细见后续代码分析。
在Linux中提供了一系列的函数,这些函数能用可执行文件所描述的新上下文代替进程的上下文。这样的函数名以前缀exec开始。所有的exec函数都是调用了execve()系统调用。
sys_execve接受参数:
fork()调用创建一个新的进程,该进程几乎是当前进程的一个完全拷贝。由fork()创建的新进程被称为子进程。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。Linux将复制父进程的地址空间内容给子进程,因此,子进程拥有独立的地址空间。
从输出结果可以看出:父进程和子进程的栈和堆的数据是相同的。这些数据在创建子进程时是通过拷贝产生的。
系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。
|
|
我们执行一个不存在的hello_world程序,会报错。
我们如果添加一个函数,现在我们继续运行execl_example程序,这时输出为:Hello World!
通过比较两次输出,我们发现:当execl成功时,原有的进程执行就会被打断,替换为新的进程继续执行。
我们知道在Linux中,每个系统调用都对应一个系统调用号。这个系统调用号是在unistd.h中定义的。文件的位置是在:
/usr/src/linux-headers-2.6.28-11-generic/arch/x86/include/asm/unistd_32.h
使用汇编调用fork:
可以看到fork的系统调用号是2,我们现在使用汇编代码重新编写fork_example.c
输出结果为:CHILD PROCESS: stack_data, heap_data
PARENT PROCESS: stack_data, heap_data
使用汇编调用execl:我们再尝试一下使用汇编调用execl。通过上面的观察我们可以看到execl的系统调用号是11.
运行结果为:Hello World!
如果将系统调用号改为0x3,输出结果为:The execl must be failed!
通过上一步的过程,我们了解到,系统调用在内核中的执行是依靠中断实现的。如果我们想进一步定位fork和execl的代码,我们需要先了解系统调用的详细过程。即回答以下两个问题:
1.中断是怎么工作的?
2.int 0x80中断是怎么工作的?
中断是怎么工作的
在Linux操作系统中,中断是通过中断描述符表工作的。中断描述符表(Interrupt Descriptor Table, IDT)是一个系统表,它与每一个中断或者异常向量相联系,每一个向量在表中有相应的中断或者异常处理程序的入口地址。内核在允许中断发生前,必须适当的初始化IDT。对于每个中断,都会有对应的中断处理程序。当产生一个中断时,Linux根据中断向量表中对应的项找到存储中断处理程序的地址,然后调用相应的中断处理程序。中段描述符表在内存中的地址存储在idtr寄存器中。内核在启动中断前,必须初始化IDT,然后将IDT的地址壮载到idtr中。
内核初始化的时候调用trap_init()函数和init_IRQ()函数初始化中断向量表。
int 0x80中断是怎么工作的
通过上面的分析,我们知道每个中断都有对应的处理程序。在系统调用的过程中,会有一个系统调用分派表,每个表项存储了一个系统调用。系统调用中断处理程序,根据系统调用号找到对应的系统调用执行。对于系统调用,参数的传递是通过寄存器ebx ecx edx进行传递的。eax中存储的是系统调用号。系统调用最大为__NR_syscalls个。
在arch/x86/include/asm/irq_vectors.h中定义了
现在我们查找trap_init函数,在arch/x86/kernel/traps.c中
set_system_trap_gate(SYSCALL_VECTOR, &system_call);
现在,查找system_call函数,在arch/x86/kernel/entry_32.s中:
在include/uapi/asm_generic/unistd.h中找到:
SYSCALL(NR_fork, sys_fork)
fork的系统调用号是2,对应的系统调用分派表中为sys_fork函数。在kernel/fork.c中找到如下代码:
现在查找do_fork函数,也在kernel/fork.c中:
可以看到do_fork调用了copy_process完成了绝大部分的工作。copy_process位于同一个文件当中:
dup_task_struct也在fork.c文件中
|
|
通过上面的代码,可以总结出fork的工作的基本流程是:
举例跟踪分析Linux内核5.0系统调用处理过程
- 编译内核5.0
- qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img
- 选择系统调用号后两位与您的学号后两位相同的系统调用进行跟踪分析
https://github.com/mengning/menu- 给出相关关键源代码及实验截图,撰写一篇博客(署真实姓名或学号最后3位编号),并在博客文章中注明“原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ ”,博客内容的具体要求如下:
- Ubuntu 18.04
- gcc
一 编译内核5.0
方法 下载内核、解压、make
内核下载地址:https://github.com/mengning/linux.git,此内核是中科大孟宁教授,也是国内顶级的linux内核专家,拉出的内核分支,单独由于教学使用。
命令如下
note:编译的时候可能会出现有些依赖库未找到,需要我们去手动安装,我编译的时候就遇到了两个问题
遇到了这个错误:Unable to find the ncurses libraries,然后可以sudo apt insatll ncurses-dev解决,但是在我sudo apt install的时候,遇到了这个死锁错误,如下,网上搜了一下,结局了
12345 E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailable)E: Unable to lock the administration directory (/var/lib/dpkg/), is another process using it?# 解决方法:ps -A | grep aptsudo kill -9 processnumber
make完之后,我们需要将git上的一个根文件系统下载下来打包成img文件
二 然后启动MenuOS系统
qemu -kernel ~/linux5/arch/x86/boot/bzImage -initrd rootfs.img
三 跟踪调试内核启动
qemu -kernel linux-5.0.1/arch/x86/boot/bzImage -initrd rootfs.img -S -s -append nokaslr
注意:如果不加-append nokaslr选项,start_kernel断点有可能断不住!!!
具体如下图
首先,几乎所有的内核模块均会在start_kernel进行初始化。在start_kernel中,会对各项硬件设备进行初始
化,包括一些page_address、tick等等,直到最后需要执行的rest_init中,会开始让系统跑起来。
那rest_init这个过程中,会调用kernel_thread()来创建内核线程kernel_init,它创建用户的init进程,初始化内核,并设置成1号进程,这个进程会继续做相关的系统初始化。
然后,start_kernel会调用kernel_thread并创建kthreadd,负责管理内核中得所有线程,然后进程ID会被设置为2。
最后,会创建idle进程(0号进程),不能被调度,并利用循环来不断调号空闲的CPU时间片,并且从不返回。
增加系统调用
根据学号后两位44,在/usr/include/asm/unistd_32.h中可查得#define __NR_prof 44。
在test.c中增加函数,Prof()。
重新编译制作rootfs.img
下面是运行的结果
从结果来看,已经调用成功,但是由于Prof系统函数我没有找到函数详解,下次补上。
系统调用的触发及参数传递
- 当调用一个系统调用时,CPU从用户态切换到内核态并开始执行一个system_call和系统调用内核函数。在Linux中通过执行int 0x80来触发系统调用,内核为每个系统调用分配一个系统调用号,用户态进程必须明确指明系统调用号,需要使用EAX寄存器来传递。
- 系统调用可能需要参数,但是不能通过像用户态进程函数中将参数压栈的方式传递,因为用户态和内核态有不同的堆栈,必须通过寄存器的方式传递参数。
- 概括:EAX用来传递系统调用号,EBX、ECX、EDX、ESI、EDI、EBP用来传递参数,若参数较多,则把指向内存的指针存入寄存器。
参考网络资料
原创作品转载请注明出处+中科大孟宁老师的linux操作系统分析:https://github.com/mengning/linuxkernel/
操作系统中,OS的任务有很多,比如内存管理、文件管理、进程管理等等,而这其中,进程的管理是至关重要的,因为只有实现了进程的管理,才能使得一个操作系统可以处理多种任务,通过进程的切换,可以从逻辑上实现任务的多个进程任务并行操作,使得操作系统可以更高效的服务于应用服务。本实验着重基于linuxkernel实现一个时间片轮转多道程序内核代码
实验环境
Ubuntu 18.04、vim、gcc、vscode、QEMU
首先安装QEMU工具,后面需要它显示内核执行的过程,可以UI展示
然后下载基于3.9.4版本的linux内核和孟老师基于内核裁剪出来的补丁(给孟宁老师实力点赞),然后进行解压和打补丁
然后就是编译源码,c语言里面可以通过强大的make实现编译,先预处理编译文件,在make编译
在这一步的时候,会报错,见下图,由于环境没有gcc7,通过网络,定位可以通过将linux-3.9.4/include/linux/下面的
然后在编译成功,如下图
我们再使用qemu工具来显示代码执行的UI过程
可见我们的内核已经启动成功了,接下来我们就基于孟宁老师的项目代码去实现一个时间轮转多道程序。
我们先git clone git@github.com:mengning/mykernel.git代码到本地,然后将其中的myinterrupt.c,mymain.c,mypcb.h三个代码替换上面mykernel文件夹下对于的三个,替换前最好先像cp mymain.c mymain-bak.c这样备份一下,其中要把mypcb.h中的一行代码修改一下:
替换完之后,在make重新编译一下,这一次编译是让替换之后的代码重新编译,编译速度也会比上次快很多,因为是增量编译。
编译完之后,重新执行一下,输入qemu -kernel arch/x86/boot/bzImage
结果见下图
上图可以看出,时间片轮询多道程序已经实现了,总共有4个进程,pid分别为0,1,2,3;
下一节我们来分析代码,分析一下孟宁老师给出的简洁高效的实现时间片轮询多道进程的代码,基本深入浅出的阐明了基本工作原理。
|
|
首先是mypcb.h,其中定义了两个结构和一个函数。第一个是结构Thread,里面有两个变量,ip和sp用于保存现场。
第二个是结构PCB,PCB结构定义了进程管理块,包括6各变量:
(1)pid进程标识符;
(2)state状态,-1表示不可运行,0表示可运行,>0表示停止;
(3)定义了一个栈空间;
(4)一个Thread变量;
(5)任务入口点;
(6)下一个PCB的指针;
还定义了一个my_schedule函数,以及两个宏定义。mymain.c
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 tPCB task[MAX_TASK_NUM];tPCB * my_current_task = NULL;volatile int my_need_sched = 0;void my_process(void);unsigned long get_rand(int );void sand_priority(void){int i;for(i=0;i<MAX_TASK_NUM;i++)task[i].priority=get_rand(PRIORITY_MAX);}void __init my_start_kernel(void){int pid = 0;/* Initialize process 0*/task[pid].pid = pid;task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */// set task 0 execute entry address to my_processtask[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];task[pid].next = &task[pid];/*fork more process */for(pid=1;pid<MAX_TASK_NUM;pid++){memcpy(&task[pid],&task[0],sizeof(tPCB));task[pid].pid = pid;task[pid].state = -1;task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];task[pid].priority=get_rand(PRIORITY_MAX);//each time all tasks get a random priority}task[MAX_TASK_NUM-1].next=&task[0];printk(KERN_NOTICE "\n\n\n\n\n\n system begin :>>>process 0 running!!!<<<\n\n");/* start process 0 by task[0] */pid = 0;my_current_task = &task[pid];asm volatile("movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */"pushl %1\n\t" /* push ebp */"pushl %0\n\t" /* push task[pid].thread.ip */"ret\n\t" /* pop task[pid].thread.ip to eip */"popl %%ebp\n\t":: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/);}void my_process(void){int i = 0;while(1){i++;if(i%10000000 == 0){if(my_need_sched == 1){my_need_sched = 0;sand_priority();my_schedule();}}}}//end of my_process//produce a random priority to a taskunsigned long get_rand(max){unsigned long a;unsigned long umax;umax=(unsigned long)max;get_random_bytes(&a, sizeof(unsigned long ));a=(a+umax)%umax;return a;}首先定义了3个全局变量,两个PCB结构,一个是所有的进程集合,一个是当前的进程。然后是两个函数,my_process和my_start_kernel。
我们重点来看一下my_start_kernel函数,函数分为三部分:
第一部分,是初始化进程0。pid代表了进程号,0是第一个。state代表运行状态,初始化为可运行。Thread的ip就是进程入口点,其实就是进程运行的起点。sp实际上是定义了一段进程的栈空间。最后定义了下一个PCB的链接先指向自己。
第二部分,是根据第一个进程0初始化余下的进程。因为我们设置最大进程数为4,所以这里实际上是设置了进程1-3的数据结构的值。
最后一个部分,是从进程0号开始运行。这里使用了内联汇编编程,实际上就是将进程0的thread.sp的值赋给esp,将当前运行的地址保存到栈中,这样如果切换的话就可以保证下一个进程结束时回到原来的位置执行。总而言之,my_start_kernel函数实现了定义进程数组,并运行第一个进程。
对于my_process函数来说:
就是建立一个循环不断运行进程,并输出表明进程正在运行的语句。这里注意有一个my_schedule()函数,实际上这个函数是在myinterrupt.c中实现的,主要作用是切换进程。myinterrupt.c
|
|
首先定义了一些全局变量。然后主要实现了两个函数:my_time_handler和my_schedule,其中my_time_handler实现了中断,而my_schedule实现了中断之后进程的切换。
my_time_hander()这个函数也很简单,就是每1000毫秒的时候产生一个中断,产生中断之后把my_need_sched设置为1,这样mymain.c中的my_process函数就会调用my_schedule函数来进行进程切换。
my_schedule()实现了时间片轮转的中断处理过程。首先是初始化next和prev两个PCB结构。然后是循环运行代码,就是当下一个进程的state状态是可运行时,说明这个进程之前已经在运行了,此时可以继续执行,就切换到下一个进程,中间有一段内联汇编,实现了保存栈地址和栈指针,这样进程切换回来的时候就可以正常运行。然后根据之前保存的栈地址恢复执行。
当下一个进程的state不为0时,那么也就是说下一个进程还从来都没有执行过,所以这一段内联汇编的作用是开始执行一个新进程。总而言之,在上述函数的作用下,成功地实现了时间片轮转的中断处理内核的功能。通过分析源码,我们了解到时间片轮转算法的具体方法,即每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。这是一种最古老,最简单,最公平且使用最广的算法。
当mykernel自制操作系统启动后,即qemu命令执行后,系统会首先调用mymain产生一个进程,该进程内while循环条件恒为1所以永远执行输出“my_start_kernel here”+累计循环次数,没有结束的时候。此时由系统时钟中断触发调用myinterrupt产生一个新进程,并输出“my_timer_handler here”,结束后返回mymain产生的进程继续执行,等待下一次系统时钟中断触发调用myinterrupt产生一个新进程,myinterrupt产生的新进程和上一次的属于不同的进程,而mymain由于循环没有终止的时候,所以永远是原来的那个进程。通过观察可以发现,当执行myinterrupt后返回mymain时,终端输出的累计循环次数是连续的,并没有中断或重置,说明CPU和内核代码共同实现了保存现场和恢复现场的功能,会将一些重要的寄存器,比如eip、ebp、esp等保存下来,等待切换回来的时候继续执行。
]]>通过该实验操作,成功实现了一个简单的时间片轮转多道程序,通过一个精简的操作系统内核,完成了一个简单的操作系统功能。
计算机有三个法宝:存储程序计算机、函数调用堆栈、中断操作系统
有两把宝剑:中断上下文、进程上下文切换由于CPU只有一套寄存器,同一时间只能处理一个进程(暂且只考虑单核CPU),所以理论上只能单任务顺序执行
但基于三个法宝和两把宝剑,操作系统得以实现多任务操作。
首先要安装的就是
- gcc
- g++
- gdb
123 sudo apt install gccsudo apt install g++sudo apt install gdb
现在有个程序如下:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 #include <iostream>#include <stack>#include <queue>using namespace std;typedef int tree_node_elem_n;struct binary_tree_node_t {binary_tree_node_t *left;binary_tree_node_t *right;tree_node_elem_n elem;binary_tree_node_t(){};binary_tree_node_t(tree_node_elem_n value){elem = value;left = nullptr;right = nullptr;}};void pre_order_r(const binary_tree_node_t *root,int(*visit)(const binary_tree_node_t*)){if(root == nullptr) return;int n;n = visit(root);cout << n << " ";pre_order_r(root->left,visit);pre_order_r(root->right,visit);}int visit(const binary_tree_node_t* n){return n->elem;}void in_order_r(const binary_tree_node_t *root,int(*visit)(const binary_tree_node_t*)){if(root == nullptr) return;int n;in_order_r(root->left,visit);n = visit(root);cout << n << " ";in_order_r(root->right,visit);}void post_order_r(const binary_tree_node_t *root,int(*visit)(const binary_tree_node_t*)){if(root == nullptr) return;int n;post_order_r(root->left,visit);post_order_r(root->right,visit);n = visit(root);cout << n << " ";}void pre_order(const binary_tree_node_t *root,int(*visit)(const binary_tree_node_t*)){const binary_tree_node_t *p;stack<const binary_tree_node_t *> s;p = root;if(p != nullptr) s.push(p);// while(s)}int main(){binary_tree_node_t node1(1);binary_tree_node_t node2(4);binary_tree_node_t node3(5);binary_tree_node_t node4(1);binary_tree_node_t node5(6);binary_tree_node_t node6(3);node1.left = &node2;node1.right = &node3;node2.left = &node4;node2.right = &node5;// node4.left = nullptr;// node4.right = nullptr;node5.left = &node6;// node5.right = nullptr;// node6.left = nullptr;// node6.right = nullptr;pre_order_r(&node1,visit);cout<<endl;in_order_r(&node1,visit);cout<<endl;post_order_r(&node1,visit);cout<<endl;return 0;}
首先要编译程序,编译成有debug信息的可执行文件,命令如下
每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的
Flask是python语言中一个有名的开源web框架,在python语言中,有两大主流的web开发框架,一个是Django,另外就是Flask,从github上的火热程度来看,这两个都比较火,二者的区别就是Django比较规范笨拙,集成度高,功能齐全,Flask比较灵活,很多功能都是靠插件来实现。每个人有每个人的喜好,根据自己喜好和项目需要来选择。
flask的官方文档安装
python 开发,最好借助pip来安装各种需要的包,没有的话,可以直接安装
|
|
如果你要在纯洁的虚拟环境里面开发项目可以安装virtualenv,借助它来生产环境,剩下的具体看官网文档
另外一种是全局安装,直接运行
安装好之后,可以运行flas -version,结果显示如下,即表示安装成功
之后可以写一个脚本来测试运行一下,在/home/{yourusername}/目录下新建一个目录表示项目位置,
把下面内容添加进hello.py,构成了一个简单的web接口程序,运行在本机8080端口
然后运行python hello.py,再访问http://127.0.0.1:8080就可以看到效果
这是flask自带的内置启动服务器功能,方便我们本地开发调试,下面第二步我们采用Gunicorn来启动web服务
Gunicorn“绿色独角兽”是一个被广泛使用的高性能的Python WSGI UNIX HTTP服务器,移植自Ruby的独角兽(Unicorn )项目,使用pre-fork worker模式,具有使用非常简单,轻量级的资源消耗,以及高性能等特点。
Gunicorn 服务器作为wsgi app的容器,能够与各种Web框架兼容(flask,django等),得益于gevent等技术,使用Gunicorn能够在基本不改变wsgi app代码的前提下,大幅度提高wsgi app的性能。
直接执行
在第一步创建的hello.py同级目录创建一个gunicorn配置文件deploy_config.py
启动gunicorn:
myapp 是入口Python文件名,app 是Flask 实例名。如果输出 worker 相关信息,表明启动成功。
Nginx的介绍和牛叉之处这里就不介绍了,全球各大厂商基本都用!
直接执行
修改 /etc/nginx/sites-available/ 下的defalut 文件为如下内容:
配置完了之后软链接一份到 /etc/nginx/sites-enabled/defalut 下面
注:也可以删除default 文件的,新建自己的配置文件,并建立软链接。
Supervisor 是一个用 Python 写的进程管理工具,可以很方便的用来启动、重启、关闭进程(不仅仅是 Python 进程)。除了对单个进程的控制,还可以同时启动、关闭多个进程,比如很不幸的服务器出问题导致所有应用程序都被杀死,此时可以用 supervisor 同时启动所有应用程序而不是一个一个地敲命令启动。
安装Superisor
直接执行
1 sudo pip install supervisor
首先来看 supervisord 的配置文件。安装完 supervisor 之后,可以运行echo_supervisord_conf 命令输出默认的配置项,也可以重定向到一个配置文件里:
配置文件中添加:
如出现端口占用的错误,则:
启动 Supervisord:
关闭 supervisor:
重新载入配置:
访问自己的云服务器ip:port可以看到效果
监听端口:
12345 lsof -i tcp | grep LISTEN******************************sshd 837 root 3u IPv4 8888 0t0 TCP *:ssh (LISTEN)vsftpd 4463 root 3u IPv4 19989 0t0 TCP *:ftp (LISTEN)
|
|
常用命令:
获取所有运行中的 nginx 进程列表:
若 nginx 主进程 pid 为 1628,则可用kill命令发送 QUIT 信号,关闭此进程:
Supervisor 相当强大,提供了很丰富的功能,不过我们可能只需要用到其中一小部分。安装完成之后,可以编写配置文件,来满足自己的需求。为了方便,我们把配置分成两部分:supervisord(supervisor 是一个 C/S 模型的程序,这是 server 端,对应的有 client 端:supervisorctl)和应用程序(即我们要管理的程序)。
配置文件中出去很多注释,主要有:
我们把上面这部分配置保存到 /etc/supervisord.conf(或其他任意有权限访问的文件),然后启动 supervisord(通过 -c 选项指定配置文件路径,如果不指定会按照这个顺序查找配置文件:$CWD/supervisord.conf, $CWD/etc/supervisord.conf, /etc/supervisord.conf)
program 配置
上面我们已经把 supervisrod 运行起来了,现在可以添加我们要管理的进程的配置文件。可以把所有配置项都写到 supervisord.conf 文件里,但并不推荐这样做,而是通过 include 的方式把不同的程序(组)写到不同的配置文件里。
为了举例,我们新建一个目录 /etc/supervisor/ 用于存放这些配置文件,相应的,把 /etc/supervisord.conf 里 include 部分的的配置修改一下:
假设有个用 Python 和 Flask 框架编写的用户中心系统,取名 usercenter,用 gunicorn (http://gunicorn.org/) 做 web 服务器。项目代码位于 /home/leon/projects/usercenter,gunicorn 配置文件为 gunicorn.py,WSGI callable 是 wsgi.py 里的 app 属性。所以直接在命令行启动的方式可能是这样的:
现在编写一份配置文件来管理这个进程(需要注意:用 supervisord 管理时,gunicorn 的 daemon 选项需要设置为 False):
一份配置文件至少需要一个 [program:x] 部分的配置,来告诉 supervisord 需要管理那个进程。[program:x] 语法中的 x 表示 program name,会在客户端(supervisorctl 或 web 界面)显示,在 supervisorctl 中通过这个值来对程序进行 start、restart、stop 等操作。
|
|
上面这个命令会进入 supervisorctl 的 shell 界面,然后可以执行不同的命令了
上面这些命令都有相应的输出,除了进入 supervisorctl 的 shell 界面,也可以直接在 bash 终端运行:
除了单个进程的控制,还可以配置 group,进行分组管理。
经常查看日志文件,包括 supervisord 的日志和各个 pragram 的日志文件,程序 crash 或抛出异常的信息一半会输出到 stderr,可以查看相应的日志文件来查找问题。
Supervisor 有很丰富的功能,还有其他很多项配置,可以在官方文档获取更多信息:http://supervisord.org/index.html
]]>list,tuple,dictionary
list相当于数组
tuple是不可修改的数据类型,一般用于定义常量
字典数据类型,存储key-value类型数据
|
|
|
|
type、str、dir 和其它的 Python 内置函数都归组到了 builtin (前后分别是双下 划线) 这个特殊的模块中。如果有帮助的话,你可以认为 Python 在启动时自 动执行了 from builtin import *,此语句将所有的 “内置” 函数导入该命名空间, 所以在这个命名空间中可以直接使用这些内置函数
使用 getattr 函数,可以得到一个直到运行时才知道名称的函数的引用。
|
|
getattr 常见的使用模式是作为一个分发者。举个例子,如果你有一个程序可以 以不同的格式输出数据,你可以为每种输出格式定义各自的格式输出函数, 然后使用唯一的分发函数调用所需的格式输出函数。例如,让我们假设有一个以 HTML、XML 和普通文本格式打印站点统计的程序。 输出格式在命令行中指定,或者保存在配置文件中。statsout 模块定义了三个 函数:output_html、output_xml 和 output_text。然后主程序定义了唯一的输出函数, 如下:
|
|
Python 具有通过列表解析 (Section 3.6, “映射 list”) 将列表映射到 其它列表的强大能力。这种能力同过滤机制结合使用,使列表中的有些元素 被映射的同时跳过另外一些元素。
过滤列表语法:[mapping-expression for element in source-list if filter-expression]
|
|
|
|
Python 支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做
lambda 的函数,是从 Lisp 借用来的,可以用在任何需要函数的地方。
总的来说,lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个 表达式的值。lambda 函数不能包含命令,包含的表达式不能超过一个。不要 试图向 lambda 函数中塞入太多的东西;如果你需要更复杂的东西,应该定义 一个普通函数,然后想让它多长就多长。
|
|
|
|
与大多数的语言不同,一个 Python 函数,方法,或属性是私有还是公有,完 全取决于它的名字。举例来说
有两个方法:__parse 和 __setitem。正如我们已经讨论过的, \setitem 是一个专有方法;通常,你不直接调用它,而是通过在一个类上使 用字典语法来调用,但它是公有的,并且如果有一个真正好的理由,你可以 直接调用它 (甚至从 fileinfo 模块的外面)。然而,\parse 是私有的,因为在它的 名字前面有两个下划线。
在 Python 中,所有的专用方法 (像 __setitem) 和内置属性 (像 \doc__) 遵守一 个标准的命名习惯:开始和结束都有两个下划线。不要对你自已的方法和属 性用这种方法命名;
Python 使用 try…except 来处理异常,使用 raise 来引发异常。Java 和 C++ 使用 try…catch 来处理异常,使用 throw 来引发异常。
|
|
|
|
|
|
python类支持继承,也支持多继承
for element in [1, 2, 3]:
print(element)
这种形式的访问清晰、简洁、方便。迭代器的用法在 Python 中普遍而且统一。 在后台,for 语句在容器对象中调用 iter() 。该函数返回一个定义了 next() 方法的迭代器对象,它在容器中逐一访问元素。没有后续的元素时, next() 抛 出一个 StopIteration 异常通知 for 语句循环结束。以下是其工作原理的示 例:
|
|
了解了迭代器协议的后台机制,就可以很容易的给自己的类添加迭代器行为。定 义一个 iter() 方法,使其返回一个带有 next() 方法的对象。如果这个类 已经定义了 next() ,那么 iter() 只需要返回 self:
12345678910111213141516 class Reverse:"""Iterator for looping over a sequence backwards."""def __init__(self, data):self.data = dataself.index = len(data)def __iter__(self):return selfdef __next__(self):if self.index == 0:raise StopIterationself.index = self.index - 1return self.data[self.index]rev = Reverse('spam')iter(rev) # <__main__.Reverse object at 0x00A1DB50>for char in rev:print(char) # 输出 m a p s我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
|
|
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:
|
|
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。把list、dict、str等Iterable变成Iterator可以使用iter()函数:
|
|
为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
|
|
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
Python的for循环本质上就是通过不断调用next()函数实现的,例如:
|
|
安装的三种方法:
- sketch toolbox
- github下载插件项目,直接双击压缩包里面的.sketchplugin的插件文件
- 直接将解压缩包里面的插件文件拷贝放到Plugins路径下
插件:- Duplicator:快捷键复制插件
- Content Generator:自动内容填充
- Magic mirror:选中一个区域,可以将你制定的一个图片去填充选中区域,比如图片中一个手机界面,可以用钢笔描边选中,然后用另一张图片去填充图片中手机的屏幕,具体用法是把屏幕用vector工具转变成path
- sketch Style Inventory:导入所有的设计稿的颜色、文本样式、元件
- Material Design Color Palette Sketch Plugin:生成配色方案的插件
- sketch notebook:标注设计稿的注意事项
- Sketch Measure: 标注尺寸
只针对android手机开发者设计图片时制作,例如微信聊天文字对话框会根据文字的多少,手机屏幕尺寸进行拉伸,所以点9
图产生了。PS也可以制作点9图具体参考如下
教程
制作工具/插件
样式
- 行内样式:
This paragraph simply takes on the browser’s default paragraph style.
By adding inline CSS styling to this paragraph, you override the default styles.
- 嵌入式样式:
- 链接样式:
@import url(css/styles2.css)
:first-child和:last-child
|
|
:nth-child
e:nth-child(n)/e:nth-child(odd)/e:nth-child(even)
::before和::after伪元素
|
|
这种用法可以用在后台查询数据给前台时,进行一些渲染
在一个较大的样式表中,可能会有很多条规则都选择同一个元素的同一个属性。比如,一个带有类属性的段落,可能会被一条以标签名作选择符的规则选中并指定一种字体,而另一条以该段落的类名作选择符的规则却会给它指定另一种字体。我们知道,字体属性在任意时刻都只能应用一种设定,那此时该应用哪种字体呢?为解决类似的冲突,确定哪条规则“胜出”并最终被应用,CSS提供了三种机制:继承、层叠和特指。接下来的三节,就分别讨论这三种机制。
body {font-family:helvetica, arial, sans-serif;}
body是所有所有元素的祖宗,所有元素都会继承它的样式,对于个别想使用自己的样式,只需自身指定即可,就如同OOP编程语言里面的类继承类似,如果子类想定义自己从父类的方法,也可重写实现自己的特性。
有些属性可以继承:颜色、字体、字号等
有些属性不能继承:边框、外边距、内边距
以下就是浏览器层叠各个来源样式的顺序:
css属性值主要分为三类
数字值(又分为相对值和绝对值):font-size:12px
颜色值:color:#336699
页面版式主要由三个属性控制:position属性、display属性和float属性。其中,position属性控制页面上元素间的位置关系,display属性控制元素是堆叠、并排,还是根本不在页面上出现,float属性提供控制的方式,以便把元素组成成多栏布局。
|
|
边框(border)有3个相关属性。
- 宽度(border-width)。可以使用thin、medium和thick等文本值,也可以使用除百分比和负值之外的任何绝对值。
- 样式(border-style)。有none、hidden、dotted、dashed、solid、double、groove、ridge、inset和outset等文本值。
- 颜色(border-color)。可以使用任意颜色值,包括RGB、HSL、十六进制颜色值和颜色关键字。
一般在开发中,我们会采用中和内外边距的写法,给需要边距的再单独编写样式:“* {margin:0; padding:0;}”
下面的这个样式表不仅重置了外边距和内边距,还对很多元素在跨浏览器显示时的外观进行了标准化。至于为什么,可以参考博客文章12
12345678910111213141516171819202122232425262728293031323334353637383940 /* http://meyerweb.com/eric/tools/css/reset/v2.0 | 20110126License: none (public domain)*/html, body, div, span, applet, object, iframe,h1, h2, h3, h4, h5, h6, p, blockquote, pre,a, abbr, acronym, address, big, cite, code,del, dfn, em, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var,b, u, i, center,dl, dt, dd, ol, ul, li,fieldset, form, label, legend,table, caption, tbody, tfoot, thead, tr, th, td,article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup,menu, nav, output, ruby, section, summary,time, mark, audio, video {margin: 0;padding: 0;border: 0;font-size: 100%;font: inherit;vertical-align: baseline;}/* HTML5 display-role reset for older browsers */article, aside, details, figcaption, figure,footer, header, hgroup, menu, nav, section {display: block;}body {line-height: 1;}ol, ul {list-style: none;}blockquote, q {quotes: none;}blockquote:before, blockquote:after,q:before, q:after {content: '';content: none;}table {border-collapse: collapse;border-spacing: 0;}
请看例子
1234 为简明起见,省略了字体声明p {height:50px; border:1px solid #000; backgroundcolor:#fff; margin-top:50px; margin-bottom:30px;}由于第一段的下外边距与第二段的上外边距相邻,你自然会认为它们之间的外边距是80像素(50+30),但是你错啦!它们实际的间距是50像素。也就是说较宽的外边距决定两个元素最终离多远。
根据经验,为文本元素设置外边距时通常需要混合使用不同的单位。比如说,一个段落的左、右外边距可以使用像素,以便该段文本始终与包含元素边界保持固定间距,不受字号变大或变小的影响。而对于上、下外边距,以em为单位则可以让段间距随字号变化而相应增大或缩小
12 /*这里使用了简写属性把上、下外边距设置为.75em,把左、右外边距设置为30像素*/p {font-size:1em; margin:.75em 30px;}
12 body {font-family:helvetica, arial, sans-serif; font-size:1em; margin:0px; background-color:#caebff;}p {margin:0 30px; background-color:#fff; padding:0 20px; border:solid red; border-width:0 6px 0 6px;}外边距在元素盒子与窗口之间创造了空白,此时内容宽度变成了288像素(400 – ( (20 + 6 + 30)× 2) )。而元素声明的总宽度并没有变,仍然是400像素。
盒模型结论一:没有(就是没有设置width的)宽度的元素始终会扩展到填满其父元素的宽度为止。添加水平边框、内边距和外边距,会导致内容宽度减少,减少量等于水平边框、内边距和外边距的和。
盒模型结论二:为设定了宽度的盒子添加边框、内边距和外边距,会导致盒子扩展得更宽。实际上,盒子的width属性设定的只是盒子内容区的宽度,而非盒子要占据的水平宽度。
浮动和清除是用来组织页面布局的又一柄利剑,这柄剑的剑刃就是float和clear属性。浮动,你看这俩字儿多形象,意思就是把元素从常规文档流中拿出来。拿出来干什么?一是可以实现传统出版物上那种文字绕排图片的效果,二是可以让原来上下堆叠的块级元素,变成左右并列,从而实现布局中的分栏。浮动元素脱离了常规文档流之后,原来紧跟其后的元素就会在空间允许的情况下,向上提升到与浮动元素平起平坐。如果浮动元素后面有两个段落,而你只想让第一段与浮动元素并列(就算旁边还能放下第二段,也不想让它上来),怎么办?用clear属性来“清除”第二段,然后它就乖乖地呆在浮动元素下面了。
浮动
CSS设计float属性的主要目的,是为了实现文本绕排图片的效果。然而,这个属性居然也成了创建多栏布局最简单的方式。
文本绕排图片…the paragraph text…
/为简明起见,省略了字体声明/
p {margin:0; border:1px solid red;}
/外边距防止图片紧挨文本/
img {float:left; margin:0 4px 4px 0;
动图片会从文档流中被移除,如果在标记中有文本元素跟在它后面,则其中的文本会绕开图片.浮动非图片元素时,必须给它设定宽度,否则后果难以预料。图片无所谓,因为它本身有默认的宽度。
浮动还用另一种用途是实现分栏围住浮动元素的三种方法
- 为父元素添加overflow:hidden
- 同时浮动父元素,同时给父元素下面的文档流一个{clear:left}
- 添加非浮动的清除元素
123456789101112131415161718192021222324 <section><img src="images/rubber_duck.jpg"><p>It's fun to float.</p><div class="clear_me"></div></section><footer> Here is the footer element…</footer><!-- section {border:1px solid blue;}img {float:left;}.clear_me {clear:left;}footer {border:1px solid red;} --><section class="clearfix"><img src="images/rubber_duck.jpg"><p>It's fun to float.</p></section><footer> Here is the footer element…</footer><!-- .clearfix:after {content:".";display:block;height:0;visibility:hidden;clear:both;} --><!-- 这个clearfix规则最早是由程序员Tony Aslett发明的,它只添加了一个清除的包含句点作为非浮动元素(必须得有内容,而句点是最小的内容)1。规则中的其他声明是为了确保这个伪元素没有高度,而且在页面上不可见。使用clear:both意味着section中新增的子元素会清除左、右浮动元素(位于左、右浮动元素下方)。这里当然可以只用left,但both也适用于将来图片float:right的情况。-->
CSS布局的核心是position属性,对元素盒子应用这个属性,可以相对于它在常规文档流中的位置重新定位。position属性有4个值:static、relative、absolute、fixed,默认值为static。
- 静态定位:static块级元素会在默认文档流中上下堆叠
- 相对定位reletive:相对的是它原来在文档流中的位置,可以使用top、right、bottom和left属性来改变它的位置了。但多数情况下,只用top和left就可以实现我们想要的效果。{position:relative; top:25px; left:30px;}
- 绝对定位absolute:绝对定位会把元素彻底从文档流中拿出来,有的时候是相对body进行定位,这就涉及到定位上下文的概念了。绝对定位默认的上下文是body元素。
- 固定定位fixed:从完全移出文档流的角度说,固定定位与绝对定位类似,{position:fixed; top:30px; left:20px;},但不同之处在于,固定定位元素的定位上下文是视口(浏览器窗口或手持设备的屏幕),因此它不会随页面滚动而移动。固定定位并不常用,最常见的情况是用它创建不随页面滚动而移动的导航元素。
- 定位上下文:把元素的position属性设定为relative、absolute或fixed后,继而可以使用top、right、bottom和left属性,相对于另一个元素移动该元素的位置。这里的“另一个元素”,就是该元素的定位上下文。
在讲绝对定位的时候,我们知道绝对定位元素默认的定位上下文是body。这是因为body是标记中所有元素唯一的祖先元素。而实际上,绝对定位元素的任何祖先元素都可以成为它的定位上下文,只要你把相应祖先元素的position设定为relative即可。
所有元素都有postion和display,display的默认值是block和inline。块级元素和行内元素可以相互转换
/默认为block/p {display:inline;}/默认为inline/a {display:block;}
display属性设置为none,会把元素的display设定为none,该元素及所有包含在其中的元素,都不会在页面中显示。它们原先占据的所有空间也都会被“回收”。与此相对的是visibility属性,这个属性最常用的两个相对的值是visible(默认值)和hidden。把元素的visibility设定为hidden,元素会隐藏,但它占据的页面空间仍然“虚位以待”。
每个元素盒子都可以想象成由两个图层组成。元素的前景层包含内容(如文本或图片)和边框,元素的背景层可以用实色填充(使用background-color属性),也可以包含任意多个背景图片(使用background-image属性),背景图片叠加在背景颜色之上。
背景颜色
元素的background-color是蓝绿色,段落的background-color是白色,前景色color是灰色,前景色既影响文本,也影响边框
背景图片
background-image:url(images/blue_circle.png);比元素小的背景图片会在水平和垂直方向上重复出现,直至填满整个背景空间
要改变默认的水平和垂直重复效果,可以修改background-repeat属性;要改变背景图片的起点,可以修改background-position属性。
默认值就是repeat另外3个值分别是只在水平方向重复的repeat-x、只在垂直方向上重复的repeat-y和在任何方向上都不重复(或者说只让背景图片显示一次)的no-repeat背景位置
用于控制背景位置的background-position属性,是所有背景属性中最复杂的。background-position属性有5个关键字值,分别是top、left、bottom、right和center,这些关键字中的任意两个组合起来都可以作为该属性的值。比如,top right表示把图片放在元素的右上角位置,center center把图片放在元素的中心位置。设定背景位置时可以使用三种值:关键字、百分比、绝对或相对单位的数值
背景尺寸
background-size:
- 50%:缩放图片,使其填充背景区的一半。
- 100px 50px:把图片调整到100像素宽,50像素高。
- cover:拉大图片,使其完全填满背景区;保持宽高比。
- contain:缩放图片,使其恰好适合背景区;保持宽高比。
背景粘附
“background-attachment属性控制滚动元素内的背景图片是否随元素滚动而移动。这个属性的默认值是scroll,即背景图片随元素移动。如果把它的值改为fixed,那么背景图片不会随元素滚动而移动。
background-attachment:fixed最常用于给body元素中心位置添加淡色水印,让水印不随页面滚动而移动。
123456789 p{ background-image:url(images/watermark.png);background-position:center;background-color:#fff;background-repeat:no-repeat;background-size:contain;background-attachment:fixed;}/*简写*/body {background:url(images/watermark.png) center #fff no-repeat contain fixed;}
|
|
渐变分两种,一种线性渐变,一种放射性渐变。线性渐变从元素的一端延伸到另一端,放射性渐变则从元素内一点向四周发散。
1234567891011121314151617181920 /*为元素盒子添加样式*/div {height:150px;width:200px;border:1px solid #ccc;float:left;margin:16px;}/*例1:默认为从上到下*/.gradient1 {background:linear-gradient(#e86a43, #fff);}/*例2:从左到右*/.gradient2 {background:linear-gradient(left, #64d1dd, #fff);}/*例3:左上到右下*/.gradient3 {background:linear-gradient(-45deg, #e86a43, #fff);}
|
|
|
|
为鼓励浏览器厂商尽早采用W3C的CSS3推荐标准,于是就产生了VSP(Vendor Specific Prefixes,厂商前缀)的概念。有了这些CSS属性的前缀,厂商就可以尝试实现W3C涵盖新CSS属性的工作草案。在迅速实现新属性的同时,还可以声明它们是过渡的、部分实现的,或者实验性的。总之,后果由使用者自负。
就拿W3C推荐的transform属性为例,标准语法是这样的:
transform: skewX(-45deg);
12345 -moz-transform:skewX(-45deg); /* Firefox */-webkit-transform:skewX(-45deg); /* Chrome及Safari */-ms-transform:skewX(-45deg); /* 微软Internet Explorer */-o-transform:skewX(-45deg); /* Opera */transform:skewX(-45deg); /* 最后是W3C标准属性 */
Safari和Chrome都使用相同的-webkit-前缀,是因为它们都使用Webkit渲染引擎。”
以下CSS3属性必须加VPS:
border-image translate
linear-gradient transition
radial-gradient background
transform background-image
transform-origin
为了达到三栏布局,可以使用float:left,但还有时候随着给一栏增加内外边距等,会使得第三栏放不下,浮到下面,这里面有,这种情况下有三种手段解决:
- 从设定的元素宽度中减去添加的水平外边距、边框和内边距的宽度和。
- 在容器内部的元素上添加内边距或外边距。
- 使用CSS3的box-sizing属性切换盒子缩放方式,比如section {box-sizing:border-box;}。 应用box-sizing属性后,给section添加边框和内边距都不会增大盒子,相反会导致内容变窄。
- 子-星选择符
预防过大的元素
设计一个将来可能由他人维护的动态网站时,需要考虑得更长远一些。比如,应该预见到可能出现一些过大的元素。- img {max-width:100%;}
- overflow:hidden
- word-wrap:break-word
三栏-中栏流动布局
实现中栏流动布局有两种方法。一种是在中栏改变大小时使用负外边距定位右栏,另一种是使用CSS3让栏容器具有类似表格单元的行为。负外边距适合比较老的浏览器,而CSS的table属性则要简单得多- 用负外边距实现:实现三栏布局且让中栏内容区流动(不固定)的核心问题,就是处理右栏的定位,并在中栏内容区大小改变时控制右栏与布局的关系。解决方案是控制两个外包装(通过ID值为wrapper)容器的外边距。其中一个外包装包围所有三栏,另一个外包装只包围左栏和中栏。
123456789101112131415161718192021222324 <div id="main_wrapper"><header><!-- 页眉--></header><div id="threecolwrap">/*三栏外包装(包围全部三栏)*/<div id="twocolwrap">/*两栏外包装(包围左栏和中栏)*//*左栏*/<nav><!-- 导航 --></nav>/*中栏*/<article>“ <!-- 区块 --></article></div>/*结束两栏外包装(twocolwrap)*//*右栏*/<aside><!-- 侧栏 --></aside></div>/*结束三栏外包装(threecolwrap)*/<footer><!-- 页脚 --></footer></div>
|
|
CSS可以把一个HTML元素的display属性设定为table、table-row和table-cell。通过这种方法可以模拟相应HTML元素的行为。而通过CSS把布局中的栏设定为table-cell有三个好处。
|
|
类是用来标记具有相同特征的元素的。ID最好是用来标记每个主要区域的顶级元素,这样可以充当路标。
123456789101112131415161718192021222324252627282930 <div id="wrapper"><header><h1>Full-width content</h1></header><nav><p>Navigation menus go here</p></nav><section id="branding"><img src="images/grand_canyon.jpg" alt="Grand Canyon" /></section><!-- branding 结束 --><section id="feature_area"><article><div class="inner"><p>Lorem Ipsum text</p></div></article><!-- 省略另外两个 article 元素 --></section><!-- feature_area 结束--><section id="promo_area"><article><div class="inner"><p>Lorem Ipsum text</p></div></article><!-- 省略另外三个 article 元素 --></section><!-- promo_area 结束--><footer><p>A CSS template from <a href="http://www.stylinwithcss.com"><em>Stylin’ with CSS, Third Edition</em></a> by Charles Wyke-Smith</p></footer></div>
|
|
|
|
|
|
使用“非首位子元素”选择符:li + li选择符的意思是“任何跟在li之后的li”。
也可以这样写:
/给所有li上方添加一条边框/
li {border-top:1px solid #f00;}
/去掉第一个li上方的边框/
li:first-child {border-top:none;}
改进版
12345678910 * {margin:0; padding:0;}nav {margin:50px; width:150px;}.list1 ul {border:1px solid #f00; border-radius:3px;padding:5px 10px 3px;}.list1 li {list-style-type:none; padding:3px 10px;}.list1 li + li a {border-top:1px solid #f00;}.list1 a {display:block; padding:3px 10px; textdecoration:none; font:20px Exo, helvetica, arial, sansserif;font-weight:400; color:#000; background:#ffed53;}.list1 a:hover {color:#069;}
|
|
|
|
浮动让li元素从垂直变成水平,display:block让链接从收缩变成扩张,从而整个li元素都变成了可以点击的。另外,选择符li + li a为除第一个链接之外的每个链接左侧都加了一条竖线,作为视觉分隔线。好啦,可以讲更复杂的样式了。
下拉菜单
三级菜单的实现,是通过ul的三层嵌套来实现的
|
|
|
|
]]>
- 居中
12345678910111213141516171819 p{margin:20px auto;}/*让文本和图片垂直居中*/div {height:150px;width:250px;border:2px solid #aaa;margin:20px auto;background-image:url(images/turq_spiral_150.png);background-repeat:no-repeat;background-position:50% 50%;//text-align: center;//line-height: 250px;//}li {/*去掉列表项目符号*/list-style-type:none;}
先以一段代码开始
12345678910111213141516171819202122232425262728293031323334353637383940414243 int main(int argc, char *argv[]){// create two arrays we care aboutint ages[] = {23, 43, 12, 89, 2};char *names[] = {"Alan", "Frank","Mary", "John", "Lisa"};// safely get the size of agesint count = sizeof(ages) / sizeof(int);int i = 0;// first way using indexingfor(i = 0; i < count; i++) {printf("%s has %d years alive.\n",names[i], ages[i]);}printf("---\n");// setup the pointers to the start of the arraysint *cur_age = ages;char **cur_name = names;// second way using pointersfor(i = 0; i < count; i++) {printf("%s is %d years old.\n",*(cur_name+i), *(cur_age+i));}printf("---\n");// third way, pointers are just arraysfor(i = 0; i < count; i++) {printf("%s is %d years old again.\n",cur_name[i], cur_age[i]);}printf("---\n");// fourth way with pointers in a stupid complex wayfor(cur_name = names, cur_age = ages;(cur_age - ages) < count;cur_name++, cur_age++){printf("%s lived %d years so far.\n",*cur_name, *cur_age);}return 0;}上面的代码很好的诠释了指正的工作原理,首先
- 在你的计算机中开辟一块内存。
- 将ages这个名字“指向”它的起始位置。
- 通过选取ages作为基址,并且获取位置为i的元素,来对内存块进行索引。
- 将ages+i处的元素转换成大小正确的有效的int,这样就返回了你想要的结果:下标i处的int。
指针仅仅是指向计算机中的某个地址,并带有类型限定符,所以你可以通过它得到正确大小的数据,指针的用途就是让你手动对内存块进行索引,一些情况下数组并不能做到。绝大多数情况中,你可能打算使用数组,但是一些处理原始内存块的情况,是指针的用武之地。指针向你提供了原始的、直接的内存块访问途径,让你能够处理它们。
指针有四个最基本的操作:
- 向OS申请一块内存,并且用指针处理它。这包括字符串,和一些你从来没见过的东西,比如结构体。
- 通过指针向函数传递大块的内存(比如很大的结构体),这样不必把全部数据都传递进去。
- 获取函数的地址用于动态调用。
- 对一块内存做复杂的搜索,比如,转换网络套接字中的字节,或者解析文
其实当我们在阅读别人的代码或者自己运用指正,只要把下面这几点牢记于心,就应该没问题,一步一步去拆解理解:- type *ptr:type类型的指针,名为ptr
- *ptr:ptr所指向位置的值
- *(ptr + i):ptr所指向位置加上i)的值
- &thing:thing的地址
- type *ptr = &thing:名为ptr,type类型的指针,值设置为thing的地址
- ptr++:自增ptr指向的位置*
从一段代码搞起:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 struct Person {char *name;int age;int height;int weight;};struct Person *Person_create(char *name, int age, int height, int weight){struct Person *who = malloc(sizeof(struct Person));assert(who != NULL);//使用strdup来复制字符串name,是为了确保结构体真正拥有它。strdup的行为实际上类似malloc但是它同时会将原来的字符串复制到新创建的内存。who->name = strdup(name);who->age = age;who->height = height;who->weight = weight;return who;}void Person_destroy(struct Person *who){assert(who != NULL);free(who->name);free(who);}void Person_print(struct Person *who){printf("Name: %s\n", who->name);printf("\tAge: %d\n", who->age);printf("\tHeight: %d\n", who->height);printf("\tWeight: %d\n", who->weight);}int main(int argc, char *argv[]){// make two people structuresstruct Person *joe = Person_create("Joe Alex", 32, 64, 140);struct Person *frank = Person_create("Frank Blank", 20, 72, 180);// print them out and where they are in memoryprintf("Joe is at memory location %p:\n", joe);Person_print(joe);printf("Frank is at memory location %p:\n", frank);Person_print(frank);// make everyone age 20 years and print them againjoe->age += 20;joe->height -= 2;joe->weight += 40;Person_print(joe);frank->age += 20;frank->weight += 20;Person_print(frank);// destroy them both so we clean upPerson_destroy(joe);Person_destroy(frank);return 0;}
其实结构体就相当于数据库中的一行或者OOP语言中的类一样*
##
]]>打开博客根目录的_config.yml文件,找到:
|
|
修改为(根据自己的仓库地址修改,其中增加了开源中国仓库地址和码云的仓库地址):
|
|
然后执行下面命令,以重新生成baidusitemap.xml。
|
|
其实学习PHP或者Python,最终要的一点就是要熟悉一些常用库或者函数,这样在应用的时候才能游刃有余,下面总结一下常用的函数
查看数据类型
- gettype(传入一个变量) 能够获得变量的类型
- var_dump(传入一个变量) 输出变类型和值
判断数据类型- is_int/bool/float/string/array/object/null/resource(资源)/scalar(标量)/numeric(是否为数值类型)/callable(是否为函数)
1234 $float = 88.8;$type = gettype($float);var_dump($float);echo $type;
强制类型转换有三种方式:
- 用后面的三个函数可以完成类型转换,intval()、floatval()、strval()
- 变量前加上()里面写上类型,将它转换后赋值给其他变量
- settype(变量,类型) 直接改变量本身
定义 define(常量名,常量值);defined()函数来做安全机制
系统的一些常量:
常量明 说明 常量明 说明 LINE 当前所在的行 FILE 当前文件在服务器的路径 FUNCTIOIN 当前函数名 CLASS 当前类名 METHOD 当前成员方法名 PHP_OS PHP运行的操作系统 PHP_VERSION 当前PHP的版本 TRAIT Trait 的名字,php5.4新加 DIR 文件所在的目录 NAMESPACE 当前命名空间的名称(区分大小写
123456 define('MY_NAME','PHP');echo MY_NAME;//下面是错误的调用方式echo '我的名字是MY_NAME';//正确的调用方式该这么写echo '我的名字是' . MY_NAME;
变变量其实就是——已声明的变量前,再上变量符
123456789 $shu = 'biao';$biao = 'wo';$wo = 'test';$test = 'sina';$sina = 'zhongguo';$zhongguo = 'china';$china = '我爱你';//别运行,自己去推理一下代码。也写几个可变变量玩玩吧!echo $$$$$shu;(输出是zhongguo)PHP的外部变量是PHP 在使用过程中规定好的一些变量:
应有场景:
user.html
123456789101112 <html><head></head><body><form action="reg.php" method="get"><input type="text" name="username" /><input type="password" name="pwd" /><input type="submit" value="提交" /></form></body></html>reg.php
12345678 //$_GET后面加上中括号,将username作为字符串放在中括号里面,就得到了表单里面的<input type="text" name="username" /> 的值$u = $_GET['username'];$u = $_POST['username'];echo $u.'<br />';//$_GET['pwd'] 得到表单<input type="password" name="pwd" /> 的值$passwd = $_GET['pwd'];echo $passwd.'<br />';
全局变量名 功能说明 全局变量名 功能说明 $_COOKIE 得到会话控制中cookie传值 $_SESSION 得到会话控制中session的值 $_FILES 得到文件上传的结果 $_GET 得到get传值的结果 $_POST 得到post传值的结果 $_REQUEST 即能得到get的传值结果,也能得到post传值的结果
- 一些常用的环境变量的键名和值对应的意思:
键名 含义 $_SERVER[“REQUEST_METHOD”] 请求当前PHP页面的方法 $_SERVER[“REQUEST_URI”] 请求的URI $_SERVER[“SERVER_SOFTWARE”] 用的是哪一种服务器 $_SERVER[“REMOTE_ADDR”] 客户的IP地址 $_SERVER[“SERVER_ADDR”] 当前服务器的IP地址 $_SERVER[“SCRIPT_FILENAME”] 当前请求文件的路径 $_SERVER[“HTTP_USER_AGENT”] 当前访问这个网址的电脑和浏览器的情况 $_SERVER[“HTTP_REFERER”] 上级来源(用户从哪个地址进入当前网页的) $_SERVER[“REQUEST_TIME”] 当前请求时间
- 变量引用(类似于C语言指正)
12345678 $fo = 5;//注意,加上了一个&符哟$bar = &$fo;$bar = 6;//$bar的结果为6echo $bar.'<br />';//$fo的结果为6echo $fo.'<br />';
符号 说明 $x? 真代码段:假代码段 判断是否为真假 ? 真情况 : 假情况; ``(反引号) 反引号中间插代命令,执行系统命令,等价于shell_exec函数 @ 单行抑制错误,把这一行的错误不让它显示出来了,效率低不建议使用 => 数组下标访问符 -> 对象访问符 instanceof 判断某个对象是否来自某个类,如果是的返回true,如果不是返回false
|
|
|
|
|
|
|
|
我们将函数体外的变量通过$GLOBALS拿到了函数体使用。所以,打破了函数外的变量不能在函数体内使用的限定。
12345678 $one = 10;function demo(){$two = 100;$result = $two + $GLOBALS['one'];return $result;}//你会发现结果变成了110echo demo();
主要掌握如何使用内置函数,就是会看PHP官方文档
使用函数的重点是三块:
- 了解函数的功能,特别是常用函数的功能
- 了解函数的参数
- 了解函数的返回值
针对上面的三块,讲解6个函数,这6个函数,概况了函数的基本用法的全部注意事项:- 直接返回布尔型,如bool copy ()
- 带有MIXED参数的函数如何调用。Mixed表示任何类型的数据。如Array_unshift()
- 参数中带有&符的参数,一定要传一个变量做为参数。函数里面改变了他的值。
- 带有[]的参数,表示可选项。
- 带有…的参数,表示可以传任意多个参数。
- 带有callback的参数,表示回调函数。需要传一个函数进来。Array_map()
- 函数支持的版本你要了解
这个函数的功能为: 拷备一个文件
返回值为为: bool型值,就是成功返回true,失败返回false
参数为: 两个字符串的值,一个是copy的源文件,一个为目标文件。第三个参数可选的,不常用,我们不管它。
12345 $file = 'example.txt';$newfile = 'example.txt.bak';if (!copy($file, $newfile)) {echo "failed to copy $file...\n";}
Mixed表示任何类型的数据。
功能: 操作一个数组,向数组中之前插入其他类型的参数。
返回值: int 类型,可能就是插入成功最后的个数
参数: 第一个参数为&符,也就是在操作的过程中,改变了第一个参数的值。引用传参。也就是操作这个数组,向这个数组中传入参数。会直接改变这个数组的值。第二个参数为mixed,因为数组可以存入多个不同的类型.mixed是指混合的意思。因此,mixed是指可传入任意类型。第三个数数加了中括号,我们所有遇到中括号的。都是指后面的参数可传,也可以不传。第四,最后还看到了三个…(省略号)。代表可以传入任意多个参数。
1234567891011 $queue = array("orange", "banana");array_unshift($queue, "apple", "raspberry");print_r($queue);// The above example will output:// Array// (// [0] => apple// [1] => raspberry// [2] => orange// [3] => banana// )
功能:传入一个回调函数,将数组的原来的组操作,并且发生变化。
返回值:bool 值 也就是意味着,提示成功或者失败
参数:第一个参数是要操作的数组。第二个参数是callback 代表着可以传入函数或者匿名函数。
函数 | 包含失败 | 特点 |
---|---|---|
Inlcude | 返回一条警告 | 文件继续向下执行。通常用于动态包含 |
Require | 一个致命的错 | 代码就不会继续向下执行。通常包含极为重要的文件,整个代码甭想执行 |
Include_once | 返回一条警告 | 除了原有include的功能以外,它还会做once检测,如果文件曾经已经被被包含过,不再包含 |
Require_once | 一个致命的错 | 除了原的功能一外,会做一次once检测,防止文件反复被包含 |
在正式学习日期函数前大家要了解几个概念:
- 时区:1884年在华盛顿召开国际经度会议时,为了克服时间上的混乱,规定将全球划分为24个时区。在中国采用首都北京所在地东八区的时间为全国统一使用时间。
- 世界时:如果对国际上某一重大事情,用地方时间来记录,就会感到复杂不便.而且将来日子一长容易搞错。因此,天文学家就提出一个大家都能接受且又方便的记录方法,那就是以格林尼治(英国某地区)的地方时间为标准。
- unix时间戳:从Unix纪元(1970 年 1月1日零时)开始到一个时间经过的秒数。
时间函数有:- 设置时区:
- date_default_timezone_get()
- date_default_timezone_set()
123456 //定义一下时区常量,以后你可以放到配置文件里define('TIME_ZONE','Asia/shanghai');//执行函数date_default_timezone_set(TIME_ZONE);echo date_default_timezone_get ();echo date('Y-m-d H:i:s');
getdate获取当前系统时间
|
|
日期验证函数:bool checkdate ( int $month , int $day , int $year )
声明:
]]>内容参考:《7天学会PHP》
问题描述:给两个有序的数组,然后给出算法,使得两个数组合并成一个有序数组
算法:
1234567891011121314151617181920212223242526272829303132333435363738394041424344 // C++ program to merge two sorted arrays/using namespace std;// Merge arr1[0..n1-1] and arr2[0..n2-1] into// arr3[0..n1+n2-1]void mergeArrays(int arr1[], int arr2[], int n1,int n2, int arr3[]){int i = 0, j = 0, k = 0;// Traverse both arraywhile (i<n1 && j <n2){// Check if current element of first// array is smaller than current element// of second array. If yes, store first// array element and increment first array// index. Otherwise do same with second arrayif (arr1[i] < arr2[j])arr3[k++] = arr1[i++];elsearr3[k++] = arr2[j++];}// Store remaining elements of first arraywhile (i < n1)arr3[k++] = arr1[i++];// Store remaining elements of second arraywhile (j < n2)arr3[k++] = arr2[j++];}// Driver codeint main(){int arr1[] = {1, 3, 5, 7};int n1 = sizeof(arr1) / sizeof(arr1[0]);int arr2[] = {2, 4, 6, 8};int n2 = sizeof(arr2) / sizeof(arr2[0]);int arr3[n1+n2];mergeArrays(arr1, arr2, n1, n2, arr3);cout << "Array after merging" <<endl;for (int i=0; i < n1+n2; i++)cout << arr3[i] << " ";return 0;}
分析:Time Complexity : O(n1 + n2)
Auxiliary Space : O(n1 + n2)
- MySQL 是一个关系数据库系统,支持 SQL 查询语言。
- MySQL 可以是免费的,你不需要为它付费。
- MySQL 系统的速度非常快,同样它的性能也是十分优良的。
- MySQL 是一个管理简捷的数据库,它没有庞大而臃肿的可视化管理工具。
|
|
|
|
|
|
|
|
|
|
正如所见,MySQL的通配符很有用。但这种功能是有代价的:通配 符搜索的处理一般要比前面讨论的其他搜索所花时间更长。这里给出一 些使用通配符要记住的技巧。
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该 使用其他操作符
- 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用 在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起 来是最慢的
- 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。总之,通配符是一种极重要和有用的搜索工具,以后我们经常会用 到它。
|
|
LIKE与REGEXP 在LIKE和REGEXP之间有一个重要的差别。请 看以下两条语句:
12 SELECT column1,column2,column3 FROM tablename WHERE column1 LIKE '.1000' ORDER BY column1;SELECT column1,column2,column3 FROM tablename WHERE column1 REGEXP '1000' ORDER BY column1;如果执行上述两条语句,会发现第一条语句不返回数据,而第 二条语句返回一行。为什么?
正如第8章所述,LIKE匹配整个列。如果被匹配的文本在列值 中出现,LIKE将不会找到它,相应的行也不被返回(除非使用 通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在 列值中出现,REGEXP将会找到它,相应的行将被返回。这是一 个非常重要的差别。
那么,REGEXP能不能用来匹配整个列值(从而起与LIKE相同的作用)?答案是肯定的,使用^和$定位符(anchor)即可.
匹配不区分大小写 MySQL中的正则表达式匹配(自版本 3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大 小写,可使用BINARY关键字,如WHERE prod_name REGEXP BINARY ‘JetPack .000’。
123456789 --进行OR匹配:为搜索两个串之一(或者为这个串,或者为另一个串),使用|,如下所示SELECT column1,column2,column3 FROM tablename WHERE column1 REGEXP '1000|2000' ORDER BY column1;-- 匹配几个字符之一两个以上的OR条件 可以给出两个以上的OR条件。例如, '1000 | 2000 | 3000'将匹配1000或2000或3000。--匹配几个字符之一SELECT column1,column2,column3 FROM tablename WHERE column1 REGEXP '[123] tom' ORDER BY column1;--1 ton和2 ton都匹配且返回-- 字符集合也可以被否定,即,它们将匹配除指定字符外的任何东西。 为否定一个字符集,在集合的开始处放置一个^即可。因此,尽管[123] 匹配字符1、2或3,但[^123]却匹配除这些字符外的任何东西。-- 匹配范围-- 集合可用来定义要匹配的一个或多个字符。例如,下面的集合将匹 配数字0到9:[0123456789],为简化这种类型的集合,可使用-来定义一个范围。下面的式子功能 上等同于上述数字列表:[0-9],[a-z]匹配任意字母字符道理类似-- 匹配特殊字符,要用\\转移,\\.,代表搜索含有.字符的匹配字符类:存在找出你自己经常使用的数字、所有字母字符或所有数字字母字 符等的匹配。为更方便工作,可以使用预定义的字符集,称为字符类(character class)。
- [:alnum:]:任意字母和数字(同[a-zA-Z0-9])
- [:alpha:]: 任意字符(同[a-zA-Z])
- [:blank:]:空格和制表(同[\t])
- [:cntrl:]:ASCII控制字符(ASCII 0到31和127)
- [:digit:]:任意数字(同[0-9])
- [:graph:]:与[:print:]相同,但不包括空格
- [:lower:]:任意小写字母(同[a-z])
- [:print:]:任意可打印字符
- [:punct:]:既不在[:alnum:]又不在[:cntrl:]中的任意字符
- [:space:]:包括空格在内的任意空白字符(同[\f\n\r\t\v])
- [:upper:]:任意大写字母(同[A-Z])
- [:xdigit:]:任意十六进制数字(同[a-fA-F0-9])
匹配多个实例:
元字符 说明 * 0个或多个匹配 + 1个或多个匹配(等于{1,}) ? 0个或1个匹配(等于{0,1}) {n} 指定数目的匹配 {n,} 不少于指定数目的匹配 {n,m} 匹配数目的范围(m不超过255) 举例:正则表达式\([0-9] sticks?\):\(匹配),[0-9]匹配任意数字(这个例子中为1和5),sticks?匹配stick 和sticks(s后的?使s可选,因为?匹配它前面的任何字符的0次或1次出 现),\)匹配)。没有?,匹配stick和sticks会非常困难。
[[:digit:]]{4}:匹配连在一起的任意4位数字
定位符
| 元字符 | 说明 |
| :————-: |:————-:|
|^|文本的开始|
|$|文本的结尾|
|[[:<:]]|词的开始|
|[[:>:]]|词的结尾|
^[0-9\.]只在.或任意数字为串中第
一个字符时才匹配它们
存储在数据库表中的数据一般不是应用程序所需要的格式。下面举
几个例子。
- 如果想在一个字段中既显示公司名,又显示公司的地址,但这两 个信息一般包含在不同的表列中。
- 城市、州和邮政编码存储在不同的列中(应该这样),但邮件标签 打印程序却需要把它们作为一个恰当格式的字段检索出来。
- 列数据是大小写混合的,但报表程序需要把所有数据按大写表示 出来。
- 物品订单表存储物品的价格和数量,但不需要存储每个物品的总 价格(用价格乘以数量即可)。为打印发票,需要物品的总价格。
- 需要根据表数据进行总数、平均数计算或其他计算。
在上述每个例子中,存储在表中的数据都不是应用程序所需要的。 我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是 检索出数据,然后再在客户机应用程序或报告程序中重新格式化。
这就是计算字段发挥作用的所在了。与前面各章介绍过的列不同, 计算字段并不实际存在于数据库表中。计算字段是运行时在SELECT语句 内创建的。拼接字段
为了说明如何使用计算字段,举一个创建由两列组成的标题的简单例子。vendors表包含供应商名和位置信息。假如要生成一个供应商报表, 需要在供应商的名字中按照name(location)这样的格式列出供应商的位 置。此报表需要单个值,而表中数据存储在两个列vend_name和vend_country中在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。
12345678 -- MySQL的不同之处 多数DBMS使用+或||来实现拼接, MySQL则使用Concat()函数来实现。当把SQL语句转换成 MySQL语句时一定要把这个区别铭记在心。SELECT Concat(vend_name,'(',vend_country,')') FROM vendors ORDER BY vend_name;-- 曾提到通过删除数据右侧多余的空格来整理数据,这可以 使用MySQL的RTrim()函数来完成SELECT Concat(RTrim(vend_name),'(',RTrim(vend_country),')') FROM vendors ORDER BY vend_name;-- 使用别名SELECT Concat(RTrim(vend_name),'(',RTrim(vend_country),')') AS vend_title FROM vendors ORDER BY vend_name;-- 执行算数运算SELECT product_id,quantity,item_price,quantity*item_price AS price FROM ordertiems WHERE order_num = 20005;
函数:SQL支持利用函数来处理数据。函数一般是在数据上执行的,它给数据的转换和处理提供了方便。去掉串尾空格的RTrim()就是一个函数例子。大多数SQL实现支持以下类型的函数。
- 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数。
- 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
- 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
- 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。
|
|
函数 | 说明 |
---|---|
Left() | 返回串左边的字符 |
Length() | 返回串的长度 |
Locate() | 找出串的一个子串 |
Lower() | 将串转换为小写 |
LTrim() | 去掉串左边的空格 |
Right() | 返回串右边的字符 |
RTrim() | 去掉串右边的空格 |
Soundex() | 返回串的SOUNDEX值 |
SubString() | 返回子串的字符 |
Upper() | 将串转换为大写 |
函数 | 说明 |
---|---|
AddDate() | 增加一个日期(天、周等) |
AddTime() | 增加一个时间(时、分等) |
CurDate() | 返回当前日期 |
CurTime() | 返回当前时间 |
Date() | 返回日期时间的日期部分 |
DateDiff() | 计算两个日期之差 |
Date_Add() | 高度灵活的日期运算函数 |
Date_Format() | 返回一个格式化的日期或时间串 |
Day() | 返回一个日期的天数部分 |
DayOfWeek() | 对于一个日期,返回对应的星期几 |
Hour() | 返回一个时间的小时部分 |
Minute() | 返回一个时间的分钟部分 |
Month() | 返回一个日期的月份部分 |
Now() | 返回当前日期和时间 |
Second() | 返回一个时间的秒部分 |
Time() | 返回一个日期时间的时间部分 |
Year() | 返回一个日期的年份部分 |
|
|
函数 | 说明 |
---|---|
Abs() | 返回一个数的绝对值 |
Cos() | 返回一个角度的余弦 |
Exp() | 返回一个数的指数值 |
Mod() | 返回除操作的余数 |
Pi() | 返回圆周率 |
Rand() | 返回一个随机数 |
Sin() | 返回一个角度的正弦 |
Sqrt() | 返回一个数的平方根 |
Tan() | 返回一个角度的正切 |
我们经常需要汇总数据而不用把它们实际检索出来,为此MySQL提 供了专门的函数。使用这些函数,MySQL查询可用于检索数据,以便分 析和报表生成。这种类型的检索例子有以下几种。
- 确定表中行数(或者满足某个条件或包含某个特定值的行数)。
- 获得表中行组的和。
- 找出表列(或所有行或某些特定的行)的最大值、最小值和平均
值。
上述例子都需要对表中数据(而不是实际数据本身)汇总。因此, 返回实际表数据是对时间和处理资源的一种浪费(更不用说带宽了)。重 复一遍,实际想要的是汇总信息。为方便这种类型的检索,MySQL给出了5个聚集函数,见表12-1。 这些函数能进行上述罗列的检索。
函数 | 说明 |
---|---|
AVG() | 返回某列的平均值 |
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
SUM() | 返回某列值之和 |
|
|
|
|
GROUP BY子句和HAVING子句,分组允许把数据分为多个逻辑组,以 便能对每个组进行聚集计算。
创建分组
1 SELECT id COUNT(*) AS num_product FROM products GROUP BY id;具体使用GROUP BY子句前,需要知道一些重要的规定
- GROUP BY子句可以包含任意数目的列。这使得能对分组进行嵌套, 为数据分组提供更细致的控制。
- 如果在GROUP BY子句中嵌套了分组,数据将在最后规定的分组上 进行汇总。换句话说,在建立分组时,指定的所有列都一起计算
(所以不能从个别的列取回数据)。- GROUP BY子句中列出的每个列都必须是检索列或有效的表达式
(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在 GROUP BY子句中指定相同的表达式。不能使用别名。- 除聚集计算语句外,SELECT语句中的每个列都必须在GROUP BY子 句中给出。
- 如果分组列中具有NULL值,则NULL将作为一个分组返回。如果列 中有多行NULL值,它们将分为一组。
- GROUP BY子句必须出现在WHERE子句之后,ORDER BY子句之前
除了能用GROUP BY分组数据外,MySQL还允许过滤分组,规定包括 哪些分组,排除哪些分组。例如,可能想要列出至少有两个订单的所有 顾客。为得出这种数据,必须基于完整的分组而不是个别的行进行过滤。在这个例 子中WHERE不能完成任务,因为WHERE过滤指定的是行而不是分组。事实 上,WHERE没有分组的概念。那么,不使用WHERE使用什么呢?MySQL为此目的提供了另外的子 句,那就是HAVING子句。HAVING非常类似于WHERE。事实上,目前为止所 学过的所有类型的WHERE子句都可以用HAVING来替代。唯一的差别是 WHERE过滤行,而HAVING过滤分组。HAVING支持所有WHERE操作符 ,我们学习 了WHERE子句的条件(包括通配符条件和带多个操作符的子 句)。所学过的有关WHERE的所有这些技术和选项都适用于 HAVING。它们的句法是相同的,只是关键字有差别。
1 SELECT id,COUNT(*) AS orders FROM oders GROUP BY id HAVING COUNT(*) >= 2;HAVING和WHERE的差别 这里有另一种理解方法,WHERE在数据 分组前进行过滤,HAVING在数据分组后进行过滤。这是一个重 要的区别,WHERE排除的行不包括在分组中。这可能会改变计 算值,从而影响HAVING子句中基于这些值过滤掉的分组。
同时使用WHERE和HAVING子句例子:
12 SELECT id ,COUNT(*) AS num_prods FROM products WHERE price >= 10GROUP BY id HAVING COUNT(*)> 2;
虽然GROUP BY和ORDER BY经常完成相同的工作,但它们是非常不同的
| ORDER BY | GROUP BY |
| :————- | :————- |
| 排序产生的输出任意列都可以使用(甚至非选择的列也可以使用) | 分组行。但输出可能不是分组的顺序只可能使用选择列或表达式列,而且必须使用每个选择列表达式 |
|不一定需要|如果与聚集函数一起使用列(或表达式),则必须使用|
12 SELECT order_num ,,SUM(quantity* item_price) as ordertotal FROM orderitems GROUP BY order_numHAVING SUM(quantity* item_price)>= 50 ORDER BY ordertotal;
SQL还允许创建子查询(subquery),即嵌套在其他查询中的查询
1234567 SELECT cunst_name,cust_contact FROM customers WHERE cust_id IN (SELECT cust_id FROM orders WHERE order_num IN (SELECT order_num FROM orderitems WHERE prod_id ='TNT2'));-- 子查询当嵌套太多的时候,性能就会有问题,联结表会解决这个问题
SQL最强大的功能之一就是能在数据检索查询的执行中联结(join) 表。联结是利用SQL的SELECT能执行的最重要的操作,很好地理解联结 及其语法是学习SQL的一个极为重要的组成部分。
维护引用完整性 重要的是,要理解联结不是物理实体。换句 话说,它在实际的数据库表中不存在。联结由MySQL根据需 要建立,它存在于查询的执行当中。
在使用关系表时,仅在关系列中插入合法的数据非常重要。回
到这里的例子,如果在products表中插入拥有非法供应商ID (即没有在vendors表中出现)的供应商生产的产品,则这些
产品是不可访问的,因为它们没有关联到某个供应商。
为防止这种情况发生,可指示MySQL只允许在products表的 供应商ID列中出现合法值(即出现在vendors表中的供应商)。 这就是维护引用完整性,它是通过在表的定义中指定主键和外 键来实现的。
|
|
|
|
SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结
的基本规则也相同。首先列出所有表,然后定义表之间的关系
1234 SELECT prod_name,vend_name,prod_price,quantityFROM orderitems,products,vendorsWHERE products.vend_id = vendors.vend_idAND orderitems.prod_id = products.prod_id AND order_num = 2005;
|
|
由上面这个例子可以知道,创建列类型的语法是: col_name col_type [col_attributes][general_attributes]
给其他工作人员提供某台服务器的 mysql 中某个数据库的访问权限。
之所以要做限制,是防止对其他的数据库非法进行操作。
- 使用 root 管理员登陆 mysql
1 mysql -uroot -p;
|
|
‘%’ - 所有情况都能访问
‘localhost’ - 本机才能访问
’111.222.33.44‘ - 指定 ip 才能访问
注:修改密码
|
|
给该用户添加权限
|
|
删除用户
|
|
刷新修改
|
|