
指针基本概念 什么是地址 生活中的地址: 内存地址: 地址与内存单元中的数据是两个完全不同的概念 地址如同房间编号, 根据这个编号我们可以找到对应的房间 内存单元如同房间, 房间是专门用于存储数据的 变量地址: 系统分配给"变量"的"内存单元"的起始地址 int num = 6; // 占用4个字节 //那么变量num的地址为: 0ff06 char c = 'a'; // 占用1个字节 //那么变量c的地址为:0ff05 什么是指针 在计算机中所有数据都存储在内存单元中,而每个内存单元都有一个对应的地址, 只要通过这个地址就能找到对应单元中存储的数据. 由于通过地址能找到所需的变量单元,所以我们说该地址指向了该变量单元。将地址形象化的称为“指针” 内存单元的指针(地址)和内存单元的内容是两个不同的概念。 什么是指针变量 在C语言中,允许用一个变量来存放其它变量的地址, 这种专门用于存储其它变量地址的变量, 我们称之为指针变量 示例: int age;// 定义一个普通变量 num = 10; int *pnAge; // 定义一个指针变量 pnAge = &age; 定义指针变量的格式 指针变量的定义包括两个内容: 指针类型说明,即定义变量为一个指针变量; 指针变量名; 示例: char ch = 'a'; char *p; // 一个用于指向字符型变量的指针 p = &ch; int num = 666; int *q; // 一个用于指向整型变量的指针 q = &num; 其中,*表示这是一个指针变量 变量名即为定义的指针变量名 类型说明符表示本指针变量所指向的变量的数据类型 指针变量的初始化方法 指针变量初始化的方法有两种:定义的同时进行初始化和先定义后初始化 定义的同时进行初始化 int a = 5; int *p = &a; 先定义后初始化 int a = 5; int *p; p=&a; 把指针初始化为NULL int *p=NULL; int *q=0; 不合法的初始化: 指针变量只能存储地址, 不能存储其它类型 int *p; p = 250; // 错误写法 给指针变量赋值时,指针变量前不能再加“*” int *p; *p=&a; //错误写法 注意点: 多个指针变量可以指向同一个地址 指针的指向是可以改变的 int a = 5; int *p = &a; int b = 10; p = &b; // 修改指针指向 指针没有初始化里面是一个垃圾值,这时候我们这是一个野指针 野指针可能会导致程序崩溃 野指针访问你不该访问数据 所以指针必须初始化才可以访问其所指向存储区域 访问指针所指向的存储空间 C语言中提供了地址运算符&来表示变量的地址。其一般形式为: &变量名; C语言中提供了*来定义指针变量和访问指针变量指向的内存存储空间 在定义变量的时候 * 是一个类型说明符,说明定义的这个变量是一个指针变量 int *p=NULL; // 定义指针变量 在不是定义变量的时候 *是一个操作符,代表访问指针所指向存储空间 int a = 5; int *p = &a; printf("a = %d", *p); // 访问指针变量 指针类型 在同一种编译器环境下,一个指针变量所占用的内存空间是固定的。 虽然在同一种编译器下, 所有指针占用的内存空间是一样的,但不同类型的变量却占不同的字节数 一个int占用4个字节,一个char占用1个字节,而一个double占用8字节; 现在只有一个地址,我怎么才能知道要从这个地址开始向后访问多少个字节的存储空间呢,是4个,是1个,还是8个。 所以指针变量需要它所指向的数据类型告诉它要访问多少个字节存储空间 二级指针 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针” char c = 'a'; char *cp; cp = &c; char **cp2; cp2 = &cp; printf("c = %c", **cp2); 多级指针的取值规则 int ***m1; //取值***m1 int *****m2; //取值*****m2 练习 定义一个函数交换两个变量的值 写一个函数,同时返回两个数的和与差 数组指针的概念及定义 数组元素指针 一个变量有地址,一个数组包含若干元素,每个数组元素也有相应的地址, 指针变量也可以保存数组元素的地址 只要一个指针变量保存了数组元素的地址, 我们就称之为数组元素指针 printf(“%p %p”, &(a[0]), a); //输出结果:0x1100, 0x1100 注意: 数组名a不代表整个数组,只代表数组首元素的地址。 “p=a;”的作用是“把a数组的首元素的地址赋给指针变量p”,而不是“把数组a各元素的值赋给 p” 指针访问数组元素 int main (void) { int a[5] = {2, 4, 6, 8, 22}; int *p; // p = &(a[0]); p = a; printf(“%d %d\n”,a[0],*p); // 输出结果: 2, 2 } 在指针指向数组元素时,允许以下运算: 加一个整数(用+或+=),如p+1 减一个整数(用-或-=),如p-1 自加运算,如p++,++p 自减运算,如p--,--p 如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同 一数组中的上一个元素。 结论: 访问数组元素,可用下面两种方法: 下标法, 如a[i]形式 指针法, *(p+i)形式 注意: 数组名虽然是数组的首地址,但是数组名所所保存的数组的首地址是不可以更改的 int x[10]; x++; //错误 int* p = x; p++; //正确 指针与字符串 定义字符串的两种方式 字符数组 char string[]=”I love lnj!”; printf("%s\n",string); 字符串指针指向字符串 // 数组名保存的是数组第0个元素的地址, 指针也可以保存第0个元素的地址 char *str = "abc" 字符串指针使用注意事项 可以查看字符串的每一个字符 har *str = "lnj"; for(int i = 0; i < strlen(str);i++) { printf("%c-", *(str+i)); // 输出结果:l-n-j } 不可以修改字符串内容 // + 使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所有可以修改字符串中的的字符 // + 使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所以我们不可以修改字符串中的字符 char *str = "lnj"; *(str+2) = 'y'; // 错误 不能够直接接收键盘输入 // 错误的原因是:str是一个野指针,他并没有指向某一块内存空间 // 所以不允许这样写如果给str分配内存空间是可以这样用 的 char *str; scanf("%s", str); 指向函数指针 为什么指针可以指向一个函数? 函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址 函数有自己的地址,那就好办了,我们的指针变量就是用来存储地址的。 因此可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。 指针函数的定义 格式: 返回值类型 (*指针变量名)(形参1, 形参2, ...); int sum(int a,int b) { return a + b; } int (*p)(int,int); p = sum; 指针函数定义技巧 1、把要指向函数头拷贝过来 2、把函数名称使用小括号括起来 3、在函数名称前面加上一个* 4、修改函数名称 应用场景 调用函数 将函数作为参数在函数间传递 注意点: 由于这类指针变量存储的是一个函数的入口地址,所以对它们作加减运算(比如p++)是无意义的 函数调用中"(指针变量名)"的两边的括号不可少,其中的不应该理解为求值运算,在此处它 只是一种表示符号
数组的基本概念 数组,从字面上看,就是一组数据的意思,没错,数组就是用来存储一组数据的 在C语言中,数组属于构造数据类型 数组的几个名词 数组:一组相同数据类型数据的有序的集合 数组元素: 构成数组的每一个数据。 数组的下标: 数组元素位置的索引(从0开始) 数组的应用场景 一个int类型的变量能保存一个人的年龄,如果想保存整个班的年龄呢? 第一种方法是定义很多个int类型的变量来存储 第二种方法是只需要定义一个int类型的数组来存储 #include <stdio.h> int main(int argc, const char * argv[]) { /* // 需求: 保存2个人的分数 int score1 = 99; int score2 = 60; // 需求: 保存全班同学的分数(130人) int score3 = 78; int score4 = 68; ... int score130 = 88; */ // 数组: 如果需要保存`一组``相同类型`的数据, 就可以定义一个数组来保存 // 只要定义好一个数组, 数组内部会给每一块小的存储空间一个编号, 这个编号我们称之为 索引, 索引从0开始 // 1.定义一个可以保存3个int类型的数组 int scores[3]; // 2.通过数组的下标往数组中存放数据 scores[0] = 998; scores[1] = 123; scores[2] = 567; // 3.通过数组的下标从数组中取出存放的数据 printf("%i\n", scores[0]); printf("%i\n", scores[1]); printf("%i\n", scores[2]); return 0; } 定义数组 元素类型 数组名[元素个数]; // int 元素类型 // ages 数组名称 // [10] 元素个数 int ages[10]; 初始化数组 定义的同时初始化 指定元素个数,完全初始化 其中在{ }中的各数据值即为各元素的初值,各值之间用逗号间隔 int ages[3] = {4, 6, 9}; 不指定元素个数,完全初始化 根据大括号中的元素的个数来确定数组的元素个数 int nums[] = {1,2,3,5,6}; 指定元素个数,部分初始化 没有显式初始化的元素,那么系统会自动将其初始化为0 int nums[10] = {1,2}; 指定元素个数,部分初始化 int nums[5] = {[4] = 3,[1] = 2}; 不指定元素个数,部分初始化 int nums[] = {[4] = 3}; 先定义后初始化 int nums[3]; nums[0] = 1; nums[1] = 2; nums[2] = 3; 没有初始化会怎样? 如果定义数组后,没有初始化,数组中是有值的,是随机的垃圾数,所以如果想要正确使用数组应该要进行初始化。 int nums[5]; printf("%d\n", nums[0]); printf("%d\n", nums[1]); printf("%d\n", nums[2]); printf("%d\n", nums[3]); printf("%d\n", nums[4]); 输出结果: 0 0 1606416312 0 1606416414 注意点: 使用数组时不能超出数组的索引范围使用, 索引从0开始, 到元素个数-1结束 使用数组时不要随意使用未初始化的元素, 有可能是一个随机值 对于数组来说, 只能在定义的同时初始化多个值, 不能先定义再初始化多个值 int ages[3]; ages = {4, 6, 9}; // 报错 数组的使用 通过下标(索引)访问: // 找到下标为0的元素, 赋值为10 ages[0]=10; // 取出下标为2的元素保存的值 int a = ages[2]; printf("a = %d", a); 数组的遍历 数组的遍历:遍历的意思就是有序地查看数组的每一个元素 int ages[4] = {19, 22, 33, 13}; for (int i = 0; i < 4; i++) { printf("ages[%d] = %d\n", i, ages[i]); } 数组长度计算方法 因为数组在内存中占用的字节数取决于其存储的数据类型和数据的个数 数组所占用存储空间 = 一个元素所占用存储空间 * 元素个数(数组长度) 所以计算数组长度可以使用如下方法 数组的长度 = 数组占用的总字节数 / 数组元素占用的字节数 int ages[4] = {19, 22, 33, 13}; int length = sizeof(ages)/sizeof(int); printf("length = %d", length); 输出结果: 4 练习 正序输出(遍历)数组 int ages[4] = {19, 22, 33, 13}; for (int i = 0; i < 4; i++) { printf("ages[%d] = %d\n", i, ages[i]); } 逆序输出(遍历)数组 int ages[4] = {19, 22, 33, 13}; for (int i = 3; i >=0; i--) { printf("ages[%d] = %d\n", i, ages[i]); } 从键盘输入数组长度,构建一个数组,然后再通过for循环从键 盘接收数字给数组初始化。并使用for循环输出查看 数组内部存储细节 存储方式: 1)内存寻址从大到小, 从高地址开辟一块连续没有被使用的内存给数组 2)从分配的连续存储空间中, 地址小的位置开始给每个元素分配空间 3)从每个元素分配的存储空间中, 地址最大的位置开始存储数据 4)用数组名指向整个存储空间最小的地址 示例 #include <stdio.h> int main() { int num = 9; char cs[] = {'l','n','j'}; printf("cs = %p\n", &cs); // cs = 0060FEA9 printf("cs[0] = %p\n", &cs[0]); // cs[0] = 0060FEA9 printf("cs[1] = %p\n", &cs[1]); // cs[1] = 0060FEAA printf("cs[2] = %p\n", &cs[2]); // cs[2] = 0060FEAB int nums[] = {2, 6}; printf("nums = %p\n", &nums); // nums = 0060FEA0 printf("nums[0] = %p\n", &nums[0]);// nums[0] = 0060FEA0 printf("nums[1] = %p\n", &nums[1]);// nums[1] = 0060FEA4 return 0; } 注意:字符在内存中是以对应ASCII码值的二进制形式存储的,而非上述的形式。 数组的越界问题 数组越界导致的问题 约错对象 程序崩溃 char cs1[2] = {1, 2}; char cs2[3] = {3, 4, 5}; cs2[3] = 88; // 注意:这句访问到了不属于cs1的内存 printf("cs1[0] = %d\n", cs1[0] ); 输出结果: 88 为什么上述会输出88, 自己按照"数组内部存储细节"画图脑补 数组注意事项 在定义数组的时候[]里面只能写整型常量或者是返回整型常量的表达式 int ages4['A'] = {19, 22, 33}; printf("ages4[0] = %d\n", ages4[0]); int ages5[5 + 5] = {19, 22, 33}; printf("ages5[0] = %d\n", ages5[0]); int ages5['A' + 5] = {19, 22, 33}; printf("ages5[0] = %d\n", ages5[0]); 错误写法 // 没有指定元素个数,错误 int a[]; // []中不能放变量 int number = 10; int ages[number]; // 老版本的C语言规范不支持 printf("%d\n", ages[4]); int number = 10; int ages2[number] = {19, 22, 33} // 直接报错 // 只能在定义数组的时候进行一次性(全部赋值)的初始化 int ages3[5]; ages10 = {19, 22, 33}; // 一个长度为n的数组,最大下标为n-1, 下标范围:0~n-1 int ages4[4] = {19, 22, 33} ages4[8]; // 数组角标越界 练习 从键盘录入当天出售BTC的价格并计算出售的BTC的总价和平均价(比如说一天出售了10个比特币) 数组和函数 数组可以作为函数的参数使用,数组用作函数参数有两种形式: 一种是把数组元素作为实参使用 一种是把数组名作为函数的形参和实参使用 数组元素作为函数参数 数组的元素作为函数实参,与同类型的简单变量作为实参一样,如果是基本数据类型, 那么形参的改变不影响实参 void change(int val)// int val = number { val = 55; } int main(int argc, const char * argv[]) { int ages[3] = {1, 5, 8}; printf("ages[0] = %d", ages[0]);// 1 change(ages[0]); printf("ages[0] = %d", ages[0]);// 1 } 用数组元素作函数参数不要求形参也必须是数组元素 数组名作为函数参数 在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址,因此,当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递" 实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元, 系统不再为形参数组分配存储单元 既然两个数组共享一段内存单元, 所以形参数组修改时,实参数组也同时被修改了 void change2(int array[3])// int array = 0ffd1 { array[0] = 88; } int main(int argc, const char * argv[]) { int ages[3] = {1, 5, 8}; printf("ages[0] = %d", ages[0]);// 1 change(ages); printf("ages[0] = %d", ages[0]);// 88 } 数组名作函数参数的注意点 在函数形参表中,允许不给出形参数组的长度 void change(int array[]) { array[0] = 88; } 形参数组和实参数组的类型必须一致,否则将引起错误。 void prtArray(double array[3]) // 错误写法 { for (int i = 0; i < 3; i++) { printf("array[%d], %f", i, array[i]); } } int main(int argc, const char * argv[]) { int ages[3] = {1, 5, 8}; prtArray(ages[0]); } 当数组名作为函数参数时, 因为自动转换为了指针类型,所以在函数中无法动态计算除数组的元素个数 void printArray(int array[]) { printf("printArray size = %lu\n", sizeof(array)); // 8 int length = sizeof(array)/ sizeof(int); // 2 printf("length = %d", length); } 练习: 设计一个函数int arrayMax(int a[], int count)找出数组元素的最大值 从键盘输入3个09的数字,然后输出09中哪些数字没有出现过 要求从键盘输入6个0~9的数字,排序后输出
进制基本概念 什么是进制? 进制是一种计数的方式,数值的表示形式 常见的进制 十进制、二进制、八进制、十六进制 进制书写的格式和规律 十进制 0、1、2、3、4、5、6、7、8、9 逢十进一 二进制 0、1 逢二进一 书写形式:需要以0b或者0B开头,例如: 0b101 八进制 0、1、2、3、4、5、6、7 逢八进一 书写形式:在前面加个0,例如: 061 十六进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 逢十六进一 书写形式:在前面加个0x或者0X,例如: 0x45 练习 1.用不同进制表示如下有多少个方格 2.判断下列数字是否合理 00011 0x001 0x7h4 10.98 0986 .089-109 +178 0b325 0b0010 0xffdc 96f 96.0f 96.oF -.003 进制转换 10 进制转 2 进制 除2取余, 余数倒序; 得到的序列就是二进制表示形式 例如: 将十进制(97) 10转换为二进制数 2 进制转 10 进制 每一位二进制进制位的值 * 2的当前索引次幂; 再将所有位求出的值相加 例如: 将二进制01100100转换为十进制 01100100 索引从右至左, 从零开始 第0位: 0 * 2^0 = 0; 第1位: 0 * 2^1 = 0; 第2位: 1 * 2^2 = 4; 第3位: 0 * 2^3 = 0; 第4位: 0 * 2^4 = 0; 第5位: 1 * 2^5 = 32; 第6位: 1 * 2^6 = 64; 第7位: 0 * 2^7 = 0; 最终结果为: 0 + 0 + 4 + 0 + 0 + 32 + 64 + 0 = 100 2 进制转 8 进制 三个二进制位代表一个八进制位, 因为3个二进制位的最大值是7,而八进制是逢8进1 例如: 将二进制01100100转换为八进制数 从右至左每3位划分为8进制的1位, 不够前面补0 001 100 100 第0位: 100 等于十进制 4 第1位: 100 等于十进制 4 第2位: 001 等于十进制 1 最终结果: 144就是转换为8进制的值 2 进制转 16 进制 四个二进制位代表一个十六进制位,因为4个二进制位的最大值是15,而十六进制是逢16进1 例如: 将二进制01100100转换为十六进制数 从右至左每4位划分为16进制的1位, 不够前面补0 0110 0100 第0位: 0100 等于十进制 4 第1位: 0110 等于十进制 6 最终结果: 64就是转换为16进制的值 其它进制转换为十进制 系数 * 基数 ^ 索引 之和 十进制 --> 十进制 12345 = 10000 + 2000 + 300 + 40 + 5 = (1 * 10 ^ 4) + (2 * 10 ^ 3) + (3 * 10 ^ 2) + (4 * 10 ^ 1) + (5 * 10 ^ 0) = (1 * 10000) + (2 + 1000) + (3 * 100) + (4 * 10) + (5 * 1) = 10000 + 2000 + 300 + 40 + 5 = 12345 规律: 其它进制转换为十进制的结果 = 系数 * 基数 ^ 索引 之和 系数: 每一位的值就是一个系数 基数: 从x进制转换到十进制, 那么x就是基数 索引: 从最低位以0开始, 递增的数 二进制 --> 十进制 543210 101101 = (1 * 2 ^ 5) + (0 * 2 ^ 4) + (1 * 2 ^ 3) + (1 * 2 ^ 2) + (0 * 2 ^ 1) + (1 * 2 ^ 0) = 32 + 0 + 8 + 4 + 0 + 1 = 45 八进制 --> 十进制 016 = (0 * 8 ^ 2) + (1 * 8 ^ 1) + (6 * 8 ^ 0) = 0 + 8 + 6 = 14 十六进制 --> 十进制 0x11f = (1 * 16 ^ 2) + (1 * 16 ^ 1) + (15 * 16 ^ 0) = 256 + 16 + 15 = 287 十进制快速转换为其它进制 十进制除以基数取余, 倒叙读取 十进制 --> 二进制 100 --> 1100100 100 / 2 = 50 0 50 / 2 = 25 0 25 / 2 = 12 1 12 / 2 = 6 0 6 / 2 = 3 0 3 / 2 = 1 1 1 / 2 = 0 1 十进制 --> 八进制 100 --> 144 100 / 8 = 12 4 12 / 8 = 1 4 1 / 8 = 0 1 十进制 --> 十六进制 100 --> 64 100 / 16 = 6 4 6 / 16 = 0 6 十进制小数转换为二进制小数 整数部分,直接转换为二进制即可 小数部分,使用"乘2取整,顺序排列" 用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,直到积中的小数部分为零,或者达到所要求的精度为止 然后把取出的整数部分按顺序排列起来, 即是小数部分二进制 最后将整数部分的二进制和小数部分的二进制合并起来, 即是一个二进制小数 例如: 将12.125转换为二进制 // 整数部分(除2取余) 12 / 2 ------ 6 // 余0 / 2 ------ 3 // 余0 / 2 ------ 1 // 余1 / 2 ------ 0 // 余1 //12 --> 1100 // 小数部分(乘2取整数积) 0.125 * 2 ------ 0.25 //0 0.25 * 2 ------ 0.5 //0 0.5 * 2 ------ 1.0 //1 0.0 // 0.125 --> 0.001 // 12.8125 --> 1100.001 二进制小数转换为十进制小数 整数部分按照二进制转十进制即可 小数部分从最高位开始乘以2的负n次方, n从1开始 例如: 将 1100.001转换为十进制 // 整数部分(乘以2的n次方, n从0开始) 0 * 2^0 = 0 0 * 2^1 = 0 1 * 2^2 = 4 1 * 2^3 = 8 // 1100 == 8 + 4 + 0 + 0 == 12 // 小数部分(乘以2的负n次方, n从0开始) 0 * (1/2) = 0 0 * (1/4) = 0 1 * (1/8) = 0.125 // .100 == 0 + 0 + 0.125 == 0.125 // 1100.001 --> 12.125 练习: 将0.8125转换为二进制 将0.1101转换为十进制 0.8125 * 2 -------- 1.625 // 1 0.625 * 2 -------- 1.25 // 1 0.25 * 2 -------- 0.5 // 0 * 2 -------- 1.0 // 1 0.0 // 0. 8125 --> 0.1101 1*(1/2) = 0.5 1*(1/4)=0.25 0*(1/8)=0 1*(1/16)=0.0625 //0.1101 --> 0.5 + 0.25 + 0 + 0.0625 == 0.8125 原码反码补码 计算机只能识别0和1, 所以计算机中存储的数据都是以0和1的形式存储的 数据在计算机内部是以补码的形式储存的, 所有数据的运算都是以补码进行的 正数的原码、反码和补码 正数的原码、反码和补码都是它的二进制 例如: 12的原码、反码和补码分别为 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 1100 0000 0000 0000 0000 0000 0000 0000 1100 负数的原码、反码和补码 二进制的最高位我们称之为符号位, 最高位是0代表是一个正数, 最高位是1代表是一个负数 一个负数的原码, 是将该负数的二进制最高位变为1 一个负数的反码, 是将该数的原码除了符号位以外的其它位取反 一个负数的补码, 就是它的反码 + 1 例如: -12的原码、反码和补码分别为 0000 0000 0000 0000 0000 0000 0000 1100 // 12二进制 1000 0000 0000 0000 0000 0000 0000 1100 // -12原码 1111 1111 1111 1111 1111 1111 1111 0011 // -12反码 1111 1111 1111 1111 1111 1111 1111 0100 // -12补码 负数的原码、反码和补码逆向转换 反码 = 补码-1 原码= 反码最高位不变, 其它位取反 1111 1111 1111 1111 1111 1111 1111 0100 // -12补码 1111 1111 1111 1111 1111 1111 1111 0011 // -12反码 1000 0000 0000 0000 0000 0000 0000 1100 // -12原码 为什么要引入反码和补码 在学习本节内容之前,大家必须明白一个东西, 就是计算机只能做加法运算, 不能做减法和乘除法, 所以的减法和乘除法内部都是用加法来实现的 例如: 1 - 1, 内部其实就是 1 + (-1); 例如: 3 * 3, 内部其实就是 3 + 3 + 3; 例如: 9 / 3, 内部其实就是 9 + (-3) + (-3) + (-3); 首先我们先来观察一下,如果只有原码会存储什么问题 很明显, 通过我们的观察, 如果只有原码, 1-1的结果不对 // 1 + 1 0000 0000 0000 0000 0000 0000 0000 0001 // 1原码 +0000 0000 0000 0000 0000 0000 0000 0001 // 1原码 --------------------------------------- 0000 0000 0000 0000 0000 0000 0000 0010 == 2 // 1 - 1; 1 + (-1); 0000 0000 0000 0000 0000 0000 0000 0001 // 1原码 +1000 0000 0000 0000 0000 0000 0000 0001 // -1原码 --------------------------------------- 1000 0000 0000 0000 0000 0000 0000 0010 == -2 正是因为对于减法来说,如果使用原码结果是不正确的, 所以才引入了反码 通过反码计算减法的结果, 得到的也是一个反码; 将计算的结果符号位不变其余位取反,就得到了计算结果的原码 通过对原码的转换, 很明显我们计算的结果是-0, 符合我们的预期 // 1 - 1; 1 + (-1); 0000 0000 0000 0000 0000 0000 0000 0001 // 1反码 1111 1111 1111 1111 1111 1111 1111 1110 // -1反码 --------------------------------------- 1111 1111 1111 1111 1111 1111 1111 1111 // 计算结果反码 1000 0000 0000 0000 0000 0000 0000 0000 // 计算结果原码 == -0 虽然反码能够满足我们的需求, 但是对于0来说, 前面的负号没有任何意义, 所以才引入了补码 由于int只能存储4个字节, 也就是32位数据, 而计算的结果又33位, 所以最高位溢出了,符号位变成了0, 所以最终得到的结果是0 // 1 - 1; 1 + (-1); 0000 0000 0000 0000 0000 0000 0000 0001 // 1补码 1111 1111 1111 1111 1111 1111 1111 1111 // -1补码 --------------------------------------- 10000 0000 0000 0000 0000 0000 0000 0000 // 计算结果补码 0000 0000 0000 0000 0000 0000 0000 0000 // == 0 位运算符 程序中的所有数据在计算机内存中都是以二进制的形式储存的。 位运算就是直接对整数在内存中的二进制位进行操作 C语言提供了6个位操作运算符, 这些运算符只能用于整型操作数 符号 名称 运算结果 & 按位与 同1为1 | 按位或 有1为1 ^ 按位异或 不同为1 ~ 按位取反 0变1,1变0 << 按位左移 乘以2的n次方 >> 按位右移 除以2的n次方 按位与: 只有对应的两个二进位均为1时,结果位才为1,否则为0 规律: 二进制中,与1相&就保持原位,与0相&就为0 9&5 = 1 1001 &0101 ------ 0001 按位或: 只要对应的二个二进位有一个为1时,结果位就为1,否则为0 9|5 = 13 1001 |0101 ------ 1101 按位异或 当对应的二进位相异(不相同)时,结果为1,否则为0 规律: 相同整数相的结果是0。比如55=0 多个整数相^的结果跟顺序无关。例如: 567=576 同一个数异或另外一个数两次, 结果还是那个数。例如: 577 = 5 9^5 = 12 1001 ^0101 ------ 1100 按位取反 各二进位进行取反(0变1,1变0) ~9 =-10 0000 0000 0000 0000 0000 1001 // 取反前 1111 1111 1111 1111 1111 0110 // 取反后 // 根据负数补码得出结果 1111 1111 1111 1111 1111 0110 // 补码 1111 1111 1111 1111 1111 0101 // 反码 1000 0000 0000 0000 0000 1010 // 源码 == -10 位运算应用场景: 判断奇偶(按位或) 偶数: 的二进制是以0结尾 8 -> 1000 10 -> 1010 奇数: 的二进制是以1结尾 9 -> 1001 11 -> 1011 任何数和1进行&操作,得到这个数的最低位 1000 &0001 ----- 0000 // 结果为0, 代表是偶数 1011 &0001 ----- 0001 // 结果为1, 代表是奇数 权限系统 enum Unix { S_IRUSR = 256,// 100000000 用户可读 S_IWUSR = 128,// 10000000 用户可写 S_IXUSR = 64,// 1000000 用户可执行 S_IRGRP = 32,// 100000 组可读 S_IWGRP = 16,// 10000 组可写 S_IXGRP = 8,// 1000 组可执行 S_IROTH = 4,// 100 其它可读 S_IWOTH = 2,// 10 其它可写 S_IXOTH = 1 // 1 其它可执行 }; // 假设设置用户权限为可读可写 printf("%d\n", S_IRUSR | S_IWUSR); // 384 // 110000000 交换两个数的值(按位异或) a = a^b; b = b^a; a = a^b; 按位左移 把整数a的各二进位全部左移n位,高位丢弃,低位补0 由于左移是丢弃最高位,0补最低位,所以符号位也会被丢弃,左移出来的结果值可能会改变正负性 规律: 左移n位其实就是乘以2的n次方 2<<1; //相当于 2 *= 2 // 4 0010 <<0100 2<<2; //相当于 2 *= 2^2; // 8 0010 <<1000 按位右移 把整数a的各二进位全部右移n位,保持符号位不变 为正数时, 符号位为0,最高位补0 为负数时,符号位为1,最高位是补0或是补1(取决于编译系统的规定) 规律: 快速计算一个数除以2的n次方 2>>1; //相当于 2 /= 2 // 1 0010 >>0001 4>>2; //相当于 4 /= 2^2 // 1 0100 >>0001 练习: 写一个函数把一个10进制数按照二进制格式输出 #include <stdio.h> void printBinary(int num); int main(int argc, const char * argv[]) { printBinary(13); } void printBinary(int num){ int len = sizeof(int)*8; int temp; for (int i=0; i<len; i++) { temp = num; //每次都在原数的基础上进行移位运算 temp = temp>>(31-i); //每次移动的位数 int t = temp&1; //取出最后一位 if(i!=0&&i%4==0)printf(" "); printf("%d",t); } } 变量内存分析 内存模型 内存模型是线性的(有序的) 对于 32 机而言,最大的内存地址是2^32次方bit(4294967296)(4GB) 对于 64 机而言,最大的内存地址是2^64次方bit(18446744073709552000)(171亿GB) CPU 读写内存 CPU 在运作时要明确三件事 存储单元的地址(地址信息) 器件的选择,读 or 写 (控制信息) 读写的数据 (数据信息) 如何明确这三件事情 通过地址总线找到存储单元的地址 通过控制总线发送内存读写指令 通过数据总线传输需要读写的数据 地址总线: 地址总线宽度决定了CPU可以访问的物理地址空间(寻址能力) 例如: 地址总线的宽度是1位, 那么表示可以访问 0 和 1的内存 例如: 地址总线的位数是2位, 那么表示可以访问 00、01、10、11的内存 数据总线: 数据总线的位数决定CPU单次通信能交换的信息数量 例如: 数据总线:的宽度是1位, 那么一次可以传输1位二进制数据 例如: 地址总线的位数是2位,那么一次可以传输2位二进制数据 控制总线: 用来传送各种控制信号 写入流程 CPU 通过地址线将找到地址为 FFFFFFFB 的内存 CPU 通过控制线发出内存写入命令,选中存储器芯片,并通知它,要其写入数据。 CPU 通过数据线将数据 8 送入内存 FFFFFFFB 单元中 读取流程 CPU 通过地址线将找到地址为 FFFFFFFB 的内存 CPU 通过控制线发出内存读取命令,选中存储器芯片,并通知它,将要从中读取数据 存储器将 FFFFFFFB 号单元中的数据 8 通过数据线送入 CPU寄存器中 变量的存储原则 先分配字节地址大内存,然后分配字节地址小的内存(内存寻址是由大到小) 变量的首地址,是变量所占存储空间字节地址(最小的那个地址 ) 低位保存在低地址字节上,高位保存在高地址字节上 10的二进制: 0b00000000 00000000 00000000 00001010 高字节← →低字节 Qt Creator编译过程做了什么? 当我们按下运行按钮的时, 其实Qt Creator编译器做了5件事情 对源文件进行预处理, 生成预处理文件 对预处理文件进行编译, 生成汇编文件 对汇编文件进行编译, 生成二进制文件 对二进制文件进行链接, 生成可执行文件 运行可执行文件 Qt Creator编译过程验证 1.编写代码, 保存源文件: #include <stdio.h> int main(){ printf("hello lnj\n"); return 0; } 2.执行预处理编译 执行预处理编译后生成的文件 打开预处理编译后生成的文件 处理源文件中预处理相关的指令 处理源文件中多余注释等 3.执行汇编编译 执行汇编编译后生成的文件 打开汇编编译后生成的文件 4.执行二进制编译 执行二进制编译后生成的文件 打开二进制编译后生成的文件 5.执行链接操作 将依赖的一些C语言函数库和我们编译好的二进制合并为一个文件 执行链接操作后生成的文件 6.运行链接后生成的文件 计算机是运算过程分析 1.编写一个简单的加法运算 2.调试编写好的代码, 查看对应的汇编文件 结论: 1.通过地址线找到对应地址的存储单元 2.通过控制线发送内存读取指令 3.通过数据线将内存中的值传输到CPU寄存器中 4.在CPU中完成计算操作 5.通过地址线找到对应地址的存储单元 6.通过控制线发送内存写入指令 7.通过数据线将计算结果传输到内存中 char类型内存存储细节 char类型基本概念 char是C语言中比较灵活的一种数据类型,称为“字符型” char类型变量占1个字节存储空间,共8位 除单个字符以外, C语言的的转义字符也可以利用char类型存储 字符 意义 \b 退格(BS)当前位置向后回退一个字符 \r 回车(CR),将当前位置移至本行开头 \n 换行(LF),将当前位置移至下一行开头 \t 水平制表(HT),跳到下一个 TAB 位置 \0 用于表示字符串的结束标记 \ 代表一个反斜线字符 \ \" 代表一个双引号字符" \' 代表一个单引号字符' char型数据存储原理 计算机只能识别0和1, 所以char类型存储数据并不是存储一个字符, 而是将字符转换为0和1之后再存储 正是因为存储字符类型时需要将字符转换为0和1, 所以为了统一, 老美就定义了一个叫做ASCII表的东东 ASCII表中定义了每一个字符对应的整数 char ch1 = 'a'; printf("%i\n", ch1); // 97 char ch2 = 97; printf("%c\n", ch2); // a char类型注意点 char类型占一个字节, 一个中文字符占3字节(unicode表),所有char不可以存储中文 char c = '我'; // 错误写法 除转义字符以外, 不支持多个字符 char ch = 'ab'; // 错误写法 char类型存储字符时会先查找对应的ASCII码值, 存储的是ASCII值, 所以字符6和数字6存储的内容不同 char ch1 = '6'; // 存储的是ASCII码 64 char ch2 = 6; // 存储的是数字 6 练习 定义一个函数, 实现输入一个小写字母,要求转换成大写输出 类型说明符 类型说明符基本概念 C语言提供了说明长度和说明符号位的两种类型说明符, 这两种类型说明符一共有4个: short 短整型 (说明长度) long 长整型 (说明长度) signed 有符号型 (说明符号位) unsigned 无符号型 (说明符号位) 这些说明符是用来修饰int类型的,所以在使用时可以省略int 这些说明符都属于C语言关键字 short和long short和long可以提供不同长度的整型数,也就是可以改变整型数的取值范围。 在64bit编译器环境下,int占用4个字节(32bit),取值范围是-2^31 ~ 2^31-1; short占用2个字节(16bit),取值范围是-2^15 ~ 2^15-1; long占用8个字节(64bit),取值范围是-2^63 ~ 2^63-1 总结一下:在64位编译器环境下: short占2个字节(16位) int占4个字节(32位) long占8个字节(64位)。 因此,如果使用的整数不是很大的话,可以使用short代替int,这样的话,更节省内存开销。 世界上的编译器林林总总,不同编译器环境下,int、short、long的取值范围和占用的长度又是不一样的。比如在16bit编译器环境下,long只占用4个字节。不过幸运的是,ANSI \ ISO制定了以下规则: short跟int至少为16位(2字节) long至少为32位(4字节) short的长度不能大于int,int的长度不能大于long char一定为为8位(1字节),毕竟char是我们编程能用的最小数据类型 可以连续使用2个long,也就是long long。一般来说,long long的范围是不小于long的,比如在32bit编译器环境下,long long占用8个字节,long占用4个字节。不过在64bit编译器环境下,long long跟long是一样的,都占用8个字节。 #include <stdio.h> int main() { // char占1个字节, char的取值范围 -2^7~2^7 char num = 129; printf("size = %i\n", sizeof(num)); // 1 printf("num = %i\n", num); // -127 // short int 占2个字节, short int的取值范围 -2^15~2^15-1 short int num1 = 32769;// -32767 printf("size = %i\n", sizeof(num1)); // 2 printf("num1 = %hi\n", num1); // int占4个字节, int的取值范围 -2^31~2^31-1 int num2 = 12345678901; printf("size = %i\n", sizeof(num2)); // 4 printf("num2 = %i\n", num2); // long在32位占4个字节, 在64位占8个字节 long int num3 = 12345678901; printf("size = %i\n", sizeof(num3)); // 4或8 printf("num3 = %ld\n", num3); // long在32位占8个字节, 在64位占8个字节 -2^63~2^63-1 long long int num4 = 12345678901; printf("size = %i\n", sizeof(num4)); // 8 printf("num4 = %lld\n", num4); // 由于short/long/long long一般都是用于修饰int, 所以int可以省略 short num5 = 123; printf("num5 = %lld\n", num5); long num6 = 123; printf("num6 = %lld\n", num6); long long num7 = 123; printf("num7 = %lld\n", num7); return 0; } signed和unsigned 首先要明确的:signed int等价于signed,unsigned int等价于unsigned signed和unsigned的区别就是它们的最高位是否要当做符号位,并不会像short和long那样改变数据的长度,即所占的字节数。 signed:表示有符号,也就是说最高位要当做符号位。但是int的最高位本来就是符号位,因此signed和int是一样的,signed等价于signed int,也等价于int。signed的取值范围是-2^31 ~ 2^31 - 1 unsigned:表示无符号,也就是说最高位并不当做符号位,所以不包括负数。 因此unsigned的取值范围是:0000 0000 0000 0000 0000 0000 0000 0000 ~ 1111 1111 1111 1111 1111 1111 1111 1111,也就是0 ~ 2^32 - 1 #include <stdio.h> int main() { // 1.默认情况下所有类型都是由符号的 int num1 = 9; int num2 = -9; int num3 = 0; printf("num1 = %i\n", num1); printf("num2 = %i\n", num2); printf("num3 = %i\n", num3); // 2.signed用于明确说明, 当前保存的数据可以是有符号的, 一般情况下很少使用 signed int num4 = 9; signed int num5 = -9; signed int num6 = 0; printf("num4 = %i\n", num4); printf("num5 = %i\n", num5); printf("num6 = %i\n", num6); // signed也可以省略数据类型, 但是不推荐这样编写 signed num7 = 9; printf("num7 = %i\n", num7); // 3.unsigned用于明确说明, 当前不能保存有符号的值, 只能保存0和正数 // 应用场景: 保存银行存款,学生分数等不能是负数的情况 unsigned int num8 = -9; unsigned int num9 = 0; unsigned int num10 = 9; // 注意: 不看怎么存只看怎么取 printf("num8 = %u\n", num8); printf("num9 = %u\n", num9); printf("num10 = %u\n", num10); return 0; } 注意点: 修饰符号的说明符可以和修饰长度的说明符混合使用 相同类型的说明符不能混合使用 signed short int num1 = 666; signed unsigned int num2 = 666; // 报错
函数基本概念 C源程序是由函数组成的 例如: 我们前面学习的课程当中,通过main函数+scanf函数+printf函数+逻辑代码就可以组成一个C语言程序 C语言不仅提供了极为丰富的库函数, 还允许用户建立自己定义的函数。用户可把自己的算法编写成一个个相对独立的函数,然后再需要的时候调用它 例如:你用C语言编写了一个MP3播放器程序,那么它的程序结构如下图所示 可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言 函数的分类 在C语言中可从不同的角度对函数分类 从函数定义的角度看,函数可分为库函数和用户定义函数两种 库函数: 由C语言系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf、scanf、getchar、putchar等函数均属此类 用户定义函数:由用户按需编写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用 从函数执行结果的角度来看, 函数可分为有返回值函数和无返回值函数两种 有返回值函数: 此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。(必须指定返回值类型和使用return关键字返回对应数据) 无返回值函数: 此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。(返回值类型为void, 不用使用return关键字返回对应数据) 从主调函数和被调函数之间数据传送的角度看,又可分为无参函数和有参函数两种 无参函数: 在函数定义及函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。 有参函数: 在函数定义及函数说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参) 函数的定义 定义函数的目的 将一个常用的功能封装起来,方便以后调用 自定义函数的书写格式 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) { 函数体; 返回值; } 示例 int main(){ printf("hello world\n"); retrun 0; } 定义函数的步骤 函数名:函数叫什么名字 函数体:函数是干啥的,里面包含了什么代码 返回值类型: 函数执行完毕返回什么和调用者 无参无返回值函数定义 没有返回值时return可以省略 格式: void 函数名() { 函数体; } 示例: // 1.没有返回值/没有形参 // 如果一个函数不需要返回任何数据给调用者, 那么返回值类型就是void void printRose() { printf(" {@}\n"); printf(" |\n"); printf(" \\|/\n"); // 注意: \是一个特殊的符号(转意字符), 想输出\必须写两个斜线 printf(" |\n"); // 如果函数不需要返回数据给调用者, 那么函数中的return可以不写 } 无参有返回值函数定义 格式: 返回值类型 函数名() { 函数体; return 值; } 示例: int getMax() { printf("请输入两个整数, 以逗号隔开, 以回车结束\n"); int number1, number2; scanf("%i,%i", &number1, &number2); int max = number1 > number2 ? number1 : number2; return max; } 有参无返回值函数定义 形式参数表列表的格式: 类型 变量名,类型 变量2,...... 格式: void 函数名(参数类型 形式参数1,参数类型 形式参数2,…) { 函数体; } 示例: void printMax(int value1, int value2) { int max = value1 > value2 ? value1 : value2; printf("max = %i\n", max); } 有参有返回值函数定义 格式: 返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) { 函数体; return 0; } 示例: int printMax(int value1, int value2) { int max = value1 > value2 ? value1 : value2; return max; } 函数定义注意 函数不能嵌套定义 void test() { void test2() { // 错误写法 } } 函数名称不能相同 void test() { } void test() { // 报错 } 函数的参数和返回值 形式参数 在定义函数时,函数名后面小括号()中定义的变量称为形式参数,简称形参 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。 因此,形参只有在函数内部有效,函数调用结束返回主调函数后则不能再使用该形参变量 int max(int number1, int number2) // 形式参数 { return number1 > number2 ? number1 : number2; } 实际参数 在调用函数时, 传入的值称为实际参数,简称实参 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参 因此应预先用赋值,输入等办法使实参获得确定值 int main() { int num = 99; // 88, num, 22+44均能得到一个确定的值, 所以都可以作为实参 max(88, num, 22+44); // 实际参数 return 0; } 形参、实参注意点 调用函数时传递的实参个数必须和函数的形参个数必须保持一致 int max(int number1, int number2) { // 形式参数 return number1 > number2 ? number1 : number2; } int main() { // 函数需要2个形参, 但是我们只传递了一个实参, 所以报错 max(88); // 实际参数 return 0; } 形参实参类型不一致, 会自动转换为形参类型 void change(double number1, double number2) {// 形式参数 // 输出结果: 10.000000, 20.000000 // 自动将实参转换为double类型后保存 printf("number1 = %f, number2 = %f", number1, number2); } int main() { change(10, 20); return 0; } 当使用基本数据类型(char、int、float等)作为实参时,实参和形参之间只是值传递,修改形参的值并不影响到实参函数可以没有形参 void change(int number1, int number2) { // 形式参数 number1 = 250; // 不会影响实参 number2 = 222; } int main() { int a = 88; int b = 99; change(a, b); printf("a = %d, b = %d", a, b); // 输出结果: 88, 99 return 0; } 返回值类型注意点 如果没有写返回值类型,默认是int max(int number1, int number2) {// 形式参数 return number1 > number2 ? number1 : number2; } 函数返回值的类型和return实际返回的值类型应保持一致。如果两者不一致,则以返回值类型为准,自动进行类型转换 int height() { return 3.14; } int main() { double temp = height(); printf("%lf", temp);// 输出结果: 3.000000 } 一个函数内部可以多次使用return语句,但是return语句后面的代码就不再被执行 int max(int number1, int number2) {// 形式参数 return number1 > number2 ? number1 : number2; printf("执行不到"); // 执行不到 return 250; // 执行不到 } 函数的声明 在C语言中,函数的定义顺序是有讲究的: 默认情况下,只有后面定义的函数才可以调用前面定义过的函数 如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明, 否则 系统搞不清楚有没有这个函数 系统搞不清楚这个函数接收几个参数 系统搞不清楚这个函数的返回值类型是什么 所以函数声明,就是在函数调用之前告诉系统, 该函数叫什么名称, 该函数接收几个参数, 该函数的返回值类型是什么 函数的声明格式: 将自定义函数时{}之前的内容拷贝到调用之间即可 例如: int max( int a, int b ); 或者: int max( int, int ); // 函数声明 void getMax(int v1, int v2); int main(int argc, const char * argv[]) { getMax(10, 20); // 调用函数 return 0; } // 函数实现 void getMax(int v1, int v2) { int max = v1 > v2 ? v1 : v2; printf("max = %i\n", max); } 函数的声明与实现的关系 声明仅仅代表着告诉系统一定有这个函数, 和这个函数的参数、返回值是什么 实现代表着告诉系统, 这个函数具体的业务逻辑是怎么运作的 函数声明注意点: 函数的实现不能重复, 而函数的声明可以重复 // 函数声明 void getMax(int v1, int v2); void getMax(int v1, int v2); void getMax(int v1, int v2); // 不会报错 int main(int argc, const char * argv[]) { getMax(10, 20); // 调用函数 return 0; } // 函数实现 void getMax(int v1, int v2) { int max = v1 > v2 ? v1 : v2; printf("max = %i\n", max); } 函数声明可以写在函数外面,也可以写在函数里面, 只要在调用之前被声明即可 int main(int argc, const char * argv[]) { void getMax(int v1, int v2); // 函数声明, 不会报错 getMax(10, 20); // 调用函数 return 0; } // 函数实现 void getMax(int v1, int v2) { int max = v1 > v2 ? v1 : v2; printf("max = %i\n", max); } 当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作声明 // 函数实现 void getMax(int v1, int v2) { int max = v1 > v2 ? v1 : v2; printf("max = %i\n", max); } int main(int argc, const char * argv[]) { getMax(10, 20); // 调用函数 return 0; } 如果被调函数的返回值是整型时,可以不对被调函数作说明,而直接调用 int main(int argc, const char * argv[]) { int res = getMin(5, 3); // 不会报错 printf("result = %d\n", res ); return 0; } int getMin(int num1, int num2) {// 返回int, 不用声明 return num1 < num2 ? num1 : num2; } main函数分析 main的含义: main是函数的名称, 和我们自定义的函数名称一样, 也是一个标识符 只不过main这个名称比较特殊, 程序已启动就会自动调用它 return 0;的含义: 告诉系统main函数是否正确的被执行了 如果main函数的执行正常, 那么就返回0 如果main函数执行不正常, 那么就返回一个非0的数 返回值类型: 一个函数return后面写的是什么类型, 函数的返回值类型就必须是什么类型, 所以写int 形参列表的含义 int argc : 系统在启动程序时调用main函数时传递给argv的值的个数 const char * argv[] : 系统在启动程序时传入的的值, 默认情况下系统只会传入一个值, 这个值就是main函数执行文件的路径 也可以通过命令行或项目设置传入其它参数 函数练习 写一个函数从键盘输入三个整型数字,找出其最大值 写一个函数求三个数的平均值 递归函数(了解) 什么是递归函数? 一个函数在它的函数体内调用它自身称为递归调用 void function(int x){ function(x); } 递归函数构成条件 自己搞自己 存在一个条件能够让递归结束 问题的规模能够缩小 示例: 获取用户输入的数字, 直到用户输入一个正数为止 void getNumber(){ int number = -1; while (number < 0) { printf("请输入一个正数\n"); scanf("%d", &number); } printf("number = %d\n", number); } void getNumber2(){ int number = -1; printf("请输入一个正数abc\n"); scanf("%d", &number); if (number < 0) { // 负数 getNumber2(); }else{ // 正数 printf("number = %d\n", number); } } 递归和循环区别 能用循环实现的功能,用递归都可以实现 递归常用于"回溯", "树的遍历","图的搜索"等问题 但代码理解难度大,内存消耗大(易导致栈溢出), 所以考虑到代码理解难度和内存消耗问题, 在企业开发中一般能用循环都不会使用递归 递归练习 有5个人坐在一起,问第5个人多少岁?他说比第4个人大两岁。问 第4个人岁数,他说比第3个人大两岁。问第3个人,又说比第2个 人大两岁。问第2个人,说比第1个人大两岁。最后问第1个人, 他说是10岁。请问第5个人多大? 用递归法求N的阶乘 设计一个函数用来计算B的n次方
流程控制基本概念 默认情况下程序运行后,系统会按书写顺序从上至下依次执行程序中的每一行代码。但是这并不能满足我们所有的开发需求, 为了方便我们控制程序的运行流程,C语言提供3种流程控制结构,不同的流程控制结构可以实现不同的运行流程。 这3种流程结构分别是顺序结构、选择结构、循环结构 顺序结构: 按书写顺序从上至下依次执行 选择结构 对给定的条件进行判断,再根据判断结果来决定执行代码 循环结构 在给定条件成立的情况下,反复执行某一段代码 选择结构 C语言中提供了两大选择结构, 分别是if和switch 选择结构if if第一种形式 表示如果表达式为真,执行语句块1,否则不执行 if(表达式) { 语句块1; } 后续语句; if(age >= 18) { printf("开网卡\n"); } printf("买烟\n"); if第二种形式 如果表达式为真,则执行语句块1,否则执行语句块2 else不能脱离if单独使用 if(表达式){ 语句块1; }else{ 语句块2; } 后续语句; if(age > 18){ printf("开网卡\n"); }else{ printf("喊家长来开\n"); } printf("买烟\n"); if第三种形式 如果"表达式1"为真,则执行"语句块1",否则判断"表达式2",如果为真执行"语句块2",否则再判断"表达式3",如果真执行"语句块3", 当表达式1、2、3都不满足,会执行最后一个else语句 众多大括号中,只有一个大括号中的内容会被执行 只有前面所有添加都不满足, 才会执行else大括号中的内容 if(表达式1) { 语句块1; }else if(表达式2){ 语句块2; }else if(表达式3){ 语句块3; }else{ 语句块4; } 后续语句; if(age>40){ printf("给房卡"); }else if(age>25){ printf("给名片"); }else if(age>18){ printf("给网卡"); }else{ printf("给好人卡"); } printf("买烟\n"); if嵌套 if中可以继续嵌套if, else中也可以继续嵌套if if(表达式1){ 语句块1; if(表达式2){ 语句块2; } }else{ if(表达式3){ 语句块3; }else{ 语句块4; } } if注意点 任何数值都有真假性 #include <stdio.h> int main(){ if(0){ printf("执行了if"); }else{ printf("执行了else"); // 被执行 } } 当if else后面只有一条语句时, if else后面的大括号可以省略 // 极其不推荐写法 int age = 17; if (age >= 18) printf("开网卡\n"); else printf("喊家长来开\n"); 当if else后面的大括号被省略时, else会自动和距离最近的一个if匹配 #include <stdio.h> int main(){ if(0) if(1) printf("A\n"); else // 和if(1)匹配 printf("B\n"); else // 和if(0)匹配, 因为if(1)已经被匹配过了 if (1) printf("C\n"); // 输出C else // 和if(1)匹配 printf("D\n"); } 如果if else省略了大括号, 那么后面不能定义变量 #include <stdio.h> int main(){ if(1) int number = 10; // 系统会报错 printf("number = %i\n", number); } #include <stdio.h> int main(){ if(0){ int number = 10; }else int value = 20; // 系统会报错 printf("value = %i\n", value); } C语言中分号(;)也是一条语句, 称之为空语句 // 因为if(10 > 2)后面有一个分号, 所以系统会认为if省略了大括号 // if省略大括号时只能管控紧随其后的那条语句, 所以只能管控分号 if(10 > 2); { printf("10 > 2"); } // 输出结果: 10 > 2 但凡遇到比较一个变量等于或者不等于某一个常量的时候,把常量写在前面 #include <stdio.h> int main(){ int a = 8; // if(a = 10){// 错误写法, 但不会报错 if (10 == a){ printf("a的值是10\n"); }else{ printf("a的值不是10\n"); } } if练习 从键盘输入一个整数,判断其是否是偶数,如果是偶数就输出YES,否则输出NO; 接收用户输入的1~7的整数,根据用户输入的整数,输出对应的星期几 接收用户输入的一个整数month代表月份,根据月份输出对应的季节 接收用户输入的两个整数,判断大小后输出较大的那个数 接收用户输入的三个整数,判断大小后输出较大的那个数 接收用户输入的三个整数,排序后输出 实现石头剪刀布 剪刀石头布游戏: 1)定义游戏规则 剪刀 干掉 布 石头 干掉 剪刀 布 干掉石头 2)显示玩家开始猜拳 3)接收玩家输入的内容 4)让电脑随机产生一种拳 5)判断比较 (1)玩家赢的情况(显示玩家赢了) (2)电脑赢的情况(显示电脑赢了) (3)平局(显示平局) 选择结构switch 由于 if else if 还是不够简洁,所以switch 就应运而生了,他跟 if else if 互为补充关系。switch 提供了点的多路选择 格式: switch(表达式){ case 常量表达式1: 语句1; break; case 常量表达式2: 语句2; break; case 常量表达式n: 语句n; break; default: 语句n+1; break; } 语义: 计算"表达式"的值, 逐个与其后的"常量表达式"值相比较,当"表达式"的值与某个"常量表达式"的值相等时, 即执行其后的语句, 然后跳出switch语句 如果"表达式"的值与所有case后的"常量表达式"均不相同时,则执行default后的语句 示例: #include <stdio.h> int main() { int num = 3; switch(num){ case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); break; case 3: printf("星期三\n"); break; case 4: printf("星期四\n"); break; case 5: printf("星期五\n"); break; case 6: printf("星期六\n"); break; case 7: printf("星期日\n"); break; default: printf("回火星去\n"); break; } } switch注意点 switch条件表达式的类型必须是整型, 或者可以被提升为整型的值(char、short) #include <stdio.h> int main() { switch(1.1){ // 报错 case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); break; default: printf("回火星去\n"); break; } } case的值只能是常量, 并且还必须是整型, 或者可以被提升为整型的值(char、short) #include <stdio.h> int main() { int num = 3; switch(1){ case 1: printf("星期一\n"); break; case 'a': printf("星期二\n"); break; case num: // 报错 printf("星期三\n"); break; case 4.0: // 报错 printf("星期四\n"); break; default: printf("回火星去\n"); break; } } case后面常量表达式的值不能相同 #include <stdio.h> int main() { switch(1){ case 1: // 报错 printf("星期一\n"); break; case 1: // 报错 printf("星期一\n"); break; default: printf("回火星去\n"); break; } } case后面要想定义变量,必须给case加上大括号 #include <stdio.h> int main() { switch(1){ case 1:{ int num = 10; printf("num = %i\n", num); printf("星期一\n"); break; } case 2: printf("星期一\n"); break; default: printf("回火星去\n"); break; } } switch中只要任意一个case匹配, 其它所有的case和default都会失效. 所以如果case和default后面没有break就会出现穿透问题 #include <stdio.h> int main() { int num = 2; switch(num){ case 1: printf("星期一\n"); break; case 2: printf("星期二\n"); // 被输出 case 3: printf("星期三\n"); // 被输出 default: printf("回火星去\n"); // 被输出 break; } } switch中default可以省略 #include <stdio.h> int main() { switch(1){ case 1: printf("星期一\n"); break; case 2: printf("星期一\n"); break; } } switch中default的位置不一定要写到最后, 无论放到哪都会等到所有case都不匹配才会执行(穿透问题除外) #include <stdio.h> int main() { switch(3){ case 1: printf("星期一\n"); break; default: printf("Other,,,\n"); break; case 2: printf("星期一\n"); break; } } if和Switch转换 看上去if和switch都可以实现同样的功能, 那么在企业开发中我们什么时候使用if, 什么时候使用switch呢? if else if 针对于范围的多路选择 switch 是针对点的多路选择 判断用户输入的数据是否大于100 #include <stdio.h> int main() { int a = -1; scanf("%d", &a); if(a > 100){ printf("用户输入的数据大于100"); }else{ printf("用户输入的数据不大于100"); } } #include <stdio.h> int main() { int a = -1; scanf("%d", &a); // 挺(T)萌(M)的(D)搞不定啊 switch (a) { case 101: case 102: case 103: case 104: case 105: printf("大于\n"); break; default: printf("不大于\n"); break; } } 练习 实现分数等级判定 要求用户输入一个分数,根据输入的分数输出对应的等级 A 90~100 99/10 = 9 90/10= 9 98/10 = 9 100/10 = 10 B 80~89 89/10 = 8 C 70~79 D 60~69 E 0~59 实现+ - * / 简单计算器 循环结构 C语言中提供了三大循环结构, 分别是while、dowhile和for 循环结构是程序中一种很重要的结构。 其特点是,在给定条件成立时,反复执行某程序段, 直到条件不成立为止。 给定的条件称为"循环条件",反复执行的程序段称为"循环体" 循环结构while 格式: while ( 循环控制条件 ) { 循环体中的语句; 能够让循环结束的语句; .... } 构成循环结构的几个条件 循环控制条件 循环退出的主要依据,来控制循环到底什么时候退出 循环体 循环的过程中重复执行的代码段 能够让循环结束的语句(递增、递减、真、假等) 能够让循环条件为假的依据,否则退出循环 示例: int count = 0; while (count < 3) { // 循环控制条件 printf("发射子弹~哔哔哔哔\n"); // 需要反复执行的语句 count++; // 能够让循环结束的语句 } while循环执行流程 首先会判定"循环控制条件"是否为真, 如果为假直接跳到循环语句后面 如果"循环控制条件"为真, 执行一次循环体, 然后再次判断"循环控制条件"是否为真, 为真继续执行循环体,为假跳出循环 重复以上操作, 直到"循环控制条件"为假为止 #include <stdio.h> int main(){ int count = 4; // 1.判断循环控制条件是否为真,此时为假所以跳过循环语句 while (count < 3) { printf("发射子弹~哔哔哔哔\n"); count++; } // 2.执行循环语句后面的代码, 打印"循环执行完毕" printf("循环执行完毕\n"); } #include <stdio.h> int main(){ int count = 0; // 1.判断循环控制条件是否为真,此时0 < 3为真 // 4.再次判断循环控制条件是否为真,此时1 < 3为真 // 7.再次判断循环控制条件是否为真,此时2 < 3为真 // 10.再次判断循环控制条件是否为真,此时3 < 3为假, 跳过循环语句 while (count < 3) { // 2.执行循环体中的代码, 打印"发子弹" // 5.执行循环体中的代码, 打印"发子弹" // 8.执行循环体中的代码, 打印"发子弹" printf("发射子弹~哔哔哔哔\n"); // 3.执行"能够让循环结束的语句" count = 1 // 6.执行"能够让循环结束的语句" count = 2 // 9.执行"能够让循环结束的语句" count = 3 count++; } // 11.执行循环语句后面的代码, 打印"循环执行完毕" printf("循环执行完毕\n"); } while循环注意点 任何数值都有真假性 #include <stdio.h> int main(){ while (1) { // 死循环 printf("发射子弹~哔哔哔哔\n"); // 没有能够让循环结束的语句 } } 当while后面只有一条语句时,while后面的大括号可以省略 #include <stdio.h> int main(){ while (1) // 死循环 printf("发射子弹~哔哔哔哔\n"); // 没有能够让循环结束的语句 } 如果while省略了大括号, 那么后面不能定义变量 #include <stdio.h> int main(){ while (1) // 死循环 int num = 10; // 报错 // 没有能够让循环结束的语句 } C语言中分号(;)也是一条语句, 称之为空语句 #include <stdio.h> int main(){ int count = 0; while (count < 3);{ // 死循环 printf("发射子弹~哔哔哔哔\n"); count++; } } 最简单的死循环 // 死循环一般在操作系统级别的应用程序会比较多, 日常开发中很少用 while (1); while练习 计算1 + 2 + 3 + ...n的和 获取1~100之间 7的倍数的个数 循环结构do while 格式: do { 循环体中的语句; 能够让循环结束的语句; .... } while (循环控制条件 ); 示例 int count = 0; do { printf("发射子弹~哔哔哔哔\n"); count++; }while(count < 10); do-while循环执行流程 首先不管while中的条件是否成立, 都会执行一次"循环体" 执行完一次循环体,接着再次判断while中的条件是否为真, 为真继续执行循环体,为假跳出循环 重复以上操作, 直到"循环控制条件"为假为止 应用场景 口令校验 #include<stdio.h> int main() { int num = -1; do{ printf("请输入密码,验证您的身份\n"); scanf("%d", &num); }while(123456 != num); printf("主人,您终于回来了\n"); } while和dowhile应用场景 绝大多数情况下while和dowhile可以互换, 所以能用while就用while 无论如何都需要先执行一次循环体的情况, 才使用dowhile do while 曾一度提议废除,但是他在输入性检查方面还是有点用的 循环结构for 格式: for(初始化表达式;循环条件表达式;循环后的操作表达式) { 循环体中的语句; } 示例 for(int i = 0; i < 10; i++){ printf("发射子弹~哔哔哔哔\n"); } for循环执行流程 +首先执行"初始化表达式",而且在整个循环过程中,只会执行一次初始化表达式 接着判断"循环条件表达式"是否为真,为真执行循环体中的语句 循环体执行完毕后,接下来会执行"循环后的操作表达式",然后再次判断条件是否为真,为真继续执行循环体,为假跳出循环 重复上述过程,直到条件不成立就结束for循环 for循环注意点: 和while一模一样 最简单的死循环for(;;); for和while应用场景 while能做的for都能做, 所以企业开发中能用for就用for, 因为for更为灵活 而且对比while来说for更节约内存空间 int count = 0; // 初始化表达式 while (count < 10) { // 条件表达式 printf("发射子弹~哔哔哔哔 %i\n", count); count++; // 循环后增量表达式 } // 如果初始化表达式的值, 需要在循环之后使用, 那么就用while printf("count = %i\n", count); // 注意: 在for循环初始化表达式中定义的变量, 只能在for循环后面的{}中访问 // 所以: 如果初始化表达式的值, 不需要在循环之后使用, 那么就用for // 因为如果初始化表达式的值, 在循环之后就不需要使用了 , 那么用while会导致性能问题 for (int count = 0; count < 10; count++) { printf("发射子弹~哔哔哔哔 %i\n", count); } // printf("count = %i\n", count); // 如果需要使用初始化表达式的值, 也可以将初始化表达式写到外面 int count = 0; for (; count < 10; count++) { printf("发射子弹~哔哔哔哔\n", count); } printf("count = %i\n", count); 四大跳转 C语言中提供了四大跳转语句, 分别是return、break、continue、goto break: 立即跳出switch语句或循环 应用场景: switch 循环结构 练习 打印 10 的所有加法组和 break注意点: break离开应用范围,存在是没有意义的 if(1) { break; // 会报错 } 在多层循环中,一个break语句只向外跳一层 while(1) { while(2) { break;// 只对while2有效, 不会影响while1 } printf("while1循环体\n"); } break下面不可以有语句,因为执行不到 while(2){ break; printf("打我啊!");// 执行不到 } continue 结束本轮循环,进入下一轮循环 应用场景: 循环结构 continue注意点: continue离开应用范围,存在是没有意义的 if(1) { continue; // 会报错 } goto 这是一个不太值得探讨的话题,goto 会破坏结构化程序设计流程,它将使程序层次不清,且不易读,所以慎用 goto 语句,仅能在本函数内实现跳转,不能实现跨函数跳转(短跳转)。但是他在跳出多重循环的时候效率还是蛮高的 #include <stdio.h> int main(){ int num = 0; // loop:是定义的标记 loop:if(num < 10){ printf("num = %d\n", num); num++; // goto loop代表跳转到标记的位置 goto loop; } } #include <stdio.h> int main(){ while (1) { while(2){ goto lnj; } } lnj:printf("跳过了所有循环"); } return 结束当前函数,将结果返回给调用者 不着急, 放一放,学到函数我们再回头来看它 循环的嵌套 循环结构的循环体中存在其他的循环结构,我们称之为循环嵌套 注意: 一般循环嵌套不超过三层 外循环执行的次数 * 内循环执行的次数就是内循环总共执行的次数 格式: while(条件表达式) { while循环结构 or dowhile循环结构 or for循环结构 } for(初始化表达式;循环条件表达式;循环后的操作表达式) { while循环结构 or dowhile循环结构 or for循环结构 } do { while循环结构 or dowhile循环结构 or for循环结构 } while (循环控制条件 ); 循环优化 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数 for (row=0; row<100; row++) { // 低效率:长循环在最外层 for ( col=0; col<5; col++ ) { sum = sum + a[row][col]; } } for (col=0; col<5; col++ ) { // 高效率:长循环在最内层 for (row=0; row<100; row++) { sum = sum + a[row][col]; } } 练习 打印好友列表 好友列表1 好友1 好友2 好友列表2 好友1 好友2 好友列表3 好友1 好友2 for (int i = 0; i < 4; i++) { printf("好友列表%d\n", i+1); for (int j = 0; j < 4; j++) { printf(" 角色%d\n", j); } } 图形打印 一重循环解决线性的问题,而二重循环和三重循环就可以解决平面和立体的问题了 打印矩形 **** **** **** // 3行4列 // 外循环控制行数 for (int i = 0; i < 3; i++) { // 内循环控制列数 for (int j = 0; j < 4; j++) { printf("*"); } printf("\n"); } 打印三角形 尖尖朝上,改变内循环的条件表达式,让内循环的条件表达式随着外循环的i值变化 尖尖朝下,改变内循环的初始化表达式,让内循环的初始化表达式随着外循环的i值变化 * ** *** **** ***** /* 最多打印5行 最多打印5列 每一行和每一列关系是什么? 列数<=行数 */ for(int i = 0; i< 5; i++) { for(int j = 0; j <= i; j++) { printf("*"); } printf("\n"); } ***** **** *** ** * for(int i = 0; i< 5; i++) { for(int j = i; j < 5; j++) { printf("*"); } printf("\n"); } 练习 打印特殊三角形 1 12 123 for (int i = 0; i < 3; i++) { for (int j = 0; j <= i; j++) { printf("%d", j+1); } printf("\n"); } 打印特殊三角形 1 22 333 for (int i = 1; i <= 3; i++) { for (int j = 1; j <= i; j++) { printf("%d", i); } printf("\n"); } 打印特殊三角形 --* -*** ***** for (int i = 0; i <= 5; i++) { for (int j = 0; j < 5 - i; j++) { printf("-"); } for (int m = 0; m < 2*i+1; m++) { printf("*"); } printf("\n"); } 打印99乘法表 1 * 1 = 1 1 * 2 = 2 2 * 2 = 4 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9 for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { printf("%d * %d = %d \t", j, i, (j * i)); } printf("\n"); }
运算符基本概念 和数学中的运算符一样, C语言中的运算符是告诉程序执行特定算术或逻辑操作的符号 例如告诉程序, 某两个数相加, 相减,相乘等 什么是表达式 表达式就是利用运算符链接在一起的有意义,有结果的语句; 例如: a + b; 就是一个算数表达式, 它的意义是将两个数相加, 两个数相加的结果就是表达式的结果 注意: 表达式一定要有结果 运算符分类 按照功能划分: 算术运算符 关系运算符 逻辑运算符 位运算符 按照参与运算的操作数个数划分: 单目运算 只有一个操作数 如 : i++; 双目运算 有两个操作数 如 : a + b; 三目运算 C语言中唯一的一个,也称为问号表达式 如: a>b ? 1 : 0; 运算符的优先级和结合性 早在小学的数学课本中,我们就学习过"从左往右,先乘除后加减,有括号的先算括号里面的", 这句话就蕴含了优先级和结合性的问题 C语言中,运算符的运算优先级共分为15 级。1 级最高,15 级最低 在C语言表达式中,不同优先级的运算符, 运算次序按照由高到低执行 在C语言表达式中,相同优先级的运算符, 运算次序按照结合性规定的方向执行 算数运算符 优先级 名称 符号 说明 3 乘法运算符 * 双目运算符,具有左结合性 3 除法运算符 / 双目运算符,具有左结合性 3 求余运算符 (模运算符) % 双目运算符,具有左结合性 4 加法运算符 + 双目运算符,具有左结合性 4 减法运算符 - 双目运算符,具有左结合性 注意事项 如果参与运算的两个操作数皆为整数, 那么结果也为整数 如果参与运算的两个操作数其中一个是浮点数, 那么结果一定是浮点数 求余运算符, 本质上就是数学的商和余"中的余数 求余运算符, 参与运算的两个操作数必须都是整数, 不能包含浮点数 求余运算符, 被除数小于除数, 那么结果就是被除数 求余运算符, 运算结果的正负性取决于被除数,跟除数无关, 被除数是正数结果就是正数,被除数是负数结果就是负数 求余运算符, 被除数为0, 结果为0 求余运算符, 除数为0, 没有意义(不要这样写) #include <stdio.h> int main(){ int a = 10; int b = 5; // 加法 int result = a + b; printf("%i\n", result); // 15 // 减法 result = a - b; printf("%i\n", result); // 5 // 乘法 result = a * b; printf("%i\n", result); // 50 // 除法 result = a / b; printf("%i\n", result); // 2 // 算术运算符的结合性和优先级 // 结合性: 左结合性, 从左至右 int c = 50; result = a + b + c; // 15 + c; 65; printf("%i\n", result); // 优先级: * / % 大于 + - result = a + b * c; // a + 250; 260; printf("%i\n", result); } #include <stdio.h> int main(){ // 整数除以整数, 结果还是整数 printf("%i\n", 10 / 3); // 3 // 参与运算的任何一个数是小数, 结果就是小数 printf("%f\n", 10 / 3.0); // 3.333333 } #include <stdio.h> int main(){ // 10 / 3 商等于3, 余1 int result = 10 % 3; printf("%i\n", result); // 1 // 左边小于右边, 那么结果就是左边 result = 2 % 10; printf("%i\n", result); // 2 // 被除数是正数结果就是正数,被除数是负数结果就是负数 result = 10 % 3; printf("%i\n", result); // 1 result = -10 % 3; printf("%i\n", result); // -1 result = 10 % -3; printf("%i\n", result); // 1 } 赋值运算符 优先级 名称 符号 说明 14 赋值运算符 = 双目运算符,具有右结合性 14 除后赋值运算符 /= 双目运算符,具有右结合性 14 乘后赋值运算符 (模运算符) *= 双目运算符,具有右结合性 14 取模后赋值运算符 %= 双目运算符,具有右结合性 14 加后赋值运算符 += 双目运算符,具有右结合性 14 减后赋值运算符 -= 双目运算符,具有右结合性 简单赋值运算符 #include <stdio.h> int main(){ // 简单的赋值运算符 = // 会将=右边的值赋值给左边 int a = 10; printf("a = %i\n", a); // 10 } 复合赋值运算符 #include <stdio.h> int main(){ // 复合赋值运算符 += -= *= /= %= // 将变量中的值取出之后进行对应的操作, 操作完毕之后再重新赋值给变量 int num1 = 10; // num1 = num1 + 1; num1 = 10 + 1; num1 = 11; num1 += 1; printf("num1 = %i\n", num1); // 11 int num2 = 10; // num2 = num2 - 1; num2 = 10 - 1; num2 = 9; num2 -= 1; printf("num2 = %i\n", num2); // 9 int num3 = 10; // num3 = num3 * 2; num3 = 10 * 2; num3 = 20; num3 *= 2; printf("num3 = %i\n", num3); // 20 int num4 = 10; // num4 = num4 / 2; num4 = 10 / 2; num4 = 5; num4 /= 2; printf("num4 = %i\n", num4); // 5 int num5 = 10; // num5 = num5 % 3; num5 = 10 % 3; num5 = 1; num5 %= 3; printf("num5 = %i\n", num5); // 1 } 结合性和优先级 #include <stdio.h> int main(){ int number = 10; // 赋值运算符优先级是14, 普通运算符优先级是3和4, 所以先计算普通运算符 // 普通运算符中乘法优先级是3, 加法是4, 所以先计算乘法 // number += 1 + 25; number += 26; number = number + 26; number = 36; number += 1 + 5 * 5; printf("number = %i\n", number); // 36 } 自增自减运算符 在程序设计中,经常遇到“i=i+1”和“i=i-1”这两种极为常用的操作。 C语言为这种操作提供了两个更为简洁的运算符,即++和-- 优先级 名称 符号 说明 2 自增运算符(在后) i++ 单目运算符,具有左结合性 2 自增运算符(在前) ++i 单目运算符,具有右结合性 2 自减运算符(在后) i-- 单目运算符,具有左结合性 2 自减运算符(在前) --i 单目运算符,具有右结合性 自增 如果只有单个变量, 无论++写在前面还是后面都会对变量做+1操作 #include <stdio.h> int main(){ int number = 10; number++; printf("number = %i\n", number); // 11 ++number; printf("number = %i\n", number); // 12 } 如果出现在一个表达式中, 那么++写在前面和后面就会有所区别 前缀表达式:++x, --x;其中x表示变量名,先完成变量的自增自减1运算,再用x的值作为表达式的值;即“先变后用”,也就是变量的值先变,再用变量的值参与运算 后缀表达式:x++, x--;先用x的当前值作为表达式的值,再进行自增自减1运算。即“先用后变”,也就是先用变量的值参与运算,变量的值再进行自增自减变化 #include <stdio.h> int main(){ int number = 10; // ++在后, 先参与表达式运算, 再自增 // 表达式运算时为: 3 + 10; int result = 3 + number++; printf("result = %i\n", result); // 13 printf("number = %i\n", number); // 11 } #include <stdio.h> int main(){ int number = 10; // ++在前, 先自增, 再参与表达式运算 // 表达式运算时为: 3 + 11; int result = 3 + ++number; printf("result = %i\n", result); // 14 printf("number = %i\n", number); // 11 } 自减 #include <stdio.h> int main(){ int number = 10; // --在后, 先参与表达式运算, 再自减 // 表达式运算时为: 10 + 3; int result = number-- + 3; printf("result = %i\n", result); // 13 printf("number = %i\n", number); // 9 } #include <stdio.h> int main(){ int number = 10; // --在前, 先自减, 再参与表达式运算 // 表达式运算时为: 9 + 3; int result = --number + 3; printf("result = %i\n", result); // 12 printf("number = %i\n", number); // 9 } 注意点: 自增、自减运算只能用于单个变量,只要是标准类型的变量,不管是整型、实型,还是字符型变量等,但不能用于表达式或常量 错误用法: ++(a+b); 5++; 企业开发中尽量让++ -- 单独出现, 尽量不要和其它运算符混合在一起 int i = 10; int b = i++; // 不推荐 或者 int b = ++i; // 不推荐 或者 int a = 10; int b = ++a + a++; // 不推荐 请用如下代码替代 int i = 10; int b = i; // 推荐 i++; 或者; i++; int b = i; // 推荐 或者 int a = 10; ++a; int b = a + a; // 推荐 a++; C语言标准没有明确的规定,同一个表达式中同一个变量自增或自减后如何运算, 不同编译器得到结果也不同, 在企业开发中千万不要这样写 int a = 1; // 下列代码利用Qt运行时6, 利用Xcode运行是5 // 但是无论如何, 最终a的值都是3 // 在C语言中这种代码没有意义, 不用深究也不要这样写 // 特点: 参与运算的是同一个变量, 参与运算时都做了自增自减操作, 并且在同一个表达式中 int b = ++a + ++a; printf("b = %i\n", b); sizeof运算符 sizeof可以用来计算一个变量或常量所占的内存字节数 标准格式: sizeof(常量 or 变量); sizeof的几种形式 sizeof( 变量\常量 ); sizeof(10); char c = 'a'; sizeof(c); sizeof 变量\常量; sizeof 10; char c = 'a'; sizeof c; sizeof( 数据类型); sizeof(float); 如果是数据类型不能省略括号 sizeof面试题: sizeof()和+=、*=一样是一个复合运算符, 由sizeof和()两个部分组成, 但是代表的是一个整体 所以sizeof不是一个函数, 是一个运算符, 该运算符的优先级是2 #include <stdio.h> int main(){ int a = 10; double b = 3.14; // 由于sizeof的优先级比+号高, 所以会先计算sizeof(a); // a是int类型, 所以占4个字节得到结果4 // 然后再利用计算结果和b相加, 4 + 3.14 = 7.14 double res = sizeof a+b; printf("res = %lf\n", res); // 7.14 } 逗号运算符 在C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把多个表达式连接起来组成一个表达式,称为逗号表达式 逗号运算符会从左至右依次取出每个表达式的值, 最后整个逗号表达式的值等于最后一个表达式的值 格式: 表达式1,表达式2,… …,表达式n; 例如: int result = a+1,b=3*4; #include <stdio.h> int main(){ int a = 10, b = 20, c; // ()优先级高于逗号运算符和赋值运算符, 所以先计算()中的内容 // c = (11, 21); // ()中是一个逗号表达式, 结果是最后一个表达式的值, 所以计算结果为21 // 将逗号表达式的结果赋值给c, 所以c的结果是21 c = (a + 1, b + 1); printf("c = %i\n", c); // 21 } 关系运算符 为什么要学习关系运算符 默认情况下,我们在程序中写的每一句正确代码都会被执行。但很多时候,我们想在某个条件成立的情况下才执行某一段代码 这种情况的话可以使用条件语句来完成,但是学习条件语句之前,我们先来看一些更基础的知识:如何判断一个条件是否成立 C语言中的真假性 在C语言中,条件成立称为“真”,条件不成立称为“假”,因此,判断条件是否成立,就是判断条件的“真假” 怎么判断真假呢?C语言规定,任何数值都有真假性,任何非0值都为“真”,只有0才为“假”。也就是说,108、-18、4.5、-10.5等都是“真”,0则是“假” 关系运算符的运算结果只有2种:如果条件成立,结果就为1,也就是“真”;如果条件不成立,结果就为0,也就是“假” 优先级 名称 符号 说明 6 大于运算符 > 单目运算符,具有左结合性 6 小于运算符 < 单目运算符,具有左结合性 6 大于等于运算符 >= 单目运算符,具有左结合性 6 小于等于运算符 <= 单目运算符,具有左结合性 7 等于运算符 == 单目运算符,具有左结合性 7 不等于运算符 != 单目运算符,具有左结合性 #include <stdio.h> int main(){ int result = 10 > 5; printf("result = %i\n", result); // 1 result = 5 < 10; printf("result = %i\n", result); // 1 result = 5 > 10; printf("result = %i\n", result); // 0 result = 10 >= 10; printf("result = %i\n", result); // 1 result = 10 <= 10; printf("result = %i\n", result); // 1 result = 10 == 10; printf("result = %i\n", result); // 1 result = 10 != 9; printf("result = %i\n", result); // 1 } 优先级和结合性 #include <stdio.h> int main(){ // == 优先级 小于 >, 所以先计算> // result = 10 == 1; result = 0; int result = 10 == 5 > 3; printf("result = %i\n", result); // 0 } #include <stdio.h> int main(){ // == 和 != 优先级一样, 所以按照结合性 // 关系运算符是左结合性, 所以从左至右计算 // result = 0 != 3; result = 1; int result = 10 == 5 != 3; printf("result = %i\n", result); // 1 } 练习: 计算result的结果 int result1 = 3 > 4 + 7 int result2 = (3>4) + 7 int result3 = 5 != 4 + 2 * 7 > 3 == 10 注意点: 无论是float还是double都有精度问题, 所以一定要避免利用==判断浮点数是否相等 #include <stdio.h> int main(){ float a = 0.1; float b = a * 10 + 0.00000000001; double c = 1.0 + + 0.00000000001; printf("b = %f\n", b); printf("c = %f\n", c); int result = b == c; printf("result = %i\n", result); // 0 } 逻辑运算符 优先级 名称 符号 说明 2 逻辑非运算符 ! 单目运算符,具有右结合性 11 逻辑与运算符 && 双目运算符,具有左结合性 12 逻辑或运算符 || 双目运算符,具有左结合性 逻辑非 格式: ! 条件A; 运算结果: 真变假,假变真 运算过程: 先判断条件A是否成立,如果添加A成立, 那么结果就为0,即“假”; 如果条件A不成立,结果就为1,即“真” 使用注意: 可以多次连续使用逻辑非运算符 !!!0;相当于(!(!(!0)));最终结果为1 #include <stdio.h> int main(){ // ()优先级高, 先计算()里面的内容 // 10==10为真, 所以result = !(1); // !代表真变假, 假变真,所以结果是假0 int result = !(10 == 10); printf("result = %i\n", result); // 0 } 逻辑与 格式: 条件A && 条件B; 运算结果:一假则假 运算过程: 总是先判断"条件A"是否成立 如果"条件A"成立,接着再判断"条件B"是否成立, 如果"条件B"也成立,结果就为1,即“真” 如果"条件A"成立,"条件B"不成立,结果就为0,即“假” 如果"条件A"不成立,不会再去判断"条件B"是否成立, 因为逻辑与只要一个不为真结果都不为真 使用注意: "条件A"为假, "条件B"不会被执行 #include <stdio.h> int main(){ // 真 && 真 int result = (10 == 10) && (5 != 1); printf("result = %i\n", result); // 1 // 假 && 真 result = (10 == 9) && (5 != 1); printf("result = %i\n", result); // 0 // 真 && 假 result = (10 == 10) && (5 != 5); printf("result = %i\n", result); // 0 // 假 && 假 result = (10 == 9) && (5 != 5); printf("result = %i\n", result); // 0 } #include <stdio.h> int main(){ int a = 10; int b = 20; // 逻辑与, 前面为假, 不会继续执行后面 int result = (a == 9) && (++b); printf("result = %i\n", result); // 1 printf("b = %i\n", b); // 20 } 逻辑或 格式: 条件A || 条件B; 运算结果:一真则真 运算过程: 总是先判断"条件A"是否成立 如果"条件A"不成立,接着再判断"条件B"是否成立, 如果"条件B"成立,结果就为1,即“真” 如果"条件A"不成立,"条件B"也不成立成立, 结果就为0,即“假” 如果"条件A"成立, 不会再去判断"条件B"是否成立, 因为逻辑或只要一个为真结果都为真 使用注意: "条件A"为真, "条件B"不会被执行 #include <stdio.h> int main(){ // 真 || 真 int result = (10 == 10) || (5 != 1); printf("result = %i\n", result); // 1 // 假 || 真 result = (10 == 9) || (5 != 1); printf("result = %i\n", result); // 1 // 真 || 假 result = (10 == 10) || (5 != 5); printf("result = %i\n", result); // 1 // 假 || 假 result = (10 == 9) || (5 != 5); printf("result = %i\n", result); // 0 } #include <stdio.h> int main(){ int a = 10; int b = 20; // 逻辑或, 前面为真, 不会继续执行后面 int result = (a == 10) || (++b); printf("result = %i\n", result); // 1 printf("b = %i\n", b); // 20 } 练习: 计算result的结果 int result = 3>5 || 2<4 && 6<1; 三目运算符 条件运算符是C语言的唯一的三目运算符,它需要3个数据或表达式构成条件表达式 格式: 表达式1?表达式2(结果A):表达式3(结果B) 示例: 考试及格 ? 及格 : 不及格; 求值规则: 如果"表达式1"为真,三目运算符的运算结果为"表达式2"的值(结果A),否则为"表达式3"的值(结果B) 示例: int a = 10; int b = 20; int max = (a > b) ? a : b; printf("max = %d", max); 输出结果: 20 等价于: int a = 10; int b = 20; int max = 0; if(a>b){ max=a; }else { max=b; } printf("max = %d", max); 注意点 条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符 条件运算符?和:是一个整体,不能分开使用 条件运算符的结合方向是从右至左 #include <stdio.h> int main(){ int a = 10; int b = 5; // 先计算 a > b // 然后再根据计算结果判定返回a还是b // 相当于int max= (a>b) ? a : b; int max= a>b ? a : b; printf("max = %i\n", max); // 10 } #include <stdio.h> int main(){ int a = 10; int b = 5; int c = 20; int d = 10; // 结合性是从右至左, 所以会先计算:后面的内容 // int res = a>b?a:(c>d?c:d); // int res = a>b?a:(20>10?20:10); // int res = a>b?a:(20); // 然后再计算最终的结果 // int res = 10>5?10:(20); // int res = 10; int res = a>b?a:c>d?c:d; printf("res = %i\n", res); } 类型转换 强制类型转换(显示转换) 自动类型转换(隐式转换) (需要转换的类型)(表达式) 1.算数转换 2.赋值转换 强制类型转换(显示转换) // 将double转换为int int a = (int)10.5; 算数转换 系统会自动对占用内存较少的类型做一个“自动类型提升”的操作, 先将其转换为当前算数表达式中占用内存高的类型, 然后再参与运算 // 当前表达式用1.0占用8个字节, 2占用4个字节 // 所以会先将整数类型2转换为double类型之后再计算 double b = 1.0 / 2; 赋值转换 // 赋值时左边是什么类型,就会自动将右边转换为什么类型再保存 int a = 10.6; 注意点: 参与计算的是什么类型, 结果就是什么类型 // 结果为0, 因为参与运算的都是整型 double a = (double)(1 / 2); // 结果为0.5, 因为1被强制转换为了double类型, 2也会被自动提升为double类型 double b = (double)1 / 2; 类型转换并不会影响到原有变量的值 #include <stdio.h> int main(){ double d = 3.14; int num = (int)d; printf("num = %i\n", num); // 3 printf("d = %lf\n", d); // 3.140000 } 阶段练习 if(x==y) 与 if(x=y) 之间的区别是什么? 判断一个数是否是100到200之间的数 表达式 6==6==6 的值是多少? 用户从键盘上输入三个整数,找出最大值,然后输入最大值 配套视频地址:www.it666.com
Hello world 这个世界上, 几乎所有程序员入门的第一段代码都是Hello World. 原因是当年C语言的作者Dennis Ritchie(丹尼斯 里奇)在他的名著<The C Programming Language>中第一次引入, 传为后世经典, 其它语言亦争相效仿, 以示敬意 如何创建C语言文件 C语言程序组成 手机有很多功能, "开机","关机","打电话","发短信","拍照"等等 手机中的每一个功能就相当于C语言程序中的一个程序段(函数) 众多功能中总有一个会被先执行,不可能多个功能一起执行 想使用手机必须先执行手机的开机功能 所以C语言程序也一样,由众多功能、众多程序段组成, 众多C语言程序段中总有一个会被先执行, 这个先执行的程序段我们称之为"主函数" 一个C语言程序由多个"函数"构成,每个函数有自己的功能 一个程序有且只有一个主函数 如果一个程序没有主函数,则这个程序不具备运行能力 程序运行时系统会自动调用主函数,而其它函数需要开发者手动调用 主函数有固定书写的格式和范写 函数定义格式 主函数定义的格式: int 代表函数执行之后会返回一个整数类型的值 main 代表这个函数的名字叫做main () 代表这是一个函数 {} 代表这个程序段的范围 return 0; 代表函数执行完之后返回整数0 int main() { // insert code here... return 0; } 其它函数定义的格式 int 代表函数执行之后会返回一个整数类型的值 call 代表这个函数的名字叫做call () 代表这是一个函数 {} 代表这个程序段的范围 return 0; 代表函数执行完之后返回整数0 int call() { return 0; } 如何执行定义好的函数 主函数(main)会由系统自动调用, 但其它函数不会, 所以想要执行其它函数就必须在main函数中手动调用 call 代表找到名称叫做call的某个东西 () 代表要找到的名称叫call的某个东西是一个函数 ; 代表调用函数的语句已经编写完成 所以call();代表找到call函数, 并执行call函数 int main() { call(); return 0; } 如何往屏幕上输出内容 输出内容是一个比较复杂的操作, 所以系统提前定义好了一个专门用于输出内容的函数叫做printf函数,我们只需要执行系统定义好的printf函数就可以往屏幕上输出内容 但凡需要执行一个函数, 都是通过函数名称+圆括号的形式来执行 如下代码的含义是: 当程序运行时系统会自动执行main函数, 在系统自动执行main函数时我们手动执行了call函数和printf函数 经过对代码的观察, 我们发现两个问题 并没有告诉printf函数,我们要往屏幕上输出什么内容 找不到printf函数的实现代码 int call(){ return 0; } int main(){ call(); printf(); return 0; } 如何告诉printf函数要输出的内容 将要输出的内容编写到printf函数后面的圆括号中即可 注意: 圆括号中编写的内容必须用双引号引起来 printf("hello world\n"); 如何找到printf函数的实现代码 由于printf函数是系统实现的函数, 所以想要使用printf函数必须在使用之前告诉系统去哪里可以找到printf函数的实现代码 #include <stdio.h> 就是告诉系统可以去stdio这个文件中查找printf函数的声明和实现 #include <stdio.h> int call(){ return 0; } int main(){ call(); printf("hello world\n"); return 0; } 如何运行编写好的程序 方式1: 点击小榔头将"源代码"编译成"可执行文件" 找到编译后的源代码, 打开终端(CMD)运行可执行文件 方式2 直接点击Qt开发工具运行按钮 main函数注意点及其它写法 C语言中,每条完整的语句后面都必须以分号结尾 int main(){ printf("hello world\n") // 如果没有分号编译时会报错 return 0; } int main(){ // 如果没有分号,多条语句合并到一行时, 系统不知道从什么地方到什么地方是一条完整语句 printf("hello world\n") return 0; } C语言中除了注释和双引号引起来的地方以外都不能出现中文 int main(){ printf("hello world\n"); // 这里的分号如果是中文的分号就会报错 return 0; } 一个C语言程序只能有一个main函数 int main(){ return 0; } int main(){ // 编译时会报错, 重复定义 return 0; } 一个C语言程序不能没有main函数 int call(){ // 编译时报错, 因为只有call函数, 没有main函数 return 0; } int mian(){ // 编译时报错, 因为main函数的名称写错了,还是相当于没有main函数 return 0; } main函数前面的int可以不写或者换成void #include <stdio.h> main(){ // 不会报错 printf("hello world\n"); return 0; } #include <stdio.h> void main(){ // 不会报错 printf("hello world\n"); return 0; } main函数中的return 0可以不写 int main(){ // 不会报错 printf("hello world\n"); } 多种写法不报错的原因 C语言最早的时候只是一种规范和标准(例如C89, C11等) 标准的推行需要各大厂商的支持和实施 而在支持的实施的时候由于各大厂商利益、理解等问题,导致了实施的标准不同,发生了变化 Turbo C Visual C(VC) GNU C(GCC) 所以大家才会看到不同的书上书写的格式有所不同, 有的返回int,有的返回void,有的甚至没有返回值 所以大家只需要记住最标准的写法即可, no zuo no die #include <stdio.h> int main(){ printf("hello world\n"); return 0; } Tips: 语法错误:编译器会直接报错 逻辑错误:没有语法错误,只不过运行结果不正确 C语言程序练习 编写一个C语言程序,用至少2种方式在屏幕上输出以下内容 *** *** ********* ******* **** ** 普通青年实现 printf(" *** *** \n"); printf("*********\n"); printf(" *******\n"); printf(" ****\n"); printf(" **\n"); 2B青年实现 printf(" *** *** \n*********\n *******\n ****\n **\n"); 文艺青年实现(装逼的, 先不用理解) int i = 0; while (1) { if (i % 2 == 0) { printf(" *** *** \n"); printf("*********\n"); printf(" *******\n"); printf(" ****\n"); printf(" **\n"); }else { printf("\n"); printf(" ** ** \n"); printf(" *******\n"); printf(" *****\n"); printf(" **\n"); } sleep(1); i++; system("cls"); } 初学者如何避免程序出现BUG _ooOoo_ o8888888o 88" . "88 (| -_- |) O\ = /O ____/`---'\____ . ' \\| |// `. / \\||| : |||// \ / _||||| -:- |||||- \ | | \\\ - /// | | | \_| ''\---/'' | | \ .-\__ `-` ___/-. / ___`. .' /--.--\ `. . __ ."" '< `.___\_<|>_/___.' >'"". | | : `- \`.;`\ _ /`;.`/ - ` : | | \ \ `-. \_ __\ /__ _/ .-` / / ======`-.____`-.___\_____/___.-`____.-'====== `=---=' ............................................. 佛祖保佑 有无BUG ━━━━━━神兽出没━━━━━━ ┏┓ ┏┓ ┏┛┻━━━━━━┛┻┓ ┃ ┃ ┃ ━ ┃ ┃ ┳┛ ┗┳ ┃ ┃ ┃ ┃ ┻ ┃ ┃ ┃ ┗━┓ ┏━┛Code is far away from bug with the animal protecting ┃ ┃ 神兽保佑,代码无bug ┃ ┃ ┃ ┗━━━┓ ┃ ┣┓ ┃ ┏━━┛┛ ┗┓┓┏━┳┓┏┛ ┃┫┫ ┃┫┫ ┗┻┛ ┗┻┛ ━━━━━━感觉萌萌哒━━━━━━ ´´´´´´´´██´´´´´´´ ´´´´´´´████´´´´´´ ´´´´´████████´´´´ ´´`´███▒▒▒▒███´´´´´ ´´´███▒●▒▒●▒██´´´ ´´´███▒▒▒▒▒▒██´´´´´ ´´´███▒▒▒▒██´ 项目:第一个C语言程序 ´´██████▒▒███´´´´´ 语言: C语言 ´██████▒▒▒▒███´´ 编辑器: Qt Creator ██████▒▒▒▒▒▒███´´´´ 版本控制:git-github ´´▓▓▓▓▓▓▓▓▓▓▓▓▓▒´´ 代码风格:江哥style ´´▒▒▒▒▓▓▓▓▓▓▓▓▓▒´´´´´ ´.▒▒▒´´▓▓▓▓▓▓▓▓▒´´´´´ ´.▒▒´´´´▓▓▓▓▓▓▓▒ ..▒▒.´´´´▓▓▓▓▓▓▓▒ ´▒▒▒▒▒▒▒▒▒▒▒▒ ´´´´´´´´´███████´´´´´ ´´´´´´´´████████´´´´´´´ ´´´´´´´█████████´´´´´´ ´´´´´´██████████´´´´ 大部分人都在关注你飞的高不高,却没人在乎你飞的累不累,这就是现实! ´´´´´´██████████´´´ 我从不相信梦想,我,只,相,信,自,己! ´´´´´´´█████████´´ ´´´´´´´█████████´´´ ´´´´´´´´████████´´´´´ ________▒▒▒▒▒ _________▒▒▒▒ _________▒▒▒▒ ________▒▒_▒▒ _______▒▒__▒▒ _____ ▒▒___▒▒ _____▒▒___▒▒ ____▒▒____▒▒ ___▒▒_____▒▒ ███____ ▒▒ ████____███ █ _███_ _█_███ ——————————————————————————女神保佑,代码无bug—————————————————————— 多语言对比 C语言 #include<stdio.h> int main() { printf("南哥带你装B带你飞"); return 0; } C++语言 #include<iostream> using namespace std; int main() { cout << "南哥带你装B带你飞" << endl; return 0; } OC语言 #import <Foundation/Foundation.h> int main() { NSLog(@"南哥带你装B带你飞"); return 0; } Java语言 class Test { public static viod main() { system.out.println("南哥带你装B带你飞"); } } Go语言 package main import "fmt" //引入fmt库 func main() { fmt.Println("南哥带你装B带你飞") } 配套视频地址:www.it666.com
什么是数据? 生活中无时无刻都在跟数据打交道 例如:人的体重、身高、收入、性别等数据等 在我们使用计算机的过程中,也会接触到各种各样的数据 例如: 文档数据、图片数据、视频数据等 数据分类 静态的数据 静态数据是指一些永久性的数据,一般存储在硬盘中。硬盘的存储空间一般都比较大,现在普通计算机的硬盘都有500G左右,因此硬盘中可以存放一些比较大的文件 存储的时长:计算机关闭之后再开启,这些数据依旧还在,只要你不主动删掉或者硬盘没坏,这些数据永远都在 哪些是静态数据:静态数据一般是以文件的形式存储在硬盘上,比如文档、照片、视频等。 动态的数据 动态数据指在程序运行过程中,动态产生的临时数据,一般存储在内存中。内存的存储空间一般都比较小,现在普通计算机的内存只有8G左右,因此要谨慎使用内存,不要占用太多的内存空间 存储的时长:计算机关闭之后,这些临时数据就会被清除 哪些是动态数据:当运行某个程序(软件)时,整个程序就会被加载到内存中,在程序运行过程中,会产生各种各样的临时数据,这些临时数据都是存储在内存中的。当程序停止运行或者计算机被强制关闭时,这个程序产生的所有临时数据都会被清除。 既然硬盘的存储空间这么大,为何不把所有的应用程序加载到硬盘中去执行呢? 主要原因就是内存的访问速度比硬盘快N倍 静态数据和动态数据的相互转换 也就是从磁盘加载到内存 动态数据和静态数据的相互转换 也就是从内存保存到磁盘 数据的计量单位 不管是静态还是动态数据,都是0和1组成的 数据越大,包含的0和1就越多 1 B(Byte字节) = 8 bit(位) // 00000000 就是一个字节 // 111111111 也是一个字节 // 10101010 也是一个字节 // 任意8个0和1的组合都是一个字节 1 KB(KByte) = 1024 B 1 MB = 1024 KB 1 GB = 1024 MB 1 TB = 1024 GB C语言数据类型 作为程序员, 我们最关心的是内存中的动态数据,因为我们写的程序就是在内存中运行的 程序在运行过程中会产生各种各样的临时数据,为了方便数据的运算和操作, C语言对这些数据进行了分类, 提供了丰富的数据类型 C语言中有4大类数据类型:基本类型、构造类型、指针类型、空类型 什么是常量? "量"表示数据。常量,则表示一些固定的数据,也就是不能改变的数据 就好比现实生活中生男生女一样, 生下来是男孩永远都是男孩, 生下来是女孩就永远都是女孩, 所以性别就是现实生活中常量的一种体现 不要和江哥吹牛X说你是泰国来的, 如果你真的来自泰国, 我只能说你赢了 常量的类型 整型常量 十进制整数。例如:666,-120, 0 八进制整数,八进制形式的常量都以0开头。例如:0123,也就是十进制的83;-011,也就是十进 制的-9 十六进制整数,十六进制的常量都是以0x开头。例如:0x123,也就是十进制的291 二进制整数,逢二进一 0b开头。例如: 0b0010,也就是十进制的2 实型常量 小数形式 单精度小数:以字母f或字母F结尾。例如:0.0f、1.01f 双精度小数:十进制小数形式。例如:3.14、 6.66 默认就是双精度 可以没有整数位只有小数位。例如: .3、 .6f 指数形式 以幂的形式表示, 以字母e或字母E后跟一个10为底的幂数 上过初中的都应该知道科学计数法吧,指数形式的常量就是科学计数法的另一种表 示,比如123000,用科学计数法表示为1.23×10的5次方 用C语言表示就是1.23e5或1.23E5 字母e或字母E后面的指数必须为整数 字母e或字母E前后必须要有数字 字母e或字母E前后不能有空格 字符常量 字符型常量都是用''(单引号)括起来的。例如:'a'、'b'、'c' 字符常量的单引号中只能有一个字符 特殊情况: 如果是转义字符,单引号中可以有两个字符。例如:'\n'、'\t' 字符串常量 字符型常量都是用""(双引号)括起来的。例如:"a"、"abc"、"lnj" 系统会自动在字符串常量的末尾加一个字符'\0'作为字符串结束标志 自定义常量 后期讲解内容, 此处先不用了解 常量类型练习 123 1.1F 1.1 .3 'a' "a" "李南江" 什么是变量? "量"表示数据。变量,则表示一些不固定的数据,也就是可以改变的数据 就好比现实生活中人的身高、体重一样, 随着年龄的增长会不断发生改变, 所以身高、体重就是现实生活中变量的一种体现 就好比现实生活中超市的储物格一样, 同一个格子在不同时期不同人使用,格子中存储的物品是可以变化的。张三使用这个格子的时候里面放的可能是尿不湿, 但是李四使用这个格子的时候里面放的可能是面包 如何定义变量 格式1: 变量类型 变量名称 ; 为什么要定义变量? 任何变量在使用之前,必须先进行定义, 只有定义了变量才会分配存储空间, 才有空间存储数据 为什么要限定类型? 用来约束变量所存放数据的类型。一旦给变量指明了类型,那么这个变量就只能存储这种类型的数据 内存空间极其有限,不同类型的变量占用不同大小的存储空间 为什么要指定变量名称? 存储数据的空间对于我们没有任何意义, 我们需要的是空间中存储的值 只有有了名称, 我们才能获取到空间中的值 int a; float b; char ch; 格式2:变量类型 变量名称,变量名称; 连续定义, 多个变量之间用逗号(,)号隔开 int a,b,c; 变量名的命名的规范 变量名属于标识符,所以必须严格遵守标识符的命名原则 如何使用变量? 可以利用=号往变量里面存储数据 在C语言中,利用=号往变量里面存储数据, 我们称之为给变量赋值 int value; value = 998; // 赋值 注意: 这里的=号,并不是数学中的“相等”,而是C语言中的赋值运算符,作用是将右边的整型常量998赋值给左边的整型变量value 赋值的时候,= 号的左侧必须是变量 (10=b,错误) 为了方便阅读代码, 习惯在 = 的两侧 各加上一个 空格 变量的初始化 C语言中, 变量的第一次赋值,我们称为“初始化” 初始化的两种形式 先定义,后初始化 int value; value = 998; // 初始化 定义时同时初始化 int a = 10; int b = 4, c = 2; 其它表现形式(不推荐) int a, b = 10; //部分初始化 int c, d, e; c = d = e =0; 不初始化里面存储什么? 随机数 上次程序分配的存储空间,存数一些 内容,“垃圾” 系统正在用的一些数据 如何修改变量值? 多次赋值即可 每次赋值都会覆盖原来的值 int i = 10; i = 20; // 修改变量的值 变量之间的值传递 可以将一个变量存储的值赋值给另一个变量 int a = 10; int b = a; // 相当于把a中存储的10拷贝了一份给b 如何查看变量的值? 使用printf输出一个或多个变量的值 int a = 10, c = 11; printf("a=%d, c=%d", a, c); 输出其它类型变量的值 double height = 1.75; char blood = 'A'; printf("height=%.2f, 血型是%c", height, blood); 变量的作用域 C语言中所有变量都有自己的作用域 变量定义的位置不同,其作用域也不同 按照作用域的范围可分为两种, 即局部变量和全局变量 局部变量 局部变量也称为内部变量 局部变量是在代码块内定义的, 其作用域仅限于代码块内, 离开该代码块后无法使用 int main(){ int i = 998; // 作用域开始 return 0;// 作用域结束 } int main(){ { int i = 998; // 作用域开始 }// 作用域结束 printf("i = %d\n", i); // 不能使用 return 0; } int main(){ { { int i = 998;// 作用域开始 }// 作用域结束 printf("i = %d\n", i); // 不能使用 } return 0; } 全局变量 全局变量也称为外部变量,它是在代码块外部定义的变量 int i = 666; int main(){ printf("i = %d\n", i); // 可以使用 return 0; }// 作用域结束 int call(){ printf("i = %d\n", i); // 可以使用 return 0; } 注意点: 同一作用域范围内不能有相同名称的变量 int main(){ int i = 998; // 作用域开始 int i = 666; // 报错, 重复定义 return 0; }// 作用域结束 int i = 666; int i = 998; // 报错, 重复定义 int main(){ return 0; } 不同作用域范围内可以有相同名称的变量 int i = 666; int main(){ int i = 998; // 不会报错 return 0; } int main(){ int i = 998; // 不会报错 return 0; } int call(){ int i = 666; // 不会报错 return 0; } 变量内存分析(简单版) 字节和地址 为了更好地理解变量在内存中的存储细节,先来认识一下内存中的“字节”和“地址” 每一个小格子代表一个字节 每个字节都有自己的内存地址 内存地址是连续的 变量存储占用的空间 一个变量所占用的存储空间,和定义变量时声明的类型以及当前编译环境有关 类型 16位编译器 32位编译器 64位编译器 char 1 1 1 int 2 4 4 float 4 4 4 double 8 8 8 short 2 2 2 long 4 4 8 long long 8 8 8 void* 2 4 8 变量存储的过程 根据定义变量时声明的类型和当前编译环境确定需要开辟多大存储空间 在内存中开辟一块存储空间,开辟时从内存地址大的开始开辟(内存寻址从大到小) 将数据保存到已经开辟好的对应内存空间中 int main(){ int number; int value; number = 22; value = 666; } #include <stdio.h> int main(){ int number; int value; number = 22; value = 666; printf("&number = %p\n", &number); // 0060FEAC printf("&value = %p\n", &value); // 0060FEA8 } 先不要着急, 刚开始接触C语言, 我先了解这么多就够了. 后面会再次更深入的讲解存储的各种细节 配套视频地址:www.it666.com
printf函数 printf函数称之为格式输出函数,方法名称的最后一个字母f表示format。其功能是按照用户指定的格式,把指定的数据输出到屏幕上 printf函数的调用格式为: printf("格式控制字符串",输出项列表 ); 例如:printf("a = %d, b = %d",a, b); 非格式字符串原样输出, 格式控制字符串会被输出项列表中的数据替换 注意: 格式控制字符串和输出项在数量和类型上必须一一对应 格式控制字符串 形式: %[标志][输出宽度][.精度][长度]类型 类型 格式: printf("a = %类型", a); 类型字符串用以表示输出数据的类型, 其格式符和意义如下所示 类型 含义 d 有符号10进制整型 i 有符号10进制整型 u 无符号10进制整型 o 无符号8进制整型 x 无符号16进制整型 X 无符号16进制整型 f 单、双精度浮点数(默认保留6位小数) e / E 以指数形式输出单、双精度浮点数 g / G 以最短输出宽度,输出单、双精度浮点数 c 字符 s 字符串 p 地址 #include <stdio.h> int main(){ int a = 10; int b = -10; float c = 6.6f; double d = 3.1415926; double e = 10.10; char f = 'a'; // 有符号整数(可以输出负数) printf("a = %d\n", a); // 10 printf("a = %i\n", a); // 10 // 无符号整数(不可以输出负数) printf("a = %u\n", a); // 10 printf("b = %u\n", b); // 429496786 // 无符号八进制整数(不可以输出负数) printf("a = %o\n", a); // 12 printf("b = %o\n", b); // 37777777766 // 无符号十六进制整数(不可以输出负数) printf("a = %x\n", a); // a printf("b = %x\n", b); // fffffff6 // 无符号十六进制整数(不可以输出负数) printf("a = %X\n", a); // A printf("b = %X\n", b); // FFFFFFF6 // 单、双精度浮点数(默认保留6位小数) printf("c = %f\n", c); // 6.600000 printf("d = %lf\n", d); // 3.141593 // 以指数形式输出单、双精度浮点数 printf("e = %e\n", e); // 1.010000e+001 printf("e = %E\n", e); // 1.010000E+001 // 以最短输出宽度,输出单、双精度浮点数 printf("e = %g\n", e); // 10.1 printf("e = %G\n", e); // 10.1 // 输出字符 printf("f = %c\n", f); // a } 宽度 格式: printf("a = %[宽度]类型", a); 用十进制整数来指定输出的宽度, 如果实际位数多于指定宽度,则按照实际位数输出, 如果实际位数少于指定宽度则以空格补位 #include <stdio.h> int main(){ // 实际位数小于指定宽度 int a = 1; printf("a =|%d|\n", a); // |1| printf("a =|%5d|\n", a); // | 1| // 实际位数大于指定宽度 int b = 1234567; printf("b =|%d|\n", b); // |1234567| printf("b =|%5d|\n", b); // |1234567| } 标志 格式: printf("a = %[标志][宽度]类型", a); 标志 含义 - 左对齐, 默认右对齐 + 当输出值为正数时,在输出值前面加上一个+号, 默认不显示 0 右对齐时, 用0填充宽度.(默认用空格填充) 空格 输出值为正数时,在输出值前面加上空格, 为负数时加上负号 # 对c、s、d、u类型无影响 # 对o类型, 在输出时加前缀o # 对x类型,在输出时加前缀0x #include <stdio.h> int main(){ int a = 1; int b = -1; // -号标志 printf("a =|%d|\n", a); // |1| printf("a =|%5d|\n", a); // | 1| printf("a =|%-5d|\n", a);// |1 | // +号标志 printf("a =|%d|\n", a); // |1| printf("a =|%+d|\n", a);// |+1| printf("b =|%d|\n", b); // |-1| printf("b =|%+d|\n", b);// |-1| // 0标志 printf("a =|%5d|\n", a); // | 1| printf("a =|%05d|\n", a); // |00001| // 空格标志 printf("a =|% d|\n", a); // | 1| printf("b =|% d|\n", b); // |-1| // #号 int c = 10; printf("c = %o\n", c); // 12 printf("c = %#o\n", c); // 012 printf("c = %x\n", c); // a printf("c = %#x\n", c); // 0xa } 精度 格式: printf("a = %[精度]类型", a); 精度格式符以"."开头, 后面跟上十进制整数, 用于指定需要输出多少位小数, 如果输出位数大于指定的精度, 则删除超出的部分 #include <stdio.h> int main(){ double a = 3.1415926; printf("a = %.2f\n", a); // 3.14 } 动态指定保留小数位数 格式: printf("a = %.*f", a); #include <stdio.h> int main(){ double a = 3.1415926; printf("a = %.*f", 2, a); // 3.14 } 实型(浮点类型)有效位数问题 对于单精度数,使用%f格式符输出时,仅前6~7位是有效数字 对于双精度数,使用%lf格式符输出时,前15~16位是有效数字 有效位数和精度(保留多少位)不同, 有效位数是指从第一个非零数字开始,误差不超过本数位半个单位的、精确可信的数位 有效位数包含小数点前的非零数位 #include <stdio.h> int main(){ // 1234.567871093750000 float a = 1234.567890123456789; // 1234.567890123456900 double b = 1234.567890123456789; printf("a = %.15f\n", a); // 前8位数字是准确的, 后面的都不准确 printf("b = %.15f\n", b); // 前16位数字是准确的, 后面的都不准确 } 长度 格式: printf("a = %[长度]类型", a); 长度 修饰类型 含义 hh d、i、o、u、x 输出char h d、i、o、u、x 输出 short int l d、i、o、u、x 输出 long int ll d、i、o、u、x 输出 long long int #include <stdio.h> int main(){ char a = 'a'; short int b = 123; int c = 123; long int d = 123; long long int e = 123; printf("a = %hhd\n", a); // 97 printf("b = %hd\n", b); // 123 printf("c = %d\n", c); // 123 printf("d = %ld\n", d); // 123 printf("e = %lld\n", e); // 123 } 转义字符 格式: printf("%f%%", 3.1415); %号在格式控制字符串中有特殊含义, 所以想输出%必须添加一个转移字符 #include <stdio.h> int main(){ printf("%f%%", 3.1415); // 输出结果3.1415% } Scanf函数 scanf函数用于接收键盘输入的内容, 是一个阻塞式函数,程序会停在scanf函数出现的地方, 直到接收到数据才会执行后面的代码 printf函数的调用格式为: scanf("格式控制字符串", 地址列表); 例如: scanf("%d", &num); 基本用法 地址列表项中只能传入变量地址, 变量地址可以通过&符号+变量名称的形式获取 #include <stdio.h> int main(){ int number; scanf("%d", &number); // 接收一个整数 printf("number = %d\n", number); } 接收非字符和字符串类型时, 空格、Tab和回车会被忽略 #include <stdio.h> int main(){ float num; // 例如:输入 Tab 空格 回车 回车 Tab 空格 3.14 , 得到的结果还是3.14 scanf("%f", &num); printf("num = %f\n", num); } 非格式字符串原样输入, 格式控制字符串会赋值给地址项列表项中的变量 不推荐这种写法 #include <stdio.h> int main(){ int number; // 用户必须输入number = 数字 , 否则会得到一个意外的值 scanf("number = %d", &number); printf("number = %d\n", number); } 接收多条数据 格式控制字符串和地址列表项在数量和类型上必须一一对应 非字符和字符串情况下如果没有指定多条数据的分隔符, 可以使用空格或者回车作为分隔符(不推荐这种写法) 非字符和字符串情况下建议明确指定多条数据之间分隔符 #include <stdio.h> int main(){ int number; scanf("%d", &number); printf("number = %d\n", number); int value; scanf("%d", &value); printf("value = %d\n", value); } #include <stdio.h> int main(){ int number; int value; // 可以输入 数字 空格 数字, 或者 数字 回车 数字 scanf("%d%d", &number, &value); printf("number = %d\n", number); printf("value = %d\n", value); } #include <stdio.h> int main(){ int number; int value; // 输入 数字,数字 即可 scanf("%d,%d", &number, &value); printf("number = %d\n", number); printf("value = %d\n", value); } \n是scanf函数的结束符号, 所以格式化字符串中不能出现\n #include <stdio.h> int main(){ int number; // 输入完毕之后按下回车无法结束输入 scanf("%d\n", &number); printf("number = %d\n", number); } scanf运行原理 系统会将用户输入的内容先放入输入缓冲区 scanf方式会从输入缓冲区中逐个取出内容赋值给变量 如果输入缓冲区的内容不为空,scanf会一直从缓冲区中获取,而不要求再次输入 #include <stdio.h> int main(){ int num1; int num2; char ch1; scanf("%d%c%d", &num1, &ch1, &num2); printf("num1 = %d, ch1 = %c, num2 = %d\n", num1, ch1, num2); char ch2; int num3; scanf("%c%d",&ch2, &num3); printf("ch2 = %c, num3 = %d\n", ch2, num3); } 利用fflush方法清空缓冲区(不是所有平台都能使用) 格式: fflush(stdin); C和C++的标准里从来没有定义过 fflush(stdin) MSDN 文档里清除的描述着"fflush on input stream is an extension to the C standard" (fflush 是在标准上扩充的函数, 不是标准函数, 所以不是所有平台都支持) 利用setbuf方法清空缓冲区(所有平台有效) 格式: setbuf(stdin, NULL); #include <stdio.h> int main(){ int num1; int num2; char ch1; scanf("%d%c%d", &num1, &ch1, &num2); printf("num1 = %d, ch1 = %c, num2 = %d\n", num1, ch1, num2); //fflush(stdin); // 清空输入缓存区 setbuf(stdin, NULL); // 清空输入缓存区 char ch2; int num3; scanf("%c%d",&ch2, &num3); printf("ch2 = %c, num3 = %d\n", ch2, num3); } putchar和getchar putchar: 向屏幕输出一个字符 #include <stdio.h> int main(){ char ch = 'a'; putchar(ch); // 输出a } getchar: 从键盘获得一个字符 #include <stdio.h> int main(){ char ch; ch = getchar();// 获取一个字符 printf("ch = %c\n", ch); } 配套视频地址:www.it666.com
什么是注释? 注释是在所有计算机语言中都非常重要的一个概念,从字面上看,就是注解、解释的意思 注释可以用来解释某一段程序或者某一行代码是什么意思,方便程序员之间的交流沟通 注释可以是任何文字,也就是说可以写中文 被注释的内容在开发工具中会有特殊的颜色 为什么要使用注释? 没有编写任何注释的程序 void printMap(char map[6][7] , int row, int col); int main(int argc, const char * argv[]) { char map[6][7] = { {'#', '#', '#', '#', '#', '#', '#'}, {'#', ' ', ' ', ' ', '#' ,' ', ' '}, {'#', 'R', ' ', '#', '#', ' ', '#'}, {'#', ' ', ' ', ' ', '#', ' ', '#'}, {'#', '#', ' ', ' ', ' ', ' ', '#'}, {'#', '#', '#', '#', '#', '#', '#'} }; int row = sizeof(map)/sizeof(map[0]); int col = sizeof(map[0])/ sizeof(map[0][0]); printMap(map, row, col); int pRow = 2; int pCol = 1; int endRow = 1; int endCol = 6; while ('R' != map[endRow][endCol]) { printf("亲, 请输入相应的操作\n"); printf("w(向上走) s(向下走) a(向左走) d(向右走)\n"); char run; run = getchar(); switch (run) { case 's': if ('#' != map[pRow + 1][pCol]) { map[pRow][pCol] = ' '; pRow++;//3 map[pRow][pCol] = 'R'; } break; case 'w': if ('#' != map[pRow - 1][pCol]) { map[pRow][pCol] = ' '; pRow--; map[pRow][pCol] = 'R'; } break; case 'a': if ('#' != map[pRow][pCol - 1]) { map[pRow][pCol] = ' '; pCol--; map[pRow][pCol] = 'R'; } break; case 'd': if ('#' != map[pRow][pCol + 1]) { map[pRow][pCol] = ' '; pCol++; map[pRow][pCol] = 'R'; } break; } printMap(map, row, col); } printf("你太牛X了\n"); printf("想挑战自己,请购买完整版本\n"); return 0; } void printMap(char map[6][7] , int row, int col) { system("cls"); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf("%c", map[i][j]); } printf("\n"); } } 编写了注释的程序 /* R代表一个人 #代表一堵墙 // 0123456 ####### // 0 # # // 1 #R ## # // 2 # # # // 3 ## # // 4 ####### // 5 分析: >1.保存地图(二维数组) >2.输出地图 >3.操作R前进(控制小人行走) 3.1.接收用户输入(scanf/getchar) w(向上走) s(向下走) a(向左走) d(向右走) 3.2.判断用户的输入,控制小人行走 3.2.1.替换二维数组中保存的数据 ( 1.判断是否可以修改(如果不是#就可以修改) 2.修改现有位置为空白 3.修改下一步为R ) 3.3.输出修改后的二维数组 4.判断用户是否走出出口 */ // 声明打印地图方法 void printMap(char map[6][7] , int row, int col); int main(int argc, const char * argv[]) { // 1.定义二维数组保存迷宫地图 char map[6][7] = { {'#', '#', '#', '#', '#', '#', '#'}, {'#', ' ', ' ', ' ', '#' ,' ', ' '}, {'#', 'R', ' ', '#', '#', ' ', '#'}, {'#', ' ', ' ', ' ', '#', ' ', '#'}, {'#', '#', ' ', ' ', ' ', ' ', '#'}, {'#', '#', '#', '#', '#', '#', '#'} }; // 2.计算地图行数和列数 int row = sizeof(map)/sizeof(map[0]); int col = sizeof(map[0])/ sizeof(map[0][0]); // 3.输出地图 printMap(map, row, col); // 4.定义变量记录人物位置 int pRow = 2; int pCol = 1; // 5.定义变量记录出口的位置 int endRow = 1; int endCol = 6; // 6.控制人物行走 while ('R' != map[endRow][endCol]) { // 6.1提示用户如何控制人物行走 printf("亲, 请输入相应的操作\n"); printf("w(向上走) s(向下走) a(向左走) d(向右走)\n"); char run; run = getchar(); // 6.2根据用户输入控制人物行走 switch (run) { case 's': if ('#' != map[pRow + 1][pCol]) { map[pRow][pCol] = ' '; pRow++;//3 map[pRow][pCol] = 'R'; } break; case 'w': if ('#' != map[pRow - 1][pCol]) { map[pRow][pCol] = ' '; pRow--; map[pRow][pCol] = 'R'; } break; case 'a': if ('#' != map[pRow][pCol - 1]) { map[pRow][pCol] = ' '; pCol--; map[pRow][pCol] = 'R'; } break; case 'd': if ('#' != map[pRow][pCol + 1]) { map[pRow][pCol] = ' '; pCol++; map[pRow][pCol] = 'R'; } break; } // 6.3重新输出行走之后的地图 printMap(map, row, col); } printf("你太牛X了\n"); printf("想挑战自己,请购买完整版本\n"); return 0; } /** * @brief printMap * @param map 需要打印的二维数组 * @param row 二维数组的行数 * @param col 二维数组的列数 */ void printMap(char map[6][7] , int row, int col) { // 为了保证窗口的干净整洁, 每次打印都先清空上一次的打印 system("cls"); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf("%c", map[i][j]); } printf("\n"); } } 注释的分类 单行注释 // 被注释内容 使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面 作用范围: 从第二个斜线到这一行末尾 快捷键:Ctrl+/ 多行注释 /* 被注释内容 */ 使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面 作用范围: 从第一个/*到最近的一个*/ 注释的注意点 单行注释可以嵌套单行注释、多行注释 // 南哥 // it666.com // /* 江哥 */ // 帅哥 多行注释可以嵌套单行注释 /* // 作者:LNJ // 描述:第一个C语言程序作用:这是一个主函数,C程序的入口点 */ 多行注释不能嵌套多行注释 /* 哈哈哈 /*嘻嘻嘻*/ 呵呵呵 */ 注释的应用场景 思路分析 /* R代表一个人 #代表一堵墙 // 0123456 ####### // 0 # # // 1 #R ## # // 2 # # # // 3 ## # // 4 ####### // 5 分析: >1.保存地图(二维数组) >2.输出地图 >3.操作R前进(控制小人行走) 3.1.接收用户输入(scanf/getchar) w(向上走) s(向下走) a(向左走) d(向右走) 3.2.判断用户的输入,控制小人行走 3.2.1.替换二维数组中保存的数据 ( 1.判断是否可以修改(如果不是#就可以修改) 2.修改现有位置为空白 3.修改下一步为R ) 3.3.输出修改后的二维数组 4.判断用户是否走出出口 */ 对变量进行说明 // 2.计算地图行数和列数 int row = sizeof(map)/sizeof(map[0]); int col = sizeof(map[0])/ sizeof(map[0][0]); 对函数进行说明 /** * @brief printMap * @param map 需要打印的二维数组 * @param row 二维数组的行数 * @param col 二维数组的列数 */ void printMap(char map[6][7] , int row, int col) { system("cls"); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf("%c", map[i][j]); } printf("\n"); } } 多实现逻辑排序 // 1.定义二维数组保存迷宫地图 char map[6][7] = { {'#', '#', '#', '#', '#', '#', '#'}, {'#', ' ', ' ', ' ', '#' ,' ', ' '}, {'#', 'R', ' ', '#', '#', ' ', '#'}, {'#', ' ', ' ', ' ', '#', ' ', '#'}, {'#', '#', ' ', ' ', ' ', ' ', '#'}, {'#', '#', '#', '#', '#', '#', '#'} }; // 2.计算地图行数和列数 int row = sizeof(map)/sizeof(map[0]); int col = sizeof(map[0])/ sizeof(map[0][0]); // 3.输出地图 printMap(map, row, col); // 4.定义变量记录人物位置 int pRow = 2; int pCol = 1; // 5.定义变量记录出口的位置 int endRow = 1; int endCol = 6; // 6.控制人物行走 while ('R' != map[endRow][endCol]) { ... ... } 使用注释的好处 注释是一个程序员必须要具备的良好习惯 帮助开发人员整理实现思路 解释说明程序, 提高程序的可读性 初学者编写程序可以养成习惯:先写注释再写代码 将自己的思想通过注释先整理出来,在用代码去体现 因为代码仅仅是思想的一种体现形式而已 什么是关键字? 关键字,也叫作保留字。是指一些被C语言赋予了特殊含义的单词 关键字特征: 全部都是小写 在开发工具中会显示特殊颜色 关键字注意点: 因为关键字在C语言中有特殊的含义, 所以不能用作变量名、函数名等 C语言中一共有32个关键字 1 2 3 4 5 6 7 8 char short int long float double if else return do while for switch case break continue default goto sizeof auto register static extern unsigned signed typedef struct enum union void const volatile 这些不用专门去记住,用多了就会了。在编译器里都是有特殊颜色的。 我们用到时候会一个一个讲解这个些关键字怎么用,现在浏览下,有个印象就OK了 关键字分类 什么是标识符? 从字面上理解,就是用来标识某些东西的符号,标识的目的就是为了将这些东西区分开来 其实标识符的作用就跟人类的名字差不多,为了区分每个人,就在每个人出生的时候起了个名字 C语言是由函数构成的,一个C程序中可能会有多个函数,为了区分这些函数,就给每一个函数都起了个名称, 这个名称就是标识符 综上所述: 程序员在程序中给函数、变量等起名字就是标识符 标识符命名规则 只能由字母(a~z、 A~Z)、数字、下划线组成 不能包含除下划线以外的其它特殊字符串 不能以数字开头 不能是C语言中的关键字 标识符严格区分大小写, test和Test是两个不同的标识符 练习 下列哪些是合法的标识符 fromNo22 from#22 my_Boolean my-Boolean 2ndObj GUI lnj Mike2jack 江哥 _test test!32 haha(da)tt jack_rose jack&rose 标识符命名规范 见名知意,能够提高代码的可读性 驼峰命名,能够提高代码的可读性 驼峰命名法就是当变量名或函数名是由多个单词连接在一起,构成标识符时,第一个单词以小写字母开始;第二个单词的首字母大写. 例如: myFirstName、myLastName这样的变量名称看上去就像驼峰一样此起彼伏 配套视频地址:www.it666.com
工欲善其事必先利其器 编写C语言程序用什么工具 ? 记事本(开发效率低) Vim(初学者入门门槛高) VSCode(不喜欢) eclipse(不喜欢) CLion(深爱, 但收费) Xcode(逼格高, 但得有苹果电脑) Qt Creator(开源免费,跨平台安装和运行) 什么是Qt Creator ? Qt Creator 是一款新的轻量级集成开发环境(IDE)。它能够跨平台运行,支持的系统包括 Windows、Linux(32 位及 64 位)以及 Mac OS X Qt Creator 的设计目标是使开发人员能够利用 Qt 这个应用程序框架更加快速及轻易的完成开发任务 开源免费, 简单易用, 能够满足学习需求 集成开发环境(IDE,Integrated Development Environment )是用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具。集成了代码编写功能、分析功能、编译功能、调试功能等一体化的开发软件服务套。 Qt Creator安装 切记囫囵吞枣, 不要纠结里面的东西都是什么含义, 初学者安装成功就是一种成功 下载Qt Creator离线安装包: http://download.qt.io/archive/qt/5.11/5.11.0/ 以管理身份运行离线安装包 下一步,下一步,下一步,等待ing... 注意安装路径中最好不要出现中文 对于初学者而言全选是最简单的方式(重点!!!) 配置Qt Creator开发环境变量 你的安装路径\5.11.0\mingw53_32\bin 你的安装路径\Tools\mingw530_32\bin 启动安装好的Qt Creator 非全选安装到此为止, 全选安装继续往下看 出现这个错误, 忽略这个错误即可 等待安装完毕之后解决刚才的错误 找到安装目录下的strawberry.msi,双击运行 什么是环境变量? 打开我们添加环境变量的两个目录, 不难发现里面大部分都是.exe的可执行程序 如果我们不配置环境变量, 那么每次我们想要使用这些"可执行程序"都必须"先找到这些应用程序对应的文件夹"才能使用 为了方便我们在电脑上"任何地方"都能够使用这些"可执行程序", 那么我们就必须添加环境变量, 因为Windows执行某个程序的时候, 会先到"环境变量中Path指定的路径中"去查找 为什么要配置系统变量,不配置用户变量 用户变量只针对使用这台计算机指定用户 一个计算机可以设置多个用户, 不同的用户用不同的用户名和密码 当给计算机设置了多个用户的时候,启动计算机的时候就会让你选择哪个用户登录 系统变量针对使用这台计算机的所有用户 也就是说设置了系统变量, 无论哪个用户登录这台计算机都可以使用你配置好的工具 Qt Creator快捷键 Qt Creator Keyboard Shortcuts(Documentation) Qt Creator Keyboard Shortcuts(Wiki) 作为了解, 后期课程用到会逐一讲解 配套视频地址:www.it666.com
Helo同学们大家好~ 一年一度的七(nue)夕(gou)节又到了,今天正好也是指趣学院的对外宣布第七天。 首先感谢同学们对我的支持和信任,也借着这个机会给大家汇报一下这一周以来指趣学院的进展。 +其次在这个程序媛小虐怡情,广大(程)直(序)男(猿)“花样求生”的特殊节日里,也给同学们支支招,如何才能“平安”度过七(nue)夕(gou)节? 公布的一周(感恩) 上周五公布消息之后,在没有任何支付保障的情况下,许许多多的学员在第一时间把学费打到了我的账户里,甚至还有十多个参与团购的学员,付完全款之后至今未与我联系(也希望你们看到消息后,快快联系江哥);还有许多同学得知我创业之后,通过QQ、微信、支付宝、公众号等加油点赞,帮助江哥转发、推荐学员等;这其中有素不相识但之前一直在看我免费视频的同学,也有北上广深的老学员。在此再次感谢大家对我的支持和信任,这些留言、支付截图、转发截图我已经全部都保留下来,当我在创业的路上遇到困难时、疲惫或者无助时,这份信任会让我瞬间充满能量. 报名情况(信任) 预报名情况 除首期外,后续期预报名人数80+ 所有预报名学员后续将享受 首期价格学习后续系统课程 免费参与指趣线下大咖交流会 5折购买系统课以外企业级专家课程 更多福利,不断更新ing... ... 首期爆满开班 image 报名首期学员后续将享受 预报名学员所有福利 免费学习后续更新所有课程(含专家课程) 更多福利,不断更新ing... ... 发票开具 开课后会为所有学员统一开具发票 绝不偷税漏税,为祖国发展添砖 一周准备(责任) 鉴于很多喜欢江哥课程的同学都说喜欢江哥笔记, 所以这周开始江哥已经在陆续整理后续笔记,同时已有多家出版商洽谈中,我相信首期结束后相关书籍也会面世 鉴于很多喜欢江哥课程的同学都说喜欢江哥的幽默和激情, 看过我的课程后看别人的课程容易睡觉,所以为了能够给大家带来最嗨的体验, 江哥专门订购了一个录音棚 image 鉴于很多同学说没有开发经验,不了解企业, 所以江哥也正在努力联络身边的朋友,期待为每一期的学员都准备一次“大厂技术负责人”线上见面分享会 鉴于很多同学说没有女朋友,所以首期女生直接8折, 这也使得首期女同学人数突破10人, 请不要辜负江哥努力为大家创造的机会, 唯有好好学习,才能结对编程 image 安度七夕(祝福) 根据过往南江老师的血泪经验,七夕大体可以分为以下两种度过方式: 抱团取暖式: 作为一种永远无法享受第二杯半价的生物,七夕节大部分的程序员(媛)会选择朋友聚会的方式来获取温暖 在家学习式:作为未来丈母娘心目中好女婿(媳妇)的最佳人选,七夕节大部分程序员(媛)也会通过结对编程,不断学习,不断提升的方式给自己带来快乐 image 真情回馈(福利) 转发文章,免费获取“从零玩转JavaScript+项目实战” 扫码邀请5人砍价6分钱购买“从零玩转Go+项目实战” image 另外希望大家将每期免费培训名额的消息,告诉身边家庭困难,但仍坚持学习、努力实现梦想的学员 回归教育本质,让教育更公平
计算机常识 什么是计算机 ? 顾名思义,就是能够进行数据运算的机器(台式电脑、笔记本电脑、平板电脑、智能手机) 计算机_百度百科 计算机的发明者是谁 ? 关于电子计算机的发明者是谁这一问题,有好几种答案: 1936年英国数学家图灵首先提出了一种以程序和输入数据相互作用产生输出的计算机构想,后人将这种机器命名为通用图灵计算机 1938年克兰德·楚泽发明了首台采用继电器进行工作的计算机,这台计算机命名为Z1,但是继电器是机械式的,并不是完全的电子器材 1942年阿坦那索夫和贝利发明了首台采用真空管的计算机,这台计算机命名为ABC 1946年ENIAC诞生,它拥有了今天计算机的主要结构和功能,是通用计算机 现在世界上公认的第一台现代电子计算机是1946年在美国宾夕法尼亚大学诞生的ENIAC(Electronic Numerical Integrator And Calculator) 计算机特点是什么 ? 计算机是一种电器, 所以计算机只能识别两种状态, 一种是通电一种是断电 正是因为如此, 最初ENIAC的程序是由很多开关和连接电线来完成的。但是这样导致改动一次程序要花很长时间(需要人工重新设置很多开关的状态和连接线) 为了提高效率,工程师们想能不能把程序和数据都放在存储器中, 数学家冯·诺依曼将这个思想以数学语言系统阐述,提出了存储程序计算机模型(这是所谓的冯·诺依曼机) 那利用数学语言如何表示计算机能够识别的通电和断电两种状态呢? 非常简单用0和1表示即可 所以计算机能识别的所有指令都是由0和1组成的 所以计算机中存储和操作的数据也都是由0和1组成的 0和1更准确的是应该是高电平和低电平, 但是这个不用了解, 只需要知道计算机只能识别0和1以及存储的数据都是由0和1组成的即可。 什么是计算机程序 ? 计算机程序是为了告诉计算机"做某件事或解决某个问题"而用"计算机语言编写的命令集合(语句) 只要让计算机执行这个程序,计算机就会自动地、有条不紊地进行工作,计算机的一切操作都是由程序控制的,离开程序,计算机将一事无成 现实生活中你如何告诉别人如何做某件事或者解决某个问题? 通过人能听懂的语言: 张三你去楼下帮我买一包烟, 然后顺便到快递箱把我的快递也带上来 其实我们通过人能听懂的语言告诉别人做某件事就是在发送一条条的指令 计算机中也一样, 我们可以通过计算机语言告诉计算机我们想做什么, 每做一件事情就是一条指令, 一条或多条指令的集合我们就称之为一个计算机程序 什么计算机语言 ? 在日常生活、工作中, 语言是人们交流的工具 中国人和中国人交流,使用中文语言 美国人和美国人交流,使用英文语言 人想要和计算机交流,使用计算机语言 可以看出在日常生活、工作中,人们使用的语言种类很多 如果一个很牛人可能同时掌握了中文语言和英文语言, 那么想要和这个人交流既可以使用中文语言,也可以使用英文语言 计算机其实就是一个很牛的人, 计算机同时掌握了几十门甚至上百门语言, 所以我们只要使用任何一种计算机已经掌握的语言就可以和计算机交流 常见的计算机语言类型有哪些 ? 机器语言 所有的代码里面只有0和1, 0表示不加电,1表示加电(纸带存储时 1有孔,0没孔) 优点:直接对硬件产生作用,程序的执行效率非常非常高 缺点:指令又多又难记、可读性差、无可移植性 汇编语言 符号化的机器语言,用一个符号(英文单词、数字)来代表一条机器指令 优点:直接对硬件产生作用,程序的执行效率非常高、可读性稍好 缺点:符号非常多和难记、无可移植性 高级语言 非常接近自然语言的高级语言,语法和结构类似于普通英文 优点:简单、易用、易于理解、远离对硬件的直接操作、有可移植性 缺点:有些高级语言写出的程序执行效率并不高 对比(利用3种类型语言编写1+1) 机器语言 10111000 00000001 00000000 00000101 00000001 00000000 汇编语言 MOV AX, 1 ADD AX, 1 高级语言 1 + 1 什么是C语言? C语言是一种用于和计算机交流的高级语言, 它既具有高级语言的特点,又具有汇编语言的特点 非常接近自然语言 程序的执行效率非常高 C语言是所有编程语言中的经典,很多高级语言都是从C语言中衍生出来的, 例如:C++、C#、Object-C、Java、Go等等 C语言是所有编程语言中的经典,很多著名的系统软件也是C语言编写的 几乎所有的操作系统都是用C语言编写的 几乎所有的计算机底层软件都是用C语言编写的 几乎所有的编辑器都是C语言编写的 C语言历史 最早的高级语言:FORTRAN-->ALGOL-->CPL-->BCPL-->C-->C++等 “初,世间无语言,仅电路与连线。及大牛出,天地开,始有 FORTRAN、 LISP、ALGOL 随之, 乃有万种语” 1963年英国剑桥大学推出了CPL(Combined Programming Langurage)语言。 CPL语言在ALGOL 60的基础上接近硬件一些,但规模比较大,难以实现 1967年英国剑桥大学的 Matin Richards(理查兹)对CPL语言做了简化,推出了 BCPL (Base Combined Programming Langurage)语言 1970年美国贝尔实验室的 Ken Thompson(肯·汤普逊) 以 BCPL 语言为基础,又作了进一步的简化,设计出了很简单的而且很接近硬件的 B 语言(取BCPL的第一个字母),并用B语言写出了第一个 UNIX 操作系统。但B语言过于简单,功能有限 1972年至1973年间,贝尔实验室的 Dennis.Ritchie(丹尼斯·里奇) 在 B语言的基础上设计出了C语言(取BCPL的第二个字母)。C语言即保持 BCPL 语言和B语言的优点(精练、接近硬件),又克服了他们的缺点(过于简单,数据无类型等) C语言标准 1983年美国国家标准局(American National Standards Institute,简称ANSI)成立了一个委员会,开始制定C语言标准的工作 1989年C语言标准被批准,这个版本的C语言标准通常被称为ANSI C(C89) 1999年,国际标准化组织ISO又对C语言标准进行修订,在基本保留原C语言特征的基础上,针对应该的需要,增加了一些功能,命名为C99 2011年12月,ANSI采纳了ISO/IEC 9899:2011标准。这个标准通常即C11,它是C程序语言的现行标准 C语言现状 年度编程语言 该奖项颁发给了一年中最热门的编程语言 编程语言排行榜查看 为什么要学习C语言? 40多年经久不衰 了解操作系统、编译原理、数据结构与算法等知识的最佳语言 了解其它语言底层实现原理必备语言 基础语法与其它高级语言类似,学会C语言之后再学习其它语言事半功倍,且知根知底 当你想了解底层原理时,你才会发现后悔当初没有学习C语言 当你想学习一门新的语言时, 你才会发现后悔当初没有学习C语言 当你使用一些高级框架、甚至系统框架时发现提供的API都是C语言编写的, 你才发现后悔当初没有学习C语言 学好数理化,走遍天下都不拍 学好C语言,再多语言都不怕 如何学好C语言 学习本套课程之前 学习本套课程中 学习本套课程之后 如何达到这样的效果 配套视频地址:www.it666.com
添加节点 内部插入 append(content|fn) appendTo(content) 将元素添加到指定元素内部的最后 // 1.创建一个节点 var $li = $("<li>新增的li</li>"); // 2.添加节点 // $("ul").append($li); $li.appendTo("ul"); prepend(content|fn) prependTo(content) 将元素添加到指定元素内部的最前面 // 1.创建一个节点 var $li = $("<li>新增的li</li>"); // 2.添加节点 // $("ul").prepend($li); $li.prependTo("ul"); 外部插入 after(content|fn) insertAfter(content) 将元素添加到指定元素外部的后面 // 1.创建一个节点 var $li = $("<li>新增的li</li>"); // 2.添加节点 // $("ul").after($li); $li.insertAfter("ul"); before(content|fn) insertBefore(content) 将元素添加到指定元素外部的前面 // 1.创建一个节点 var $li = $("<li>新增的li</li>"); // 2.添加节点 // $("ul").before($li); $li.insertBefore("ul"); 删除节点 empty() 删除指定元素的内容和子元素, 指定元素自身不会被删除 $("div").empty(); remove([expr]) 删除指定元素 // 删除所有div $("div").remove(); // 删除div中id是box1的那个div $("div").remove("#box1"); detach([expr]) // 删除所有div $("div").detach(); // 删除div中id是box1的那个div $("div").detach("#box1"); remove和detach区别 remove删除元素后,元素上的事件会被移出 detach删除元素后,元素上的事件会被保留 $("button").click(function () { // $("div").remove(); // $("div").empty(); // $("li").remove(".item"); // 利用remove删除之后再重新添加,原有的事件无法响应 // var $div = $("div").remove(); // 利用detach删除之后再重新添加,原有事件可以响应 var $div = $("div").detach(); // console.log($div); // 将删除的返回值重新添加到body上 $("body").append($div); }); $("div").click(function () { alert("div被点击了"); }); 替换节点 replaceWith(content|fn) 将所有匹配的元素替换成指定的HTML或DOM元素 replaceWith参数可以是一个DOM元素 replaceWith参数也可以是一个代码片段 replaceAll(selector) 用匹配的元素替换掉所有 selector匹配到的元素 // 编写jQuery相关代码 $("button").click(function () { // 创建一个新的节点 var $item = $("<h6>我是标题6</h6>"); // 利用新的节点替换旧的节点 // $("h1").replaceWith($item); $item.replaceAll("h1"); }); 复制节点 clone([Even[,deepEven]]) 复制一个节点 浅复制不会复制节点的事件 深复制会复制节点的事件 $(function () { // clone([Even[,deepEven]]) $("button").eq(0).click(function () { // 1.浅复制一个元素 var $li = $("li:first").clone(false); // 2.将复制的元素添加到ul中 $("ul").append($li); // 点击li无法响应事件 }); $("button").eq(1).click(function () { // 1.深复制一个元素 var $li = $("li:first").clone(true); // 2.将复制的元素添加到ul中 $("ul").append($li); // 点击li可以响应事件 }); $("li").click(function () { alert($(this).html()); }); }); 包裹节点 都讲了这么多了, 骚年动动手, 查阅下文档, 尝试下自学这几个方法 编程不是死记硬背, 是学会找到解决问题的思路和自学新知识的方法 节点操作练习
显示、隐藏动画 show([s,[e],[fn]]) 显示动画 内部实现原理根据当前操作的元素是块级还是行内决定, 块级内部调用display:block;,行内内部调用display:inline; // 编写jQuery相关代码 $("button").eq(0).click(function () { // $("div").css("display", "block"); // 注意: 这里的时间是毫秒 $("div").show(1000, function () { // 作用: 动画执行完毕之后调用 alert("显示动画执行完毕"); }); }); hide([s,[e],[fn]]) 隐藏动画 $("button").eq(1).click(function () { // $("div").css("display", "none"); $("div").hide(1000, function () { alert("隐藏动画执行完毕"); }); }); toggle([spe],[eas],[fn]) 切换动画(显示变隐藏,隐藏变显示) $("button").eq(2).click(function () { $("div").toggle(1000, function () { alert("切换动画执行完毕"); }); }); 注意事项: show(1000, function () {};) 第一个参数单位是毫秒, 1000毫秒等于1秒 默认的动画时长是400毫秒 除了指定毫秒以外还可以指定三个预设参数 slow、normal、fast slow本质是600毫秒 normal本质是400毫秒 fast本质是200毫秒 其它两个方法同理可证 展开、收起动画 参数、注意事项和显示隐藏动画一模一样, 只不过动画效果不一样而已 slideDown([s],[e],[fn]) 展开动画 $("button").eq(0).click(function () { $("div").slideDown(1000, function () { alert("展开完毕"); }); }); slideUp([s,[e],[fn]]) 收起动画 $("button").eq(1).click(function () { $("div").slideUp(1000, function () { alert("收起完毕"); }); }); slideToggle([s],[e],[fn]) 切换动画(展开变收起,收起变展开) $("button").eq(2).click(function () { $("div").slideToggle(1000, function () { alert("收起完毕"); }); }); 淡入、淡出动画 参数、注意事项和显示隐藏动画一模一样, 只不过动画效果不一样而已 fadeIn([s],[e],[fn]) 淡入动画 $("button").eq(0).click(function () { $("div").fadeIn(1000, function () { alert("淡入完毕"); }); }); fadeOut([s],[e],[fn]) 淡出动画 $("button").eq(1).click(function () { $("div").fadeOut(1000, function () { alert("淡出完毕"); }); }); fadeToggle([s,[e],[fn]]) 切换动画(显示变淡出,不显示变淡入) $("button").eq(2).click(function () { $("div").fadeToggle(1000, function () { alert("切换完毕"); }); }); fadeTo([[s],o,[e],[fn]]) 淡入到指定透明度动画 可以通过第二个参数,淡入到指定的透明度(取值范围0~1) $("button").eq(3).click(function () { $("div").fadeTo(1000, 0.2, function () { alert("淡入完毕"); }) }); 自定义动画 有时候jQuery中提供的集中简单的固定动画无法满足我们的需求, 所以jQuery还提供了一个自定义动画方法来满足我们复杂多变的需求 animate(p,[s],[e],[fn]) /* 第一个参数: 接收一个对象, 可以在对象中修改属性 第二个参数: 指定动画时长 第三个参数: 指定动画节奏, 默认就是swing 第四个参数: 动画执行完毕之后的回调函数 */ $(".two").animate({ marginLeft: 500 }, 5000, "linear", function () { // alert("自定义动画执行完毕"); }); 每次开始运动都必须是初始位置或者初始状态,如果想在上一次位置或者状态下再次进行动画可以使用累加动画 $("button").eq(1).click(function () { $(".one").animate({ width: "+=100" }, 1000, function () { alert("自定义动画执行完毕"); }); }); 同时操作多个属性,自定义动画会执行同步动画,多个被操作的属性一起执行动画 $(".one").animate({ width: 500, height: 500 }, 1000, function () { alert("自定义动画执行完毕"); }); 动画队列 多个动画方法链式编程,会等到前面的动画执行完毕再依次执行后续动画 $("div").slideDown(1000).slideUp(1000).show(1000); $(".one").slideDown(1000,function () { $(".one").slideUp(1000, function () { $(".one").show(1000); }); }); 但是如果后面紧跟一个非动画方法则会被立即执行 // 立刻变为黄色,然后再执行动画 $(".one").slideDown(1000).slideUp(1000).show(1000).css("background", "yellow"); 如果想颜色再动画执行完毕之后设置, 1.使用回调 2.使用动画队列 $(".one").slideDown(1000,function () { $(".one").slideUp(1000, function () { $(".one").show(1000, function () { $(".one").css("background", "yellow") }); }); }); $(".one").slideDown(1000).slideUp(1000).show(1000).queue(function () { $(".one").css("background", "yellow") }); 注意点: 动画队列方法queue()后面不能继续直接添加queue() 如果想继续添加必须在上一个queue()方法中next()方法 $(".one").slideDown(1000).slideUp(1000).show(1000).queue(function (next) { $(".one").css("background", "yellow"); next(); // 关键点 }).queue(function () { $(".one").css("width", "500px") }); 动画相关方法 delay(d,[q]) 设置动画延迟时长 $(".one").animate({ width: 500 // height: 500 }, 1000).delay(2000).animate({ height: 500 }, 1000); stop([c],[j]) 停止指定元素上正在执行的动画 // 立即停止当前动画, 继续执行后续的动画 // $("div").stop(); // $("div").stop(false); // $("div").stop(false, false); // 立即停止当前和后续所有的动画 // $("div").stop(true); // $("div").stop(true, false); // 立即完成当前的, 继续执行后续动画 // $("div").stop(false, true); // 立即完成当前的, 并且停止后续所有的 $("div").stop(true, true);
事件绑定 jQuery中事件绑定有两种方式 eventName(function(){}) 绑定对应事件名的监听, 例如:$('#div').click(function(){}); on(eventName, funcion(){}) 通用的绑定事件监听, 例如:$('#div').on('click', function(){}); 优缺点: eventName: 编码方便, 但有的事件监听不支持 on: 编码不方便, 但更通用 企业开发中如何选择? 能用eventName就用eventName, 不能用eventName就用on 示例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>13-jQuery事件绑定和解绑</title> <style> *{ margin: 0; padding: 0; } .father{ width: 200px; height: 200px; background: red; overflow: hidden; } .son{ width: 100px; height: 100px; background: blue; margin-top: 50px; margin-left: 50px; } </style> <script src="../day01/代码/js/jquery-1.12.4.js"></script> <script> $(function () { /* // 1.通过eventName绑定事件 $(".son").click(function () { alert("son"); }); // 2.通过on绑定事件 $(".son").on("click", function () { alert("son"); }); */ // 2.可以多次添加相同类型的监听,后面添加不会覆盖前面添加 function test1() { alert("son1"); } function test2() { alert("son2"); } function test3() { alert("son3"); } $(".son").click(test1); $(".son").click(test2); $(".son").on("mouseleave", test3); }); </script> </head> <body> <div class="father"> <div class="son"></div> </div> </body> </html> 事件解绑 jQuery中可以通过off(eventName,function);解绑事件 示例: <script> $(function () { function test1() { alert("son1"); } function test2() { alert("son2"); } function test3() { alert("son3"); } $(".son").click(test1); $(".son").click(test2); $(".son").on("mouseleave", test3); // 1.1不传入任何参数,移除所有事件 // $(".son").off(); // 1.2传入一个参数,移除指定事件 // $(".son").off("click"); // 1.3传入两个参数,移除指定事件中的指定回调 $(".son").off("click", test1); }); </script> 获取事件的坐标 当事件被触发时,系统会将事件对象(event)传递给回调函数,通过event对象我们就能获取时间的坐标 获取事件坐标有三种方式 event.offsetX, event.offsetY 相对于事件元素左上角 event.pageX, event.pageY 相对于页面的左上角 event.clientX, event.clientY 相对于视口的左上角 event.page和event.client区别 网页是可以滚动的,而视口是固定的 所以想获取距离可视区域坐标通过event.client 想获取距离网页左上角的坐标通过event.client 示例代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>13-jQuery事件绑定和解绑</title> <style> *{ margin: 0; padding: 0; } .father{ width: 200px; height: 200px; background: red; overflow: hidden; } .son{ width: 100px; height: 100px; background: blue; margin-top: 50px; margin-left: 50px; } </style> <script src="../day01/代码/js/jquery-1.12.4.js"></script> <script> $(function () { // 获取事件的坐标 $(".son").click(function (event) { // 获取相对于事件元素左上角坐标 console.log(event.offsetX, event.offsetY); // 获取相对于视口左上角坐标 console.log(event.clientX, event.clientY); // 获取相对于页面左上角坐标 console.log(event.pageX, event.pageY); }); }); </script> </head> <body> <div class="father"> <div class="son"></div> </div> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> </body> </html> 阻止事件冒泡 什么是事件冒泡? 事件冒泡是从目标元素逐级向上传播到根节点的过程 小明告诉爸爸他有一个女票,爸爸告诉爷爷孙子有一个女票,一级级向上传递就是事件冒泡 如何阻止事件冒泡? 多数情况下,我们希望在触发一个元素的事件处理程序时,不影响它的父元素, 此时便可以使用停止事件冒泡 <script> $(function () { $(".son").click(function (event) { console.log(".son"); // 在子元素中停止事件冒泡,时间不会继续向上传播,所以父元素click方法不会被触发 event.stopPropagation(); }); $(".father").click(function () { console.log(".father"); }); }); </script> 阻止事件默认行为 什么是默认行为? 网页中的元素有自己的默认行为,例如单击超链接后会跳转,点击提交表单按钮会提交 如何阻止事件默认行为? 可以使用event.preventDefault();方法阻止事件默认行为方法 <script> $(function () { $("a").click(function (event) { var str = $("a").attr("href"); // 如果超链接是百度就不跳转 if(str.indexOf("baidu") > 0){ // 阻止默认行为 event.preventDefault(); } }); }); </script> <script> $(function () { $("form").submit(function () { var userName = $("input[type='text']").val().length > 0; var password = $("input[type='password']").val().length > 0; if(!userName && !password){ event.preventDefault(); } }); }); </script> 自动触发事件 什么是自动触发事件? 通过代码控制事件, 不用人为点击/移入/移除等事件就能被触发 自动触发事件方式 $("selector").trigger("eventName"); 触发事件的同时会触发事件冒泡 触发事件的同时会触发事件默认行为 $("selector").triggerHandler("eventName"); 触发事件的同时不会触发事件冒泡 触发事件的同时不会触发事件默认行为 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>13-自动触发事件</title> <style> *{ margin: 0; padding: 0; } .father{ width: 200px; height: 200px; background: red; } .son{ width: 100px; height: 100px; background: blue; } </style> <script src="../day01/代码/js/jquery-1.12.4.js"></script> <script> $(function () { /* $(".son").click(function () { alert("son"); }); $(".father").click(function () { alert("father"); }); // trigger会触发事件冒泡 // $(".father").trigger("click"); // $(".son").trigger("click"); // triggerHandler不会触发事件冒泡 // $(".father").triggerHandler("click"); // $(".son").triggerHandler("click"); */ $("input[type='submit']").click(function () { alert("点击了A标签"); }); // trigger会触发系统默认事件 // $("input[type='submit']").trigger("click"); // triggerHandler不会触发系统默认事件 $("input[type='submit']").triggerHandler("click"); }); </script> </head> <body> <div class="father"> <div class="son"></div> </div> <form action="http://www.baidu.com"> <input type="text"> <input type="password"> <input type="submit" name="sub" value="提交"> </form> </body> </html> 事件命名空间和自定义事件 什么是自定义事件? 自定义事件就是自己虾XX起一个不存在的事件名称来注册事件, 然后通过这个名称还能触发对应的方法执行, 这就是传说中的自定义事件 自定义事件的前提条件 1.事件必须是通过on绑定的 2.事件必须通过trigger来触发 因为trigger方法可以自动触发对应名称的事件,所以只要事件的名称和传递给trigger的名称一致就能执行对应的事件方法 <script> $(function () { $(".father").on("njClick", function () { alert("njClick"); }); $(".father").trigger("njClick"); }); </script> 什么是事件命名空间? 众所周知一个元素可以绑定多个相同类型的事件.企业多人协同开发中,如果多人同时给某一个元素绑定了相同类型的事件,但是事件处理的方式不同,就可能引发事件混乱 为了解决这个问题jQuery提出了事件命名空间的概念 事件命名空间主要用于区分相同类型的事件,区分不同前提条件下到底应该触发哪个人编写的事件 格式: "eventName.命名空间" 添加事件命名空间的前提条件 1.事件是通过on来绑定的 2.通过trigger触发事件 注意点(面试题!!!面试题!!!面试题!!!): 不带命名空间事件被trigger调用,会触发带命名空间事件 带命名空间事件被trigger调用,只会触发带命名空间事件 下级不带命名空间事件被trigger调用,会冒泡触发上级不带命名空间和带命名空间事件 下级带命名空间事件被trigger调用,不会触发上级不带命名空间事件 下级带命名空间事件被trigger调用,会触发上级带命名空间事件 示例: <script> $(function () { // 给父元素添加不带命名空间事件 $(".father").on("click", function () { alert("father"); }); // 给父元素添加带命名空间事件 $(".father").on("click.66", function () { alert("66 - father"); }); $(".son").on("click.nj", function () { alert("nj - 向左走"); }); $(".son").on("click.66", function () { alert("66 - 向右走"); }); // 会同时触发NJ和66编写的click事件 // 事件会冒泡到不带命名空间上级元素和带相同命名空间的上级元素 // $(".son").trigger("click"); // 只会触发NJ编写的click事件 // 事件不会冒泡到不带命名空间上级元素 // $(".son").trigger("click.nj"); // 只会触发66编写的click事件 // 事件只会冒泡到带相同命名空间的上级元素 $(".son").trigger("click.66"); }); </script> 事件委托 什么是事件委托? 例如: 张三在寝室不想去食堂吃饭,那么张三可以"委托"李四帮忙带一份饭 例如: 张三先找房,但是对要找房的地点又不熟悉,那么张三可以"委托"中介帮忙找房 所以得出结论: 事件委托就是请其他人帮忙做我们想做的事 做完之后最终的结果还是会反馈到我们这里 js中事件委托的好处 减少监听数量 添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间 每个监听的函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差 ... ... 新增元素自动有事件响应处理 默认情况下新增的元素无法响应新增前添加的事件 jQuery中如何添加事件委托 添加前 $("li").click隐式迭代给界面上所有li都添加了click事件(监听数量众多) 通过$("ul").append新添加的li无法影响click事件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>18-jQuery事件委托</title> <script src="../day01/代码/js/jquery-1.12.4.js"></script> <script> $(function () { // 1.监听li点击事件 $("li").click(function () { // 弹出当前点击行内容 alert($(this).html()); }); // 2.监听新增按钮点击 var count = 0; $("button").eq(0).click(function () { count++; // 新增一行内容 $("ul").append("<li>我是新增内容"+count+"</li>") }); }); </script> </head> <body> <ul> <li>我是第1行</li> <li>我是第2行</li> <li>我是第3行</li> </ul> <button>新增一行</button> <button>移除事件委托</button> </body> </html> 添加后 格式:$(parentSelector).delegate(childrenSelector, eventName, callback) $("ul").delegate隐式迭代所有ul添加事件(相比开始迭代li,必然ul的个数会少很多) 当事件被触发时,系统会自动动态查找当前是哪个li触发了事件,所以新增的li也能响应到事件 <script> $(function () { // 1.委托ul监听li的点击事件 $("ul").delegate("li","click",function () { // 前面我们说过事件委托就是让别人帮忙做事,但最终的结果还是会返回到我们手里,所以这里的this是触发事件的li // 这里的this之所以是触发事件的li,本质是因为"事件冒泡", 触发事件的li向上传递到ul,触发了click事件. // console.log(this); // 弹出当前点击行内容 alert($(this).html()); }); // 2.监听新增按钮点击 var count = 0; $("button").eq(0).click(function () { count++; // 新增一行内容 $("ul").append("<li>我是新增内容"+count+"</li>") }); }); </script> 移入移出事件 mouseenter和mouseleave 移动到子元素不会触发事件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>14-jQuery移入移除事件</title> <style> *{ margin: 0; padding: 0; } .father{ width: 200px; height: 200px; background-color: red; } .son{ width: 100px; height: 100px; background-color: blue; } </style> <script src="../code/js/jquery-1.12.4.js"></script> <script> $(function () { // 移动到子元素不会触发事件 // 2.1移入事件 $('.father').mouseenter(function () { console.log('mouseenter'); }); // 2.2移除事件 $('.father').mouseleave(function () { console.log('mouseleave'); }); }); </script> </head> <body> <div class="father"> <div class="son"></div> </div> </body> </html> mouseover和mouseout 移动到子元素会触发事件 <script> $(function () { // 2.1移入事件 $('.father').mouseover(function () { console.log('mouseover') ; }); // 2.2移除事件 $('.father').mouseout(function () { console.log('mouseout') ; }); }); </script> hover 内容监听移入和移出 内部实现就是调用mouseenter和mouseleave <script> $(function () { /* // 传入两个回调函数,一个监听移入,一个监听移出 $(".father").hover(function () { console.log("mouseenter"); }, function () { console.log("mouseleave"); }); */ // 如果只传入一个方式,那么这个方式既监听移入也监听移出 $(".father").hover(function () { console.log("移入移除"); }); }); </script> 移入移出练习 鼠标移动到哪一行,哪一行展开效果 <script> $(function () { // 1.监听li标签移入事件 $("li").mouseenter(function () { console.log("mouseenter"); // 当移入时候给当天li标签加上current类 $(this).addClass("current"); }); // 2.监听li标签移出事件 $("li").mouseleave(function () { console.log("mouseleave"); // 当移入时候移除原有li标签的current类 $(this).removeClass("current"); }); }); </script> 鼠标移动到哪个选项卡就显示哪个选项卡对应的图片 <script> $(function () { // 1.监听tab的移入事件 $("li").mouseenter(function () { // 2.修改当前移入tab背景 $(this).addClass("cur"); // 3.移除其它tab背景 $(this).siblings().removeClass("cur"); // 4.获取当前移入tab索引 var $idx = $(this).index(); // 5.找到对应索引的img var $img = $("img").eq($idx); // 6.让对应索引的图片显示 $img.addClass("show"); // 7.让其它所有图片隐藏 $img.siblings().removeClass("show"); }); }); </script> 鼠标移入到哪个序号就显示哪个序号对应图片 <script> $(function () { // 1.监听索引移入事件 $(".index>li").mouseenter(function () { // 2.给移入的索引添加背景,其它索引删除背景 $(this).addClass("cur").siblings().removeClass("cur"); // 3.找到当前移入索引的序号 var $idx = $(this).index(); // 4.找到序号对应的图片 var $li = $(".content>li").eq($idx); // 5.显示对应图片,并且让其它图片小事 $li.addClass("show").siblings().removeClass("show"); }); }); </script>
jQuery操作CSS样式 css(name|pro|[,val|fn])方法 用于设置或获取元素CSS样式 格式1:DOM元素.css("样式名称", "值"); 格式2:DOM元素.css({"样式名称1":"值1","样式名称2":"值2"}); <script> $(function () { $("button").click(function () { // 1.单个样式设置 // $("div").css("width", "100px"); // $("div").css("height", "100px"); // $("div").css("background", "red"); // 2.链式设置样式 // $("div").css("width", "100px").css("height", "100px").css("background", "red"); // 3.传入对象一次性设置样式 $("div").css({ "width":"100px", "height":"100px", "background":"blue" }); // 4.获取指定样式的值 console.log($("div").css("width")); }); }); </script> jQuery操作元素尺寸 width([val|fn])方法 设置或获取元素宽度(相当于获取width属性值) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>11-jQuery操作位置和尺寸</title> <style> *{ margin: 0; padding: 0; } .father{ width: 250px; height: 250px; background-color: red; margin-left: 50px; position: relative; } .son{ width: 100px; height: 100px; background-color: blue; position: absolute; left: 50px; top: 50px; } </style> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { $("button").eq(0).click(function () { // 1.获取元素宽度(不包括padding和border) // alert($('.son').width()); }); $("button").eq(1).click(function () { // 2.设置元素宽度(不包括padding和border) // $(".son").width("50px"); }); }); </script> </head> <body> <div class="father"> <div class="son"></div> </div> <button>获取</button> <button>设置</button> </body> </html> height([val|fn])方法 设置或获取元素宽度(相当于获取height属性值) 用上面按钮代码自己写,工作后都得靠自己,多锻炼自学能力(如何查看文档,如何编写测试案例等) innerHeight()/innerWidth() 用上面按钮代码自己写,工作后都得靠自己,多锻炼自学能力(如何查看文档,如何编写测试案例等) outerHeight/outerWidth() 用上面按钮代码自己写,工作后都得靠自己,多锻炼自学能力(如何查看文档,如何编写测试案例等) jQuery操作元素位置 offset([coordinates]) 获取或设置元素相对窗口的偏移位 <script> $(function () { $("button").eq(0).click(function () { // 1.获取距离窗口的偏移位(从border开始) alert($('.son').offset().left); // 100 }); $("button").eq(1).click(function () { // 2.设置距离窗口的偏移位 $('.son').offset({left:10, top:10}); }); }); </script> position() 获取相对于它最近的具有相对位置(position:relative或position:absolute)的父级元素的距离 <script> $(function () { $("button").eq(0).click(function () { // 1.获取匹配元素相对父元素的偏移 alert($('.son').position().left);// 50 }); $("button").eq(1).click(function () { // 2.无效,不能设置相对定位元素的偏移位 $('.son').position({left:10, top:10}) }); }); </script> scrollTop([val]) 设置或获取匹配元素相对滚动条顶部的偏移。 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>12-jQuery操作位置</title> <style> *{ margin: 0; padding: 0; } .scroll{ margin-top: 100px; margin-left: 100px; width: 100px; height: 200px; border: 1px solid #000; overflow: auto; } </style> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { $("button").eq(0).click(function () { // 7.获取匹配元素相对滚动条顶部的偏移 // alert($('.scroll').scrollTop()); // alert($('html').scrollTop()); // 兼容所有浏览器写法 alert($('html').scrollTop()+$('body').scrollTop()); }); $("button").eq(1).click(function () { // 8.设置匹配元素相对滚动条顶部的偏移 // $('.scroll').scrollTop(100); // $('html').scrollTop(100); // 兼容所有浏览器写法 $('html,body').scrollTop(100); }); }); </script> </head> <body> <div class="scroll"> 我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字我是文字 </div> <button>获取</button> <button>设置</button> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> <br> </body> </html> scrollLeft([val]) 用上面按钮代码自己写,工作后都得靠自己,多锻炼自学能力(如何查看文档,如何编写测试案例等)
属性和属性节点 什么是属性? 属性就是对象身上的变量 只要对象身上都可以添加属性(无论是自定义对象,还是DOM对象) <script> // 1.自定义一个对象 var obj = {}; console.log(obj); // 2.动态给自定义对象添加属性 obj.name = "lnj"; // name就是对象obj的一个属性 obj.age = 33; // age就是对象obj的一个属性 console.log(obj); </script> image.png 什么是属性节点? 在html中编写的所有标签,里面的属性都是属性节点 <span class = 'box' nj = '666'></span> // 这里的class和nj就是属性节点 image.png 如果操作属性? 添加或修改属性(没有就会添加,有就会修改) 对象.属性名称 = 值; 对象["属性名称"] = 值; 获取属性 对象.属性名称 对象["属性名称"] 如何操作属性节点? 获取属性节点 DOM对象.getAttribute("属性节点名称") 设置属性节点 DOM对象.setAttribute("属性节点名称", "值"); jQuery中的attr和prop方法 attr(name|pro|key,val|fn)方法 用于设置或获取属性节点的值 <script> $(function () { // 1.获取指定属性节点值 var $res = $(".span1").attr("nj"); console.log($res); // 2.设置属性节点 $(".span1").attr("nj", "666"); $(".span2").attr("id", "box1 box2"); // 3.注意点: // 3.1.获取属性节点时,只会获取找到所有元素中第一个元素的属性节点 $res = $("span").attr("class"); console.log($res); $("span").attr("class", "lnj"); }); </script> removeAttr(name)方法 用于删除指定属性节点 <script> $(function () { // 1.设置属性节点时,会给所有找到元素设置属性节点 $("span").attr("test", "jonathan"); // 2.删除属性节点时,会删除所有找到元素的属性节点 $("span").removeAttr("test"); }); </script> prop(n|p|k,v|f)方法 用于设置或者获取元素的属性值 <script> $(function () { // 1.设置属性 // 1.1.设置属性时,会设置所有找到元素的属性 $("span").prop("demo", "lnj"); // 2.获取属性 // 2.1.获取属性时,只会获取找到第一个元素的属性 console.log($("span").prop("demo")); }); </script> removeProp(name)方法 <script> $(function () { // 删除所有找到元素的demo属性 $("span").removeProp("demo"); }); </script> attr方法和prop方法区别 既然所有的DOM对象,都有一个attributes属性,而prop可以操作属性,所以也可以操作属性节点 官方推荐在操作属性节点时,具有 true 和 false 两个属性的属性节点,如 checked, selected 或者 disabled 使用prop(),其他的使用 attr() 因为如果具有 true 和 false 两个属性的属性节点,如果没有编写默认attr返回undefined,而prop返回false <script> $(function () { // 1.可以通过prop获取属性节点 console.log($("input").prop("class")); // 2.可以通过prop设置属性节点 $("input").prop("class", "tag"); // 3.如果没有默认值,那么attr获取返回undefined // console.log($("input[type=checkbox]").attr("checked")); // 4.如果没有默认值,那么prop获取返回false console.log($("input[type=checkbox]").prop("checked")); // 5.通过attr设置选中 // $("input[type=checkbox]").attr("checked", true); // 6.通过prop设置选中 $("input[type=checkbox]").prop("checked", true) }); </script> jQuery增删Class jQuery CSS类相关方法都是用于操作DOM对象的class属性节点的值 addClass(class|fn) 给元素添加一个或多个类 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>08-jQueryCSS类</title> <style> .class1{ width: 200px; height: 200px; background-color: red; } .class2{ border: 5px solid #000; } </style> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { $("button").eq(0).click(function () { // 1.添加一个类 // $("div").addClass("class1"); // 2.再添加一个类 // $("div").addClass("class2"); // 3.一次性添加多个类(用空格隔开) $("div").addClass("class1 class2"); }); }); </script> </head> <body> <button>添加</button> <button>删除</button> <button>切换</button> <div></div> </body> </html> removeClass([class|fn]) 删除元素的一个或多个类 <script> $(function () { $("button").eq(1).click(function () { // 4.删除一个类 // $("div").removeClass("class2"); // 5.再删除一个类 // $("div").removeClass("class1"); // 6.一次性删除多个类(用空格隔开) $("div").removeClass("class1 class2"); }); }); </script> toggleClass(class|fn[,sw]) 添加或删除一个类(存在就删除不存在就添加) <script> $(function () { $("button").eq(2).click(function () { // 7.切换一个类 // $("div").toggleClass("class2"); // 8.切换多个类 $("div").toggleClass("class1 class2"); }); }); </script> jQuery代码/文本/值 html([val|fn]) 添加或获取元素中的HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>09-jQuery代码文本值</title> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { var $btns = $("button"); var $div = $("div"); $btns.eq(0).click(function () { // 1.添加html, 相当于innerHTML // $div.html("<p>我是p标签</p>"); // $div.html("<p><span>我是span标签</span></p>"); $div.html("我是文本"); }); $btns.eq(1).click(function () { // 2.获取html console.log($div.html()); }); }); </script> </head> <body> <button>添加html</button> <button>获取html</button> <button>添加文本</button> <button>获取文本</button> <div></div> </body> </html> text([val|fn]) 添加或获取元素中的文本 text方法能做的html方法都能做,所以一般使用html方法即可 <script> $(function () { $btns.eq(2).click(function () { // 3.添加文本, 相当于innerText // 如下内容不会被转换为标签 // $div.text('<p>我是段落</p>'); $div.text('我是文本'); }); $btns.eq(3).click(function () { // 4.获取文本 console.log($div.text()); }); </script> val([val|fn|arr]) 添加或获取元素value属性的值 <script> $(function () { $btns.eq(4).click(function () { // 4.添加value值 $("input").val("我是一个输入框"); }); $btns.eq(5).click(function () { // 4.获取value值 console.log($("input").val()); }); }); </script>
基础选择器 视频参考第十章-CSS选择器 选择器 名称 描述 返回 示例 #id id选择器 根据给定的id匹配一个元素 单个元素 $("#box");选取id为box元素 .class 类选择器 根据给定的类名匹配元素 集合元素 $(".box");选取所有类名为box元素 element 元素选择器 根据给定的元素名称匹配元素 集合元素 $("p");选取所有<p>元素 * 通配符选择器 匹配所有元素 集合元素 $("*");选取所有元素 selector1,selector2,selectorN 并集选择器 将所有选择器匹配到的元素合并后一起返回 集合元素 $("div,p,.box");选取所有<div>元素,所有<p>元素和所有类名为box元素 层次选择器 视频参考第十章-CSS选择器 选择器 名称 描述 返回 示例 $("ancestor descendant") 后代选择器 选取ancestor元素的所有descendant后代标签(不光是儿子,包括孙子/重孙子等) 集合元素 $("div span");选取<div>元素里所有的<span>元素 $("parent > child") 子元素选择器 找到选取parent 元素中所有直接子元素child(只有儿子,不包括孙子/重孙子等) 集合元素 $("div>span");选取<div>元素下元素名称是<span>的子元素 $("prev + next") 相邻兄弟选择器 选取prev元素后面紧跟的那个next元素 集合元素 $(".one+div");选取类名为one的下一个同级的<div>元素 $("prev ~ siblings") 通用兄弟选择器 选取prev元素后面的所有next元素 集合元素 $("#two~div");选取id名为two元素后面所有同级的<div>元素 序选择器 视频参考第十章-CSS选择器 如上内容不再一一赘述,观看第十章-CSS选择器,使用时查询文档即可 做开发是脑力活,我们需要掌握的是解决问题的方法,而不是死记硬背 属性选择器 视频参考第十章-CSS选择器 如上内容不再一一赘述,观看第十章-CSS选择器,使用时查询文档即可 做开发是脑力活,我们需要掌握的是解决问题的方法,而不是死记硬背 内容过滤选择器 选择器 描述 返回 :empty 选取不包含子元素或文本为空的元素 集合元素 :parent 选取含有子元素或文本的元素 集合元素 :contains(text) 选取含有文本内容为text的元素 集合元素 :has(selector) 选取含有选择器所匹配的元素的元素 集合元素 :empty <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>05-jQuery选择器</title> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { // 找到所有div中不包含文本内容或子元素的元素 var $res = $("div:empty"); console.log($res.length); // 找到1个元素 $res.each(function (idx,ele) { console.log(idx, ele); // one }); }); </script> </head> <body> <div class="one"></div> <div class="two">zs</div><!--包含内容不会被找到--> <div class="three"><!--包含子元素不会被找到--> <span>lnj</span> </div> <span class="five"></span><!--不是指定元素不会被找到--> </body> </html> :parent <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>05-jQuery选择器</title> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { // 找到所有div中包含文本内容或子元素的元素 var $res = $("div:parent"); console.log($res.length); $res.each(function (idx, ele) { console.log(idx, ele); }); }); </script> </head> <body> <div class="one"></div> <div class="two">zs</div><!--有文本内容会被找到--> <div class="three"><!--有子元素会被找到--> <span>lnj</span> </div> <span class="five"></span> </body> </html> :contains(text) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>05-jQuery选择器</title> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { // 找到所有div中包含文本内容为lnj的元素 var $res = $("div:contains('lnj')"); console.log($res.length);// 找到2个元素 $res.each(function (idx, ele) { console.log(idx, ele);// one,three,four }) }); </script> </head> <body> <div class="one">lnj</div> <div class="two">zs</div> <div class="three">lnj</div> <div class="four"><!--子元素中包含该文本也会被找到--> <span>lnj</span> </div> <span class="five"></span> </body> </html> :has(selector) 和:parent区别,parent只要有子元素就会被找到,:has(selector)不仅要有子元素,而且子元素还必须满足我们的条件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>05-jQuery选择器</title> <script src="代码/js/jquery-1.12.4.js"></script> <script> $(function () { // 找到所有div中包含后代元素为span的元素 var $res = $("div:has('span')"); console.log($res.length); $res.each(function (idx, ele) { console.log(idx, ele); }); }); </script> </head> <body> <div class="one"> <!--后代中包含span元素会被找到--> <span>jjj</span> </div> <div class="two"><!--后代中不包含span元素不会被找到--> <p>zs</p> </div> <div class="three"><!--后代中包含span元素会被找到--> <p> <span>lnj</span> </p> </div> </body> </html>
jQuery核心函数 从jQuery文档中可以看出,jQuery核心函数一共3大类4小类 jQuery(callback) 当DOM加载完成后执行传入的回调函数 <script> $(function () { alert("123"); }); </script> jQuery([sel,[context]]) 接收一个包含 CSS 选择器的字符串,然后用这个字符串去匹配一组元素,并包装成jQuery对象 <script> $(function () { // 利用jquery获取所有div,得到的是一个jQuery对象 var $box = $("div"); console.log($box); // 利用js原生语法获取所有div,得到的是一个js对象 var box = document.getElementsByTagName("div"); console.log(box); }); </script> 原生JS对象和jQuery对象相互转换 <script> $(function () { var $box = $("#box"); // $box.text("新的数据"); // jQuery对象不能使用原生js对象的方法 // $box.innerText = "新的数据"; // 将jQuery对象转换为原生js对象 // 注意: 不是eq(0),eq函数返回的是jQuery类型对象,get函数返回的是原生类型对象 // var box = $box.get(0); var box = $box[0]; box.innerText = "新的数据"; var box2 = document.getElementById("box"); // 原生js对象不能使用jQuery对象的方法 // box2.text("新的数据2"); // 原生js对象只能使用原生的js方法 // box2.innerText = "新的数据2"; // 将原生js对象转换为jQuery对象 var $box2 = $(box); $box2.text("新的数据2"); }); </script> Tips:为了方便开发者之间沟通和阅读,一般情况下所有jQuery操作相关的变量前面加上$ jQuery(html,[ownerDoc]) 根据 HTML 标记字符串,动态创建DOM 元素 <script> $(function () { var $eles = $("<p>我是span</p><u>我是u</u>"); // 无论是jQuery找到的还是创建的,我们最终拿到的永远都是jQuery对象 console.log($eles); // 将创建好的DOM元素添加到body中 $("body").append($eles); }); </script> jQuery对象 jQuery对象的本质是什么? jQuery对象的本质是一个伪数组 var $div = $("div"); console.log($div); var arr = [1, 3, 5]; console.log(arr); 什么是伪数组? 有0到length-1的属性 并且有length属性 var obj = {0:"lnj", 1:"33", 2:"male", length: 3} jQuery静态方法 什么是静态方法? 静态方法对应的是对象方法,对象方法用实例对象调用,而静态方法用类名调用 <script> window.onload = function () { function AClass(){} AClass.staticMethof = function(){ alert('静态方法'); } AClass.prototype.instaceMethod = function(){ alert('实例方法'); } //静态方法用类名直接调用 AClass.staticMethof(); //实例方法必须用类的实例对象调用 var instace = new AClass(); instace.instaceMethod(); } </script> jQuery.holdReady(hold) 暂停或者恢复jQuery.ready()事件 传入true或false <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>04-jQuery静态方法</title> <script src="代码/js/jquery-1.12.4.js"></script> <script> // 使用$直接调用,是静态方法 $.holdReady(true); $(function () { $("#first").click(function () { alert("我是你想要的弹窗"); }); }); </script> </head> <body> <button id="first">点击测试弹出</button> <button id="second">解除延迟</button> <script> $("#second").click(function(){ $.holdReady(false); }); </script> </body> </html> $.each(object,[callback]) 遍历对象或数组 优点统一遍历对象和数组的方式 回调参数的顺序更符合我们的思维模式 <script> $(function () { // 3.1遍历数组 var arr = [1, 3, 5, 7, 9]; // 3.1.1通过原生方法遍历数组 // 第一个回调函数参数是遍历到的元素 // 第二个回调函数参数是当前遍历的索引 // 返回值: 没有返回值 var res = arr.forEach(function (ele, idx) { console.log(idx, ele); }); console.log(res); // 3.1.2通过jQuery静态方法遍历数组 // 第一个回调函数参数是当前遍历的索引 // 第二个回调函数参数是遍历到的元素 // 返回值: 被遍历的数组 var $res2 = $.each(arr, function (idx, ele) { console.log(idx, ele); }); console.log($res2); // 3.2遍历对象 var obj = {name: "lnj", age:"33", gender:"male"}; // 3.2.1js对象没有forEach方法,所以通过forin方法遍历对象 for(var key in obj){ console.log(key, obj[key]); } // 3.2.2通过jQuery静态方法遍历对象 $.each(obj,function (key, value) { console.log(key, value); }); }); </script> $.map(arr|obj,callback) 遍历对象或数组,将回调函数的返回值组成一个新的数组返回 $(function () { // 4.1遍历数组 var arr = [1, 3, 5, 7, 9]; // 4.1.1通过原生方法遍历数组 // 第一个回调函数参数是遍历到的元素 // 第二个回调函数参数是当前遍历的索引 // 第三个回调函数参数是当前被遍历的数组 // 返回值: 将回调函数返回值收集起来组成一个新的数组 var res = arr.map(function (ele, idx, arr) { console.log(idx, ele, arr); return ele + idx; }); console.log(res); // 4.1.2通过jQuery静态方法遍历数组 // 第一个回调函数参数是遍历到的元素 // 第二个回调函数参数是当前遍历的索引 // 返回值: 将回调函数返回值收集起来组成一个新的数组 var $res2 = $.map(arr, function (ele,idx) { console.log(idx, ele); return ele + idx; }); console.log($res2); // 4.2遍历对象 var obj = {name: "lnj", age:"33", gender:"male"}; /* obj.map(function (ele, idx, obj) { // 报错,原生JS没有map方法 console.log(idx, ele, obj); }); */ var $res = $.map(obj, function (value, key) { console.log(key, value); return key + value; }); console.log($res); }); $.trim(str) 去掉字符串起始和结尾的空格。 <script> $(function () { var str = " lnj "; console.log("---"+str+"---"); var $res = $.trim(str); console.log("---"+$res+"---"); }); </script> $.isArray(obj) 判断是否是数组 <script> $(function () { // 对象 var obj = {name:"lnj",age: "33", gender:"male"}; // 真数组 var arr = [1, 3, 5, 7, 9]; var $res = $.isArray(obj); console.log($res);// false var $res2 = $.isArray(arr); console.log($res2);// true }); </script> $.isFunction(obj) 判断是否是函数 <script> $(function () { var obj = {name:"lnj",age: "33", gender:"male"}; var arr = [1, 3, 5, 7, 9]; var fn = function () {} var $res = $.isFunction(obj); console.log($res);// false $res = $.isFunction(arr); console.log($res); $res = $.isFunction(fn); console.log($res); // 通过该方法验证了我们前面所说的,jQuery框架本质是一个匿名函数 $res = $.isFunction($); console.log($res); }); </script> $.isWindow(obj) 判断是否是window对象 <script> $(function () { var obj = window; var arr = [1, 3, 5, 7, 9]; var arrlike = {0:"zs", 1:"ls", length:2}; var $res = $.isWindow(obj); console.log($res); // true $res = $.isWindow(arr); console.log($res); // false $res = $.isWindow(arrlike); console.log($res); // false }); </script> 为什么要讲解以上极度简单的工具方法? 江哥提示: 这是为后面放大招做铺垫,一定要认真记住以上方法哦
jQuery入口函数 jQuery与JavaScript加载模式对比 多个window.onload只会执行一次, 后面的会覆盖前面的 <script> window.onload = function () { alert("hello lnj1"); // 不会显示 } window.onload = function () { alert("hello lnj2"); // 会显示 } </script> 多个$(document).ready()会执行多次,后面的不会覆盖前面的 <script> $(document).ready(function () { alert("hello lnj1"); //会显示 }); $(document).ready(function () { alert("hello lnj2"); // 会显示 }); </script> 不会覆盖的本质(了解,后面jQuery原理会详细讲解) jQuery框架本质是一个闭包,每次执行我们都会给ready函数传递一个新的函数,不同函数内部的数据不会相互干扰 <script> // 相当于这样写 var test1 = function () { alert("hello lnj1"); } var test2 = function () { alert("hello lnj2"); } $(document).ready(test1); $(document).ready(test2); </script> <script> var test3 = function () { var abc = "123"; // 因为在同一个函数中,所以456覆盖123 // var abc = "456"; alert(abc); } test3(); var test4 = function () { // 因为在不同函数中,所以不会影响 var abc = "456"; alert(abc); } test4(); </script> window.onload $(document).ready() 执行时机 必须等待网页全部加载完毕(包括 图片等),然后再执行包裹代码 只需要等待网页中的DOM结构 加载完毕,就能执行包裹的代码 执行次数 只能执行一次,如果第二次,那么 第一次的执行会被覆盖 可以执行多次,第N次都不会被上 一次覆盖 简写方案 无 $(function () { }); 为什么我们能访问$符号? 因为$符号jQuery框架对外暴露的一个全局变量 JavaScript中如何定义一个全局变量? 所有全局变量是 window 对象的属性 function test () { var customValue = 998; alert(customValue); // 1.没有如下代码customValue就不是一个全局变量,函数执行完毕之后 // customValue会被自动释放,test函数以外的地方访问不到customValue // 2.加上如下代码之后customValue就会变成一个全局变量,函数执行完毕也不 // 会被释放,test函数以外的地方可以访问customValue // window.customValue = customValue; } test(); alert(customValue); 所以jQuery框架源码实现 window.jQuery = window.$ = jQuery; 所以想要使用jQuery框架只有两种方式,一种是通过$,一种是通过jQuery jQuery入口函数的其它编写方式如下 <script> // 方式一 $(document).ready(function () { alert("hello lnj"); }); // 方式二 $(function () { alert("hello lnj"); }); // 方式三 jQuery(document).ready(function () { alert("hello lnj"); }); // 方式四 jQuery(function () { alert("hello lnj"); }); </script> 解决$符号冲突问题 为什么是window.jQuery = window.$ = jQuery;,而不是window.jQuery = jQuery; jQuery框架之所以提供了jQuery访问还提供$访问,就是为了提升开发者的编码效率 $符号冲突怎么办? 很多js的框架都提供了类似jQuery这样的便捷访问方式,所以很有可能某一天我们在使用多个框架的时,多个框架作者提供的便捷访问方式冲突(A框架通过$访问,B框架也通过$访问) ***释放$使用权 当便捷访问符号发生冲突时,我们可以释放$使用权, 释放之后只能使用jQuery <script> // 在使用jQuery之前指定自定义符号 jQuery.noConflict(); // 使用 jQuery jQuery("div p").hide(); // 使用其他库的 $() $("content").style.display = 'none'; </script> 自定义便捷访问符号 当便捷访问符号发生冲突时,我们可以自定义便捷访问符号 <script> // 在使用jQuery之前指定自定义符号 var nj = jQuery.noConflict(); // 和使用$一样通过自定义符号调用jQuery nj(function () { alert("hello lnj"); }); </script>
课前须知: 学习jQuery前必须先掌握JavaScript jQuery虽然属于前端技术, 但是对于后端人员(诸如Java、PHP等,也需要掌握) jQuery是什么? jQuery是一款优秀的JavaScript库,从命名可以看出jQuery最主要的用途是用来做查询(jQuery=js+Query). 在jQuery官方Logo下方还有一个副标题(write less, do more), 体现了jQuery除了查询以外,还能让我们对HTML文档遍历和操作、事件处理、动画以及Ajax变得更加简单 体验jQuery 原生JS设置背景(先不要求看懂代码,先看看谁更爽) <script> // 查询 var div = document.getElementsByTagName("div"); var one = document.getElementsByClassName("one"); var two = document.getElementById("two"); // 操作css div[0].style.backgroundColor = "red"; one[0].style.backgroundColor = "yellow"; two.style.backgroundColor = "blue"; </script> 使用jQuery设置背景 <script> $(document).ready(function () { // 查询,操作CSS一步到位 $("div").eq(0).css('background', 'red'); $(".one").eq(0).css('background', 'yellow'); $("#two").css('background', 'blue'); }); </script> 为什么要使用jQuery? 强大选择器: 方便快速查找DOM元素 如上面实例所展示一样,通过jQuery查找DOM元素要比原生js快捷很多 jQuery允许开发者使用CSS1-CSS3几乎所有的选择器,以及jQuery独创的选择器 链式调用: 可以通过.不断调用jQuery对象的方法 如上面实例所展示一样,jQuery可以通过.(点).不断调用jQuery对象的方法,而原生JavaScript则不一定 <script> // 1.原生JavaScript var div = document.getElementsByTagName("div"); // 报错,必须分开写 div[0].style.backgroundColor = "red".width = 200+"px"; // div[0].style.width = 200+"px"; // 2.jQuery $(document).ready(function () { // 不报错,后面还可以接着继续写 $("div").eq(1).css('background', 'yellow').css('width', '200px'); }); </script> 隐式遍历(迭代): 一次操作多个元素 <script> // 1.原生JavaScript var div = document.getElementsByTagName("div"); // div.style.backgroundColor = "red";// 无效 for(var i = 0; i<div.length; i++){ div[i].style.backgroundColor = "red"; } // 2.jQuery $(document).ready(function () { // 隐式遍历(迭代)找到的所有div $("div").css('background', 'yellow'); }); </script> 读写合一: 读数据/写数据使用是一个函数 <script> $(document).ready(function () { // 读取数据 var $tx = $("div").eq(0).text(); alert($tx); // 写入数据 $("div").eq(0).text("新的数据"); }); </script> 事件处理 DOM操作(C增U改D删) 样式操作 动画 丰富的插件支持 浏览器兼容(前端开发者痛点) 1.x:兼容ie678,但相对其它版本文件较大,官方只做BUG维护,功能不再新增,最终版本:1.12.4 (2016年5月20日). 2.x:不兼容ie678,相对1.x文件较小,官方只做BUG维护,功能不再新增,最终版本:2.2.4 (2016年5月20日) 3.x:不兼容ie678,只支持最新的浏览器,很多老的jQuery插件不支持这个版本,相对1.x文件较小,提供不包含Ajax/动画API版本。 应该选择几点几版本jQuery? 查看百度网页源码使用1.x 查看腾讯网页源码使用1.x 查看京东网页源码使用1.x 综上所述学习1.x,选择1.x 应该使用开发板还是生产版? 开发板: 所有代码没有经过压缩,体积更大(200-300KB) 生产版:所有代码经过压缩,提及更小(30-40KB) 初学者为了更好的理解jQuery编码时使用开发板,项目上线时为了提升访问速度使用生产版 ... ... 如何使用jQuery? 下载jQuery库 下载地址: http://code.jquery.com/ 引入下载的jQuery库 <head> <meta charset="UTF-8"> <title>01-初识jQuery</title> <script src="code/js/jquery-1.12.4.js"></script> </head> 编写jQuery代码 <script> $(document).ready(function () { // 所有jQuery代码写在这里面 alert("hello LNJ"); }); </script>
for循环基本概念 JavaScript中的for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况,它完全可以代替while语句。 格式: for(①初始化表达式;②循环条件表达式;④循环后操作表达式) { ③执行语句; } for循环执行流程 for循环开始时,会先执行初始化表达式,而且在整个循环过程中只执行一次初始化表达式 接着判断循环条件表达式是否为真,如果条件为真,就会执行循环体中的语句 循环体执行完毕后,接下来会执行循环后的操作表达式 执行完循环后操作表达式, 然后再次判断循环条件表达式是否为真,如果条件为真,就会执行循环体中的语句 重复上述过程,直到条件不成立就结束for循环 for(var i = 0; i < 10; i++){ alert(i); // 输出0-9 } for循环和while循环如何选择? 一般情况下while和for可以互换, 但是如果循环执行完毕之后就不需要使用用于控制循环的增量, 那么用for更好 for循环注意点 for循环中初始化表达式,循环条件表达式,循环后操作表达式都可以不写 循环条件表达式默认为真 for(; ;) 语句相当于while(1), 都是死循环 其它注意点和while一样 循环嵌套 循环语句的循环体中存在其他的循环语句就是循环嵌套 注意: 一般循环嵌套不超过三层 外循环执行的次数 * 内循环执行的次数就是总共执行的次数 格式: while/while嵌套 while/do-while嵌套 while/for嵌套 for/for嵌套(最常用) for/while嵌套 for/do-while嵌套 do-while/do-while嵌套 do-while/while嵌套 do-while/for嵌套 循环嵌套练习 打印好友列表 好友列表1 好友1 好友2 好友3 好友列表2 好友1 好友2 好友3 for (var i = 1; i <= 2; i++) { console.log("好友列表"+ i); for (var j = 1; j <= 3; j++) { console.log(" 好友"+ j); } } for循环嵌套实现:矩形绘制 需求: 在界面上绘制如下图形 **** **** **** // 外循环控制行数 for (var i = 0; i < 3; i++) { // 内循环控制列数 for (var j = 0; j < 4; j++) { window.document.write("*"); } window.document.write("<br/>"); } for循环嵌套实现:三角形绘制 尖尖朝上,改变内循环的条件表达式,让内循环的条件表达式随着外循环的i值变化 需求: 在界面上绘制如下图形 * ** *** **** ***** for(var i = 0; i< 5; i++){ for(var j = 0; j <= i; j++){ window.document.write("*\t"); } window.document.write("<br/>"); } for循环嵌套实现:三角形绘制 尖尖朝下,改变内循环的初始化表达式,让内循环的初始化表达式随着外循环的i值变化 需求: 在界面上绘制如下图形 ***** **** *** ** * for(var i = 0; i< 5; i++){ for(var j = i; j < 5; j++){ window.document.write("*\t"); } window.document.write("<br/>"); } for循环嵌套实现:打印特殊三角形 1 12 123 /* 1.观察发现有很多行很多列--》循环嵌套 2.尖尖朝上,让内循环条件表达式随着外循环i改变 3.发现每次输出的值其实就是 j + 1 的值 */ for (var i = 0; i < 3; i++) { for (var j = 0; j <= i; j++) { window.document.write(j+1+""); } window.document.write("<br/>"); } 1 22 333 /* 1.观察发现有很多行很多列--》循环嵌套 2.尖尖朝上,让内循环条件表达式随着外循环i改变 3.发现每次输出的值其实就是i的值 */ for (var i = 1; i <= 3; i++) { for (var j = 1; j <= i; j++) { window.document.write(i + ""); } window.document.write("<br/>"); } for循环嵌套实现:打印正三角形 -----* ----*** ---***** --******* -********* *********** /* 1.观察发现有很多行很多列--》循环嵌套 2.仔细观察其实需要打印2个三角形, 一个尖尖朝上,一个尖尖朝下 切记先一个一个打印, 然后再合并,否则步子迈大了会扯到蛋 */ for (var i = 0; i <= 5; i++) { for (var j = 0; j < 5 - i; j++) { window.document.write("-"); } for (var m = 0; m < 2*i+1; m++) { window.document.write("*"); } window.document.write("<br/>"); } for循环嵌套实现:打印99乘法表 1 * 1 = 1 1 * 2 = 2 2 * 2 = 4 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9 <style> span{ display: inline-block; border: 1px solid #000; padding: 10px; } </style> <script> /* 1.观察发现有很多行很多列--》循环嵌套 2.尖尖朝上,让内循环条件表达式随着外循环i改变 3.输出的结果等于 内循环的变量 * 外循环的变量 */ for (var i = 1; i <= 9; i++) { for (var j = 1; j <= i; j++) { document.write("<span>"+j+"*"+i+"="+i*j+"</span>"); } window.document.write("<br/>"); } </script> 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
关系运算符 为什么要学习关系运算符? 默认情况下,我们在程序中写的每一句正确代码都会被执行。但很多时候,我们想在某个条件成立的情况下才执行某一段代码 这种情况的话可以使用条件语句来完成,但是学习条件语句之前,我们先来看一些更基础的知识:如何判断一个条件成不成立。 JavaScript中的真假性 在JavaScript中,条件成立称为“真”,条件不成立称为“假”,因此,判断条件是否成立就是判断条件的“真假”。 在JavaScript已经给我们定义好了一个Boolean类型的值, 取值是true和false, true代表真, false代表假 而接下来我们要学习的关系运算符它的返回值正好就是Boolean类型的值, 也就是说关系运算符的返回值要么是true,要么是false 关系运算符 在程序中经常需要比较两个量的大小关系,以决定程序下一步的工作。比较两个量的运算符称为关系运算符。 序号 运算符 结合性 1 > 大于 左结合 2 < 小于 左结合 3 >= 大于等于 左结合 4 <= 小于等于 左结合 5 == 判断值是否相等 左结合 6 != 判断值是否不相等 左结合 7 === 判断值和类型是否相等 左结合 8 !== 判断值和类型是否不相等 左结合 简单示例 var a, b; a = 10; b = 10; console.log(a > b); // false console.log(a < b); // false console.log(a >= b); // true console.log(a <= b); // true console.log(a == b); // true console.log(a != b); // false 特殊示例 对于非数值进行比较时,会将其转换为数字然后在比较 console.log(1 > true); //false console.log(1 >= false); //true console.log(1 > "0"); //true console.log(1 > null); //true 如果符号两侧的值都是字符串时,不会将其转换为数字进行比较, 而会分别比较字符串中字符的Unicode编码 Unicode编码转换地址 // a的Unicode编码是:0061 // b的Unicode编码是:0062 console.log("a" < "b");//true // 比较多位时则是从前往后一位一位比较 // 第一位相同比较第二位, 直到比较到不一样或者结束为止 // c的Unicode编码是:0063 console.log("ab" < "ac");//true null、undefined 、NaN比较 console.log(null == 0); // false console.log(undefined == 0); // false // 永远不要判断两个NaN是否相等 console.log(NaN == NaN); // false /* * 可以通过isNaN()函数来判断一个值是否是NaN * 如果该值是NaN则返回true,否则返回false */ var num = NaN; console.log(isNaN(num)); // true // undefined 衍生自 null, 所以返回true console.log(null == undefined); // true; console.log(null === undefined); // false; // == 判断值是否相等 // == 会进行数据类型转换 console.log("123" == 123); // true // === 判断值和类型时候同时相等 // === 不会进行数据类型转换 console.log("123" === 123); // false 练习 计算下列表达式的值 3 > 4 + 7 (3>4) + 7 5 != 4 + 2 * 7 > 3 == 10 -Tips: 利用Unicode编码可以在控制台和网页中输出一些牛逼的符号 // 在控制台中直接使用Unicode编码即可 console.log("\u2764"); // // 在网页中需要以&#开头, 然后将十六进制2764转换为十进制 <div>&#10084</div> 逻辑运算符 为什么要学习逻辑运算符? 有时候,我们需要在多个条件同时成立的时候才能执行某段代码 比如:用户只有同时输入了QQ和密码,才能执行登录代码,如果只输入了QQ或者只输入了密码,就不能执行登录代码。这种情况下,我们就要借助于JavaScript提供的逻辑运算符。 JavaScript中提供了三种逻辑运算符: &&(与运算) ||(或运算) !(非运算) 逻辑与 格式: 条件A && 条件B 运算结果: 只有当条件A和条件B都成立时,结果才为true;其余情况的结果都为false。因此,条件A或条件B只要有一个不成立,结果都为false 口诀:一假则假 逻辑与运算过程 总是先判断条件A是否成立 如果条件A成立,接着再判断条件B是否成立:如果条件B成立,“条件A && 条件B”的结果就为true,如果条件B不成立,结果就为false 如果条件A不成立,就不会再去判断条件B是否成立:因为条件A已经不成立了,不管条件B如何结果肯定是false //如果两个值都是true则返回true var result = true && true; //只要有一个false,就返回false result = true && false; result = false && true; result = false && false; 逻辑与短路现象 //第一个值为true,会检查第二个值 true && alert("这哥们来了!!"); //第一个值为false,不会检查第二个值 false && alert("这哥们不会来了!!"); 注意点: 对于非Boolean类型的数值, 逻辑与会将其悄悄咪咪转换为Boolean类型来判断 如果条件A不成立, 则返回条件A的数值本身 如果条件A成立, 不管条件B成不成立都返回条件B数值本身 var result = "123" && "abc"; console.log(result); // "abc" result = "123" && 0; console.log(result); // 0 result = null && 0; console.log(result); // null 逻辑或 格式: 条件A || 条件B 运算结果: 当条件A或条件B只要有一个成立时(也包括条件A和条件B都成立),结果就为true;只有当条件A和条件B都不成立时,结果才为false 口诀:一真为真 逻辑或运算过程 总是先判断条件A是否成立 如果条件A成立,就不会再去判断条件B是否成立:因为条件A已经成立了,不管条件B如何结果肯定是1,也就是true 如果条件A不成立,接着再判断条件B是否成立:如果条件B成立,“条件A || 条件B”的结果就为true,如果条件B不成立,结果就为false //两个都是false,则返回false var result = false || false; //只有有一个true,就返回true result = true || false; result = false || true ; result = true || true ; 逻辑或短路现象 //第一个值为false,则会检查第二个值 false || alert("123"); //第一个值为true,则不再检查第二个值 true || alert("123"); 注意点: 对于非Boolean类型的数值, 逻辑与会将其悄悄咪咪转换为Boolean类型来判断 如果条件A不成立, 则不管条件B成不成立都返回条件B数值本身 如果条件A成立, 则返回条件A的数值本身 var result = null || 0; console.log(result); // 0 result = "123" || "abc"; console.log(result); // "123" result = "123" || 0; console.log(result); // "123" 逻辑非 格式: ! 条件A 运算结果: 其实就是对条件A进行取反:若条件A成立,结果就为false;若条件A不成立,结果就为true。也就是说:真的变假,假的变真。 口诀:真变假,假变真 var bool1 = true; var res1 = !bool1; console.log(res1); // false var bool2 = false; var res2 = !bool2; console.log(res2); // true 注意点: 对一个值进行两次取反,它不会变化 var bool = true; var res = !!bool; console.log(res); // true 对非布尔值进行元素,则会将其转换为布尔值,然后再取反 var num = 10; var res = !num; // 先将10转换为true, 然后再取反 console.log(res); // false 所以, 要想将其它类型转换为Number类型除了Boolean()函数, 还可以使用 !!数值; 值得注意的是!!数值;的形式,实现原理和Boolean()函数一样 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
什么是数据类型转换 将一个数据类型转换为其他的数据类型 例如: 将Number类型转换为Boolean类型 例如: 将Number类型转换为字符串类型 ... ... 将其它类型转换为字符串 调用被转换数据类型的toString()方法 var num1 = 10; var res1 = num1.toString(); // 重点 console.log(res1); // 10 console.log(typeof res1); // string var num2 = true; var res2 = num2.toString(); // 重点 console.log(res2); // true console.log(typeof res2); // string null和undefined这两个值没有toString()方法,如果调用他们的方法,会报错 var num3 = undefined; var res3 = num3.toString(); // 报错 console.log(res3); var num4 = null; var res4 = num4.toString(); // 报错 console.log(res4); var num5 = NaN; var res5 = num5.toString(); console.log(res5); // NaN console.log(typeof res5); // String 该方法不会影响到原变量,它会将转换的结果返回 var num6 = 10; var res6 = num6.toString(); console.log(typeof num6); // number console.log(typeof res6); // string 数值类型的toString(),可以携带一个参数,输出对应进制的值(暂时不用了解, 讲到进制转换再回来看) var num7 = 20; var res7 = num7.toString(2); var res8 = num7.toString(8); var res9 = num7.toString(10); var res10 = num7.toString(16); console.log(res7); // 10100 console.log(res8); // 24 console.log(res9); // 20 console.log(res10); // 14 将被转换的数据传入String()函数中 String()函数存在的意义: 有些值没有toString(),这个时候可以使用String()。比如:undefined和null 对于Number和Boolean实际上就是调用的toString()方法 var num1 = 10; var res1 = String(num1); // 重点 console.log(res1); // 10 console.log(typeof res1); // string var num2 = true; var res2 = String(num2); // 重点 console.log(res2); // true console.log(typeof res2); // string 对于null和undefined,就不会调用toString()方法(因为这两个哥们没有这个方法).而是在内部生成一个新的字符串 var num3 = undefined; var res3 = String(num3); console.log(res3); // undefined console.log(typeof res3); // string var num4 = null; var res4 = String(num4); console.log(res4); // null console.log(typeof res4); // string 将被转换的数据和+""连接到一起 任何数据和 +"" 连接到一起都会转换为字符串 内部实现原理和String()函数一样 var num1 = 10; var res1 = num1 + ""; console.log(res1); // 10 console.log(typeof res1); // string var num2 = true; var res2 = num2 + ""; console.log(res2); // true console.log(typeof res2); // string var num3 = undefined; var res3 = num3 + ""; console.log(res3); // undefined console.log(typeof res3); // string var num4 = null; var res4 = num4 + ""; console.log(res4); // null console.log(typeof res4); // string 将其它类型转换为Number类型 将被转换的数据传入Number()函数中 字符串 --> 数字 如果是纯数字的字符串,则直接将其转换为数字 var str1 = "123"; var res1 = Number(str1); console.log(res1); // 123 console.log(typeof res1); // number 如果字符串中有非数字的内容,则转换为NaN var str2 = "123ab"; var res2 = Number(str2); console.log(res2); // NaN 如果字符串是一个空串或者是一个全是空格的字符串,则转换为0 var str3 = ""; var res3 = Number(str3); console.log(res3); // 0 var str4 = " "; var res4 = Number(str4); console.log(res4); // 0 布尔 --> 数字 true 转成 1 false 转成 0 var bool1 = true; var res5 = Number(bool1); console.log(res5); // 1 var bool2 = false; var res6 = Number(bool2); console.log(res6); // 0 null --> 数字 --> 0 var str5 = null; var res7 = Number(str5); console.log(res7); // 0 undefined --> 数字 --> NaN var str6 = undefined; var res8 = Number(str6); console.log(res8); // NaN 将被转换的数据传入parseInt()函数中/parseFloat()函数中 Number()函数中无论混合字符串是否存在有效整数都会返回NaN 利用parseInt()/parseFloat()可以提取字符串中的有效整数 两者之前的区别是前者只能提取整数,后者可以提取小数 parseInt()提取字符串中的整数 从第一位有效数字开始, 直到遇到无效数字 如果第一位不是有效数字, 什么都提取不到, 会返回NaN 第一个参数是要转换的字符串,第二个参数是要转换的进制 var str7 = "300px"; var res9 = parseInt(str7); console.log(res9); // 300 var str8 = "300px250"; var res10 = parseInt(str8); console.log(res10); // 300 console.log(parseInt("abc123")); //返回NaN,如果第一个字符不是数字或者符号就返回NaN console.log(parseInt("")); //空字符串返回NaN,Number("")返回0 parseFloat提取字符串中的小数 会解析第一个. 遇到第二个.或者非数字结束 如果第一位不是有效数字, 什么都提取不到 不支持第二个参数,只能解析10进制数 如果解析的内容里只有整数,解析成整数 var str9 = "20.5px"; var res11 = parseInt(str9); console.log(res11); // 20 var str10 = "20.5.5.5px"; var res12 = parseFloat(str10); console.log(res12); // 20.5 对非String使用parseInt()或parseFloat(), 会先将其转换为String然后在操作 var str11 = true; var res13 = parseInt(str11); // 这里相当于parseInt("true"); console.log(res13); // NaN var res14 = Number(str11); console.log(res14); // 1 进制转换 什么是进制? 是一种计数的方式,数值的表示形式 常见的进制 十进制、二进制、八进制、十六进制 进制数字进位方法 十进制 0、1、2、3、4、5、6、7、8、9 逢十进一 二进制 0、1 逢二进一 书写形式:需要以0b或者0B开头,比如0b101 八进制 0、1、2、3、4、5、6、7 逢八进一 书写形式:在前面加个0,比如045 十六进制 0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F 逢十六进一 16进制就是逢16进1,但我们只有0~9这十个数字,所以我们用A,B,C,D,E,F这五个字母来分 别表示10,11,12,13,14,15。字母不区分大小写。 书写形式:在前面加个0x或者0X,比如0x45 数一下方块的个数, 并用十进制,二进制,八进制,十六进制表示 练习判断下列数字是否合理 00011 0x001 0x7h4 10.98 0986 .089-109 +178 0b325 0b0010 0xffdc -.003 十进制转换二进制 规律: 用需要转换的十进制/2取余然后倒叙 9 / 2 = 4 = 1 4 / 2 = 2 = 0 2 / 2 = 1 = 0 1 / 2 = 0 = 1 结果1001 var num = 0b1001; console.log(num); console.log(parseInt("1001", 2)); 二进制转换十进制 规律: 从低位数开始, 用低位 * 2的多少次幂, 幂数从0开始递增 1001 1 * 2(0) = 1 0 * 2(1) = 0 0 * 2(2) = 0 1 * 2(3) = 8 结果: 1 + 0 + 0 +8 = 9 二进制转换八进制 因为八进制逢八进一 规律: 三个二进制位代表一个八进制位, 只需要从低位开始将三个二进制位转换为十进制再链接起来 00 001 001 结果: 0 1 1 = 011 console.log(parseInt("011", 8)); // 9 二进制转换十六进制 因为十六进制逢十六进一 规律: 四个二进制位代表一个十六进制位, 只需要从低位开始将事个二进制位转换为十进制再链接起来 0001 1011 结果: 1 b = 0x1b console.log(parseInt("0x1b", 16));// 27 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
什么是数据? 生活中时时刻刻都在跟数据打交道,比如体重数据、血压数据、股票数据等。在我们使用计算机的过程中,会接触到各种各样的数据,有文档数据、图片数据、视频数据,还有聊QQ时产生的文字数据、用迅雷下载的文件数据等 数据类型 数据的分类 静态的数据 概念:静态数据是指一些永久性的数据,一般存储在硬盘中。硬盘的存储空间一般都比较大,现在普通计算机的硬盘都有500G左右,因此硬盘中可以存放一些比较大的文件。 存储的时长:计算机关闭之后再开启,这些数据依旧还在,只要你不主动删掉或者硬盘没坏,这些数据永远都在 哪些是静态数据:静态数据一般是以文件的形式存储在硬盘上,比如文档、照片、视频等。 动态的数据 概念:动态数据指在程序运行过程中,动态产生的临时数据,一般存储在内存中。内存的存储空间一般都比较小,现在普通计算机的内存只有8G左右,因此要谨慎使用内存,不要占用太多的内存空间。 存储的时长:计算机关闭之后,这些临时数据就会被清除。 哪些是动态数据:当运行某个程序(软件)时,整个程序就会被加载到内存中,在程序运行过程中,会产生各种各样的临时数据,这些临时数据都是存储在内存中的。当程序停止运行或者计算机被强制关闭时,这个程序产生的所有临时数据都会被清除。 你可能会问:既然硬盘的存储空间这么大,为何不把所有的应用程序加载到硬盘中去执行呢?有个主要原因是内存的访问速度比硬盘快N倍。 静态数据和动态数据的相互转换 静态数据到动态数据(也就是从磁盘加载到内存。) 动态数据和静态数据的相互转换 如手机拍照片存储到手机的硬盘上 数据的计量单位 不管是静态还是动态数据,都是0和1组成的。0和1如何组成这么多的数据? 数据都有大小,静态数据就会占用硬盘的空间,动态数据就占用内存的空间 数据越大,包含的0和1就越多,比特位和字节 1 B(Byte字节) = 8 bit(位) 1 KB(KByte) = 1024 B 1 MB = 1024 KB 1 GB = 1024 MB 1 TB = 1024 GB JavaScript数据类型概述 作为程序员最关心的是内存中的动态数据, 因为我们写的程序就是在内存中的 程序在运行过程中会产生各种各样的临时数据, 为了方便数据的运算和操作, JavaScript对这些数据进行了分类, 提供了丰富的数据类型 在JS中一共有六种数据类型 String 字符串(基本数据类型) Number 数值(基本数据类型) Boolean 布尔值(基本数据类型) Null 空值(基本数据类型) Undefined 未定义(基本数据类型) Object 对象(引用数据类型) 如何查看数据类型? 使用typeof操作符可以用来检查数据类型。 使用格式:typeof 数据,例如 typeof 123; typeof num; console.log(typeof 123); // number var num = 10; console.log(typeof num); // number typeof操作符会将检查的结果以字符串的形式返回给我们 var value= 10; // 此时将value的数据类型number以字符串返回给我们, 存入到res变量中 var res = typeof value; // 此时检查res的数据类型为string, 证明typeof返回给我们的是一个字符串 console.log(typeof res); // string 字符串类型 String用于表示一个字符序列,即字符串 字符串需要使用 ’或“ 括起来 var str1 = "hello"; var str2 = `nj`; var str5 = `hello nj"; // 错误 console.log(typeof str1 ); // string console.log(typeof str2); // string 相同引号不能嵌套,不同引号可以嵌套 双引号不能放双引号,单引号不能放单引号 var str3 = "hello "nj""; // 错误 var str4 = `hello `nj``; // 错误 var str5 = "hello 'nj'"; // 正确 var str6 = `hello "nj"`;// 正确 给变量加上引号, 那么变量将变为一个常量 var num = 110; console.log(num); // 输出变量中的值 console.log("num"); // 输出常量num Number类型 在JS中所有的数值都是Number类型(整数和小数) var num1= 123; var num2= 3.14; console.log(typeof num1); // number console.log(typeof num2); // number 由于内存的限制,ECMAScript 并不能保存世界上所有的数值 最大值:Number.MAX_VALUE console.log(Number.MAX_VALUE); // 1.7976931348623157e+308 最小值:Number.MIN_VALUE console.log(Number.MIN_VALUE); // 5e-324 无穷大:Infinity, 如果超过了最大值就会返回该值 console.log(Number.MAX_VALUE + Number.MAX_VALUE); // Infinity 无穷小:-Infinity, 如果超过了最小值就会返回该值 console.log(typeof Infinity); // number console.log(typeof -Infinity); // number NaN 非法数字(Not A Number),JS中当对数值进行计算时没有结果返回,则返回NaN var num3 = NaN; console.log(typeof num3); // number Number类型注意点 JS中整数的运算可以保证精确的结果 var sum1 = 10 + 20; console.log(sum1); // 30 在JS中浮点数的运算可能得到一个不精确的结果 var sum1 = 10.1 + 21.1; console.log(sum1); // 31.200000000000003 Boolean 布尔值 布尔型也被称为逻辑值类型或者真假值类型 布尔型只能够取真(true)和假(false)两种数值 var bool1 = true; var bool2 = false; console.log(typeof bool1); // boolean console.log(typeof bool2); // boolean 虽然Boolean 类型的字面值只有两个,但 ECMAScript 中所有类型的值都有与这两个 Boolean 值等价的值 任何非零数值都是true, 包括正负无穷大, 只有0和NaN是false 任何非空字符串都是true, 只有空字符串是false 任何对象都是true, 只有null和undefined是false var bool3 = Boolean(0); console.log(bool3); // false var bool4 = Boolean(1); console.log(bool4); // true var bool5 = Boolean(-1); console.log(bool4); // true var bool6 = Boolean(Infinity); console.log(bool4); // true var bool7 = Boolean(-Infinity); console.log(bool4); // true var bool8 = Boolean(NaN); console.log(bool8); // false var bool9 = Boolean(undefined); console.log(bool8); // false var bool10 = Boolean(null); console.log(bool8); // false var bool11 = Boolean(""); console.log(bool8); // false var bool12 = Boolean("abc"); console.log(bool12); // true Null和Undefined Undefined这是一种比较特殊的类型,表示变量未赋值,这种类型只有一种值就是undefined var num; console.log(num); //结果是undefined undefined是Undefined类型的字面量 前者undefined和10, "abc"一样是一个常量 后者Undefined和Number,Boolean一样是一个数据类型 需要注意的是typeof对没有初始化和没有声明的变量都会返回undefined。 var value1 = undefined; console.log(typeof value); //结果是undefined var value2; console.log(typeof value2); //结果是undefined Null 类型是第二个只有一个值的数据类型,这个特殊的值是 null 从语义上看null表示的是一个空的对象。所以使用typeof检查null会返回一个Object var test1= null; console.log(typeof test1); undefined值实际上是由null值衍生出来的,所以如果比较undefined和null是否相等,会返回true var test1 = null; var test2 = undefined; console.log(test1 == test2); console.log(test1 === test2); 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
关键字和保留字 什么是关键字? 被JavaScript语言赋予了特殊含义的单词 好比现实生活中110、120、119对我们又特殊含义一样, 在JavaScript也有一些对于JavaScript有特殊含义的单词,这些单词我们称之为关键字 关键字在开发工具中会显示特殊颜色 关键字不能用作变量名、函数名等 关键字严格区分大小写, var和Var前者是关键字, 后者不是 关键字 break do instanceof typeof case else new var catch finally return void continue for switch while default if throw delete in try function this with debugger false true null 什么是保留字? JavaScript预留的关键字,他们虽然现在没有作为关键字,但在以后的升级版本中有可能作为关键字 保留字 class enum extends super const export import implements let private public yield interface package protected static 标识符 什么是标识符? 从字面上理解就是用来标识某些东西的符号,标识的目的就是为了将这些东西区分开来 其实标识符的作用就跟人类的名字差不多,为了区分每个人,就在每个人出生的时候起了个名字 日常生活中乔丹、刘德华、吴京这些都是标识符 在编程中标识符就是程序员自己在程序中起的一些名字 例如定义变量时的变量名称就是一个标识符var num;, 这里的num就是标识符 标识符命名规则(必须遵守) 只能由26个英文字母的大小写、10个阿拉伯数字0~9、下划线_、美元符号$组成 不能以数字开头 严格区分大小写,比如test和Test是2个不同的标识符 不可以使用关键字、保留字作为标识符 JS底层保存标识符时实际上是采用的Unicode编码,所以理论上讲,所有的utf-8中含有的内容都可以作为标识符 不建议使用标识符 abstract double goto native static boolean enum implements package super byte export import private synchronize char extends int protected throws class final interface public transient const float long short volatile arguments encodeURI Infinity Number RegExp undefined isFinite Object String Boolean Error RangeError parseFloat SyntaxError Date eval JSON ReferenceError TypeError decodeURI EvalError Math URIError decodeURIComponent Function NaN isNaN parseInt Array encodeURICOmponent 标识符命名规范(建议遵守) 变量的名称要有意义(有利于提高阅读性) 变量的命名遵守驼峰命名法,首字母小写,第二个单词的首字母大写 例如: userName、myFirstName 练习找出合法的标识符 fromNo12 from#12 my_Boolean my-Boolean Obj2 2ndObj $lnj test1 json2Data My_tExt _test test!32 haha(da)tt 哈哈_text jack_rose jack&rose GUI _123 if 注释 什么是注释? 注释是在所有计算机语言中都非常重要的一个概念,从字面上看,就是注解、解释的意思 注释可以用来解释某一段程序或者某一行代码是什么意思,方便程序员之间的交流。(假如我写完一行代码后,加上相应的注释,那么别人看到这个注释就知道我的这行代码是做什么用的) 注释可以是任何文字,也就是说可以写中文 在开发工具中注释一般是灰色或者绿色 江哥提示: 初学者编写程序可以养成习惯:先写注释再写代码。 将自己的思想通过注释先整理出来,在用代码去体现。 因为代码仅仅是思想的一种体现形式而已。 为什么要写注释? 没写注释 njQuery.extend({ isString : function (str ) { return typeof str === 'string'; }, isHTML : function (str) { if (!njQuery.isString(str)){ return false; }。 if (str.charAt(0) == '<' && str.charAt(str.length - 1) == '>' && str.length >= 3) { return true; } return false; }, trim : function (str) { if( !njQuery.isString(str)){ return str; }else{ return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); } } }); 写了注释 /*静态工具方法*/ njQuery.extend({ /** * 判断是否是字符串 * @param 需要判断的内容 * @returns 是返回true, 不是返回false */ isString : function (str ) { return typeof str === 'string'; }, /** * 判断是不是html片段 * @param 需要判断的内容 * @returns 是返回true, 不是返回false */ isHTML : function (str) { // 1.判断是否是字符串 if (!njQuery.isString(str)){ return false; } // 2.如果字符串的第一个字母是<,最后一个字母是>,并且length >= 3,就可以认为是html片段。 if (str.charAt(0) == '<' && str.charAt(str.length - 1) == '>' && str.length >= 3) { return true; } // 3.其它情况一律返回false return false; }, /** * 清除字符串首位空格 * @param 需要清除的字符串 * @returns 清除之后的新字符串 */ trim : function (str) { // 1.判断是否是字符串 if( !njQuery.isString(str)){ return str; }else{ // 2.优先使用自带trim处理, // 如果不支持再使用自己的方法处理 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); } } }); 注释特点 注释是写给人看的,不是给计算机看的。计算机怎么可能看得我们写的 中文。因此注释中的内容不会被解析器解析执行,但是会在源码中显示 检查代码的作用 排除错误 注释使用 单行注释 // 我是被注释的内容 var num; 使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面 作用范围: 从第二个斜线到这一行末尾 快捷键:Ctrl+/ 多行注释 /*我是被注释的内容*/ var num; 使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面 作用范围: 从第一个/*到最近的一个*/ 注释使用注意 单行注释可以嵌套单行注释、多行注释 // 帅哥 // 南哥 // 江哥 // /* 大哥 */ 多行注释可以嵌套单行注释 /* // 作者:LNJ // 描述:这个哥们F5一刷新 */ 多行注释不能嵌套多行注释 /* 哈哈哈 /*嘻嘻嘻*/ 呵呵呵 */ 常用注释 _ooOoo_ o8888888o 88" . "88 (| -_- |) O\ = /O ____/`---'\____ . ' \\| |// `. / \\||| : |||// \ / _||||| -:- |||||- \ | | \\\ - /// | | | \_| ''\---/'' | | \ .-\__ `-` ___/-. / ___`. .' /--.--\ `. . __ ."" '< `.___\_<|>_/___.' >'"". | | : `- \`.;`\ _ /`;.`/ - ` : | | \ \ `-. \_ __\ /__ _/ .-` / / ======`-.____`-.___\_____/___.-`____.-'====== `=---=' ............................................. 佛祖保佑 有无BUG 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
计算机常识 计算机只能识别0和1 计算机所能识别的指令都是由0和1组成的 计算机中存储和操作的数据都是0和1 计算机程序是什么? 一组计算机能识别和执行的指令(机器语言) 程序是为了告诉计算机"做某件事或解决某个问题"而用"计算机语言编写的命令序列(语句)" 只要让计算机执行这个程序,计算机就会自动地、有条不紊地进行工作,计算机的一切操作都是由程序控制的,离开程序计算机将一事无成 什么是编程语言? 编程语言就是人和计算机交流的工具,人类通过编程语言来控制、操作计算机 编程语言和我们说的中文、英文本质上没有区别,只是语法比较特殊 编程语言分类 机器语言 所有的代码里面只有0和1, 0表示不加电,1表示加电(纸带存储时 1有孔,0没孔) 优点:直接对硬件产生作用,程序的执行效率非常高 缺点:指令又多又难记、可读性差、无可移植性 汇编语言 符号化的机器语言,用一个符号(英文单词、数字)来代表一条机器指令 优点:直接对硬件产生作用,程序的执行效率非常高、可读性稍好 缺点:符号非常多和难记、无可移植性 高级语言 非常接近自然语言的高级语言,语法和结构类似于普通英文 优点:简单、易用、易于理解、远离对硬件的直接操作、有可移植性 缺点:有些高级语言写出的程序执行效率并不高 对比(利用3种类型语言编写1+1) 机器语言 10111000 00000001 00000000 00000101 00000001 00000000 汇编语言 MOV AX, 1 ADD AX, 1 高级语言 1 + 1 了解更多请点我 什么是JavaScript? JavaScript简称JS,是前端开发的一门脚本语言(解释型语言) 解释型语言:程序执行之前,不需要编译,直接运行时边解析边执行的语言 编译型语言:程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,比如exe文件 JavaScript作用 HTML 提供网页上显示的内容(结构) CSS 美化网页(样式) JavaScript 控制网页行为(行为) JavaScript发展史 JavaScript起源于Netscape公司的LiveScript语言 1994年网景公司发布历史上第一个比较成熟的浏览器(Navigator 0.9), 但是只能浏览不能交互 1995年为了解决表单有效性验证就要与服务器进行多次地往返交互问题,网景公司录用Brendan Eich(布兰登·艾奇),他在 10 天内开发出 LiveScript 语言 在 Netscape Navigator 2.0 即将正式发布前,Netscape 将LiveScript 更名为 JavaScript, 目的是为了蹭Java的热度 所以Java和 JavaScript之间的关系就像老婆和老婆饼一样 参考文献 JavaScript组成 ECMAScript:JavaScript的语法标准 ECMA是European Computer Manufacturers Association的缩写,即欧洲计算机制造商协会 ECMAScript是ECMA制定的脚本语言的标准, 规定了一种脚本语言实现应该包含的基本内容 JavaScript是脚本语言的一种,所以JavaScript也必须遵守ECMAScript标准,包含ECMAScript标准中规定的基本内容 DOM(Document Object Model):JavaScript操作网页上的元素(标签)的API BOM(Browser Object Model):JavaScript操作浏览器的部分功能的API ECMAScript起源 JavaScript书写格式 和CSS书写格式一样, JavaScript也有三种书写格式, 分别是"行内式"、"内嵌式"、"外链式" 和CSS书写格式一样, JavaScript三种书写格式中也推荐大家使用"外链式", 遵守结构、样式、行为分离 行内式格式(不推荐) <div onclick="alert('hello world');">我是div</div> 内嵌式格式 </body> ... ... <script type="text/javascript"> alert("hello world"); </script> </body> 内嵌式注意点 通常将js代码放在body的最后, 因为HTML是从上至下加载, 而js代码通常是给标签添加交互(操作元素), 所以需要先加载HTML, 否则如果执行js代码时HTML还未被加载, 那么js代码将无法添加交互(操作元素); HTML页面中出现<script>标签后,就会让页面暂停等待脚本的解析和执行。无论当前脚本是内嵌式还是外链式,页面的下载和渲染都必须停下来等待脚本的执行完成才能继续, 所以如果把js代码写在head中, 那么js代码没有执行完毕之前后续网页无法查看 如果不愿意把js代码放在body的最后, 也可以放在head标签中, 但是为了保证执行js代码时HTML一定已经被加载了, 那么需要添加一些额外代码.(不推荐) <head> <script> window.onload = function () { // 必须添加这句 alert("hello world"); } </script> </head> 外链式格式 <script type="text/javascript" src="01-js书写格式.js"></script> 外链式注意点 外链式的script代码块中不能编写js代码, 即便写了也不会执行 <script type="text/javascript" src="01-js书写格式.js"> alert("hello world"); // 不会被执行 </script> 由于每次加载外链式的js文件都会发送一次请求, 这样非常消耗性能, 所以在企业开发中推荐将多个JS文件合成为一个JS文件,以提升网页的性能和加载速度 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
Div和Span标签 什么是div? 作用: 一般用于配合css完成网页的基本布局 <style> .header{ width: 980px; height: 100px; background: red; margin: auto; margin-bottom: 10px; } .content{ width: 980px; height: 500px; background: green; margin: auto; margin-bottom: 10px; } .footer{ width: 980px; height: 100px; background: blue; margin: auto; } .logo{ width: 200px; height: 50px; background: pink; float: left; margin: 20px; } .nav{ width: 600px; height: 50px; background: yellow; float: right; margin: 20px; } .aside{ width: 250px; height: 460px; background: purple; float: left; margin: 20px; } .article{ width: 650px; height: 460px; background: deepskyblue; float: right; margin: 20px; } </style> <div class="header"> <div class="logo"></div> <div class="nav"></div> </div> <div class="content"> <div class="aside"></div> <div class="article"></div> </div> <div class="footer"></div> 什么是span? 作用: 一般用于配合css修改网页中的一些局部信息 <style> span{ color: red; } </style> <p>努力到<span>无能为力</span>, 拼搏到<span>感动自己</span></p> div和span有什么区别? 1.div会单独的占领一行,而span不会单独占领一行 2.div是一个容器级的标签, 而span是一个文本级的标签 容器级的标签和文本级的标签的区别? 容器级的标签中可以嵌套其它所有的标签 常见容器级的标签: div h ul ol dl li dt dd ... 文本级的标签中只能嵌套文字/图片/超链接 常见文本级的标签:span p buis strong em ins del ... 注意点: 不用刻意去记忆哪些标签是文本级的哪些标签是容器级, 在企业开发中一般情况下要嵌套都是嵌套在div中, 或者按照组标签来嵌套(ul>li, ol>li , dl>dt+dd) CSS元素显示模式 在HTML中HTML将所有的标签分为两类, 分别是容器级和文本级 在CSS中CSS也将所有的标签分为两类, 分别是块级元素和行内元素(其实还有一类, 行内块级) 什么是块级元素, 什么是行内元素? 块级元素会独占一行 行内元素不会独占一行 常见容器级的标签: div h ul ol dl li dt dd ... 常见文本级的标签:span p buis stong em ins del ... 常见块级元素: p div h ul ol dl li dt dd 常见行内元素: span buis strong em ins del 块级元素和行内元素的区别? 块级元素 独占一行 如果没有设置宽度, 那么默认和父元素一样宽 如果设置了宽高, 那么就按照设置的来显示 行内元素 不会独占一行 如果没有设置宽度, 那么默认和内容一样宽 行内元素是不可以设置宽度和高度的 行内块级元素 为了能够让元素既能够不独占一行, 又可以设置宽度和高度, 那么就出现了行内块级元素 不独占一行, 并且可以设置宽高 CSS元素显示模式转换 如何转换CSS元素的显示模式? 设置元素的display属性 display取值 block 块级 inline 行内 inline-block 行内块级 快捷键 di display: inline; db display: block; dib display: inline-block; 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
video标签 作用: 播放视频 格式1: <video src=""> </video> video标签的属性 src: 告诉video标签需要播放的视频地址 autoplay: 告诉video标签是否需要自动播放视频 controls: 告诉video标签是否需要显示控制条 poster: 告诉video标签视频没有播放之前显示的占位图片 loop: 告诉video标签循环播放视频. 一般用于做广告视频 preload: 告诉video标签预加载视频, 但是需要注意preload和autoplay相冲, 如果设置了autoplay属性, 那么preload属性就会失效 muted:告诉video标签视频静音 width/height: 和img标签中的一模一样 格式2 <video> <source src="" type=""></source> <source src="" type=""></source> </video> 第二种格式存在的意义 由于视频数据非常非常的重要, 所以五大浏览器厂商都不愿意支持别人的视频格式, 所以导致了没有一种视频格式是所有浏览器都支持的这个时候W3C为了解决这个问题, 所以推出了第二个video标签的格式 ideo标签的第二种格式存在的意义就是为了解决浏览器适配问题. video 元素支持三种视频格式, 我们可以把这三种格式都通过source标签指定给video标签, 那么以后当浏览器播放视频时它就会从这三种中选择一种自己支持的格式来播放 注意点: 当前通过video标签的第二种格式虽然能够指定所有浏览器都支持的视频格式, 但是想让所有浏览器都通过video标签播放视频还有一个前提条件, 就是浏览器必须支持HTML5标签, 否则同样无法播放 在过去的一些浏览器是不支持HTML5标签的, 所以为了让过去的一些浏览器也能够通过video标签来播放视频, 那么我们以后可以通过一个JS的框架叫做html5media来实现 audio标签 作用: 播放音频 格式: <audio src=""> </audio> <audio> <source src="" type=""> </audio> 注意点: audio标签的使用和video标签的使用基本一样, video中能够使用的属性在audio标签中大部分都能够使用, 并且功能都一样. 只不过有3个属性不能用, height/width/poster 详情和概要标签 作用:利用summary标签来描述概要信息, 利用details标签来描述详情信息 默认情况下是折叠展示, 想看见详情必须点击 格式: <details> <summary>概要信息</summary> 详情信息 </details> marquee标签 作用: 跑马灯效果 格式: <marquee>内容</marquee> 属性: direction: 设置滚动方向 left/right/up/down scrollamount: 设置滚动速度, 值越大就越快 loop: 设置滚动次数, 默认是-1, 也就是无限滚动 behavior: 设置滚动类型 slide滚动到边界就停止, alternate滚动到边界就弹回 注意点: marquee标签不是W3C推荐的标签, 在W3C官方文档中也无法查询这个标签, 但是各大浏览器对这个标签的支持非常好 HTML中被废弃的标签 -- 由于HTML现在只负责语义而不负责样式.但是HTML一开始有一部分标签连样式也包揽了, 所以这部分标签都被废弃了 - b、u、i、s - 以上标签自己带有样式, 有浓厚的样式作用, 今后一般都只作为CSS钩子使用 - 原则: 不到万不得已,切记不要使用如上标签. 大家可以到BAT的网站查看源代码, 几乎看不到以上标签 b(Bold)作用: 将文本字体加粗 格式:<b>将文本字体加粗</b> u(Underlined)作用: 为文本添加下划线 格式:<u>为文本添加下划线</u> i(Italic)作用: 显示斜体文本效果 格式:<i>显示斜体文本效果</i> s(Strikethrough)作用: 为文本添加删除线 格式:<s>为文本添加删除线</s> 为了弥补 b、u、i、s标签的不足, W3C又推出了一组新的标签, 这些标签在显示上看似和buis没什么区别, 但是在语义上却有重大区别 strong作用: 着重内容 格式:<strong>着重内容</strong> ins(Inserted)作用: 新插入的文本 格式:<ins>新插入的文本</ins> em(Emphasized)作用:强调内容 格式:<em>强调内容</em> del(Deleted)作用: 已删除的文本 格式:<del>已删除的文本</del> 其它更多详见维基百科 HTML实体 我们想在页面上输出<h1>这些字符,但是HTML认为这是一个标签, 所以如果需要输出一些特殊的字符需要通过字符实体来实现 lt是英语less than 小于的意思, <可以在页面上输出一个小于符号 gt是英语greater than 大于的意思, >可以在页面上输出一个大于符号 html中对空格,缩进,换行不敏感,如果同时出现多个空格缩进或者执行,页面只会把它们当作一个空格来解析。所以想要在html中输出空格必须使用来实现, 一个就代表一个空格 html对中文空格敏感, 也就是说可以显示中文空格, 但是不推荐这样使用 是英语non-breaking spacing, 翻译为不打断空格的意思 显示结果 描述 实体名称 空格 < 小于号 < > 大于号 > © 版权 © ® 注册商标 ® ™ 商标 ™ & 和号 & " 引号 " ' 撇号 ' ¢ 分 ¢ £ 镑 £ ¥ 日圆 ¥ € 欧元 € § 小节 § × 乘号 × ÷ 除号 ÷ 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
meta其它类型 Keywords类型 作用: 告诉搜索引擎当前网页的关键词, 在SEO中非常重要, 能够提高搜索命中率, 让别人更容易找到你 格式: <meta name="keywords" content="IT前言技术、iOS技术、HTML5技术、Android技术" /> Descriiption类型 作用: 告诉搜索引擎当前网页的主要内容, 在SEO中非常重要, 当别人在搜索引擎中搜索到你的网站时会自动作为对你网站的描述信息展示给用户, 能够提高搜索命中率, 让别人更容易找到你 格式: <meta name="description" content="江哥热爱于IT前言技术、iOS技术、HTML5技术、Android技术研究" /> Author类型 作用: 告诉搜索引擎当前网页的作业, install B专用 格式: <meta name="author" content="Jonathan_Lee" /> Refresh类型 作用: 告诉浏览器多久自动刷新一次页面并指向新页面, 常用于新老域名交替时, 访问老域名自动跳转到新域名, 或用语一些需要定时刷新的聊天室等. 格式: <meta http-equiv="Refresh" content="2;URL=http://www.baidu.com/"> Robots类型 作用: 告诉搜索引擎当前网页的作业, 写给网络爬虫看的内容 格式: <meta name="robots" content="all|none|index|follow|noindex|nofollow" /> index: 告诉搜索引擎允许抓取当前页面 noindex:告诉搜索引擎不允许抓取当前页面 follow: 告诉搜索引擎可以从当前页面上找到链接,然后继续访问抓取下去 nofollow:告诉搜索引擎不允许从当前页面上找到链接, 拒绝其继续访问 all: 告诉搜索引擎允许抓取当前页面, 并且允许从此页找到链接继续访问 none: 告诉搜索引擎不允许抓取这个页面,并且不允许从此页找到链接、拒绝其继续访问 常见组合 <META NAME="ROBOTS" CONTENT="INDEX,FOLLOW">:可以抓取本页,而且可以顺着本页继续索引别的链接 有可以写成<META NAME="ROBOTS" CONTENT="ALL"> <META NAME="ROBOTS" CONTENT="NOINDEX,FOLLOW">:不许抓取本页,但是可以顺着本页抓取索引别的链接 <META NAME="ROBOTS" CONTENT="INDEX,NOFOLLOW">:可以抓取本页,但是不许顺着本页抓取索引别的链接 <META NAME="ROBOTS" CONTENT="NOINDEX,NOFOLLOW">:不许抓取本页,也不许顺着本页抓取索引别的链接。 注意事项: 不要把两个对立的反义词写到一起,例如 例如<META NAME="ROBOTS" CONTENT="INDEX,NOINDEX"> 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038
什么是HTML HTML其实是HyperText Markup Language的缩写, 超文本标记语言 HTML的作用 1.首先利用记事本保存了一个标题和两段描述, 然后修改纯文本文件的扩展名为.html, 然后再利用浏览器打开 郑伊健 郑伊健,1967年10月4日出生于中国香港,籍贯广东恩平,香港影视演员、流行男歌手。1988年参加新秀歌唱大赛加入无线电视,因拍摄“阳光柠檬茶”广告而入行,拜罗文为师。[1] 1991年加盟BMG唱片公司以歌手身份出道。1995年开始,凭借在《古惑仔》系列电影中饰演陈浩南一角走红。1996年凭借《古惑仔》主题曲《友情岁月》获得十大中文金曲奖。1996年至1997年连续两次获得”台湾十大偶像”奖。 2.打开之后发现显示的格式不对, 不对的原因是因为在纯文本文件中所有文字都是同级别的, 浏览器不知道哪些文字代表什么意思. 也就是浏览器不知道哪些文字是标题, 哪些文字是段落...., 所以导致了显示的格式不正确 3.正是因为如此, 所以HTML应用而生. HTML就只有一个作用, 它是专门用来描述文本的语义的. 也就是说我们可以利用HTML来告诉浏览器哪些是标题, 哪些是段落. 这些用于描述其它文本语义的文本, 我们称之为标签. 并且这些用于描述文本语义的标签将来在浏览器中是不会被显示出来的 所以正是因为HTML的这些标签是专门用来描述其它文本语义的, 并且在浏览器中不会被显示出来, 所以我们称这些文本为"超文本", 而这些文本又叫做标签, 所以HTML被称之为"超文本标记语言" <h1>郑伊健</h1> <p>郑伊健,1967年10月4日出生于中国香港,籍贯广东恩平,香港影视演员、流行男歌手。1988年参加新秀歌唱大赛加入无线电视,因拍摄“阳光柠檬茶”广告而入行,拜罗文为师。[1] </p> <p>1991年加盟BMG唱片公司以歌手身份出道。1995年开始,凭借在《古惑仔》系列电影中饰演陈浩南一角走红。1996年凭借《古惑仔》主题曲《友情岁月》获得十大中文金曲奖。1996年至1997年连续两次获得”台湾十大偶像”奖。</p> 注意事项: 虽然我们利用H1标签描述一段文本之后, 这段文本在浏览器中显示出来会被放大和加粗, 看上去我们是利用HTML的标签修改了被描述的那段文本的样式. 但是一定要记住, HTML只有一个作用, 它是专门用来给文本添加语义的, 而不是用来修改文本的样式的 H1标签它的作用是什么? 错误: H1标签可以用来修改文字的大小, 并且还可以将文字加粗 正确: H1标签的作用是用来告诉浏览器, 哪些文字是标题. 也就是H1标签是专门用于给指定的文字添加标题语义的 HTML发展史 IETF简介 IETF是英文Internet Engineering Task Force的缩写, 翻译过来就是"互联网工程任务组" IETF负责定义并管理因特网技术的所有方面。包括用于数据传输的IP协议、让域名与IP地址匹配的域名系统(DNS)、用于发送邮件的简单邮件传输协议(SMTP)等 W3C简介 W3C是英文World Wide Web Consortium的缩写, 翻译过来就是W3C理事会或万维网联盟, W3C是全球互联网最具权威的技术标准化组织. W3C于1994年10月在麻省理工学院计算机科学实验室成立。创建者是万维网的发明者Tim Berners-Lee W3C负责web方面标准的制定,像HTML、XHTML、CSS、XML的标准就是由W3C来定制的。 Tim Berners-Lee(蒂姆·伯纳斯-李),万维网之父、html设计者、w3c创始人百度百科 网页的固定格式 1.编写网页和写信一样都有一套规范和要求, 这套规范和要求中规定了写信的固定格式 2.写信基本结构 敬爱的江哥: 您好! 正文正文正文正文正文正文正文正文正文正文正文正文 正文正文正文正文正文正文正文正文正文正文正文正文 此致 敬礼! 你的朋友 伊健 2066年6月6日 3.编写网页的步骤: 3.1.新建一个文本文档 3.2.利用记事本打开 3.3.编写THML代码 3.4.保存并且修改纯文本文档的扩展名为.html 3.5.利用浏览器打开编写好的文件 4.网页基本结构: <html> <head> <title></title> </head> <body> </body> </html> 5.通过观察我们发现, HTML基本结构中所有的标签都是成对出现的, 这些成对出现的标签中有一个带/有一个不带/, 那么这些不带/的标签我们称之为开始标签, 这些带/的我们称之为结束标签 html标签 作用: 用于告诉浏览器这是一个网页, 也就是说告诉浏览器我是一个HTML文档 注意点: 其它所有的标签都必须写在html标签里面, 也就是写在html开始标签和结束标签中间 head标签 作用: 用于给网站添加一些配置信息 例如: 指定网站的标题 / 指定网站的小图片 添加网站的SEO相关的信息(指定网站的关键字/指定网站的描述信息) 外挂一些外部的css/js文件 添加一些浏览器适配相关的内容 注意点: 一般情况下, 写在head标签内部的内容都不会显示给用户查看, 也就是说一般情况下写在head标签内部的内容我们都看不到 title标签 作用: 专门用于指定网站的标题, 并且这个指定的标题将来还会作为用户保存网站的默认标题 注意点: title标签必须写在head标签里面 body标签 作用: 专门用于定义HTML文档中需要显示给用户查看的内容(文字/图片/音频/视频) 注意点: 虽然说有时候你可能将内容写到了别的地方在网页中也能看到, 但是千万不要这么干, 一定要将需要显示的内容写在body中 一对html标签中(一个html开始标签和一个html结束标签)只能有一对body标签 head内部标签 meta标签 1.为什么会有乱码现象? 因为我们在编写网页的时候没有指定字符集 2.如何解决乱码现象? 在head标签中添加<meta charset="GBK" />, 指定字符集 3.什么是字符集 字符集就是字符的集合, 也就是很多字符堆在一起. 其实字符集很像我们古代的"活字印刷术", 在活字印刷术中就是将很多刻有汉字的小章放到一个盒子中, 然后需要印刷文字的时候再去盒子中取这个小章出来用, 正是因为如此, 所以导致了乱码问题 假设北方人和南方人都拥有装满小章的盒子, 但是南方人和北方人在盒子中存储小章的顺序不太一样, 那么这个时候如果北方人和南方人都需要去取"李"字, 在南方人记忆中李字在第6个盒子的第6行的第6列中(666), 在北方人的记忆中李字在第8个盒子的第8行的第8列中(888). 那么此时如果让一个南方人去北方人的盒子中取"李"字的小章, 必然找不到,, 所以就导致了乱码问题 这个地方北方人的存储小章的盒子和南方人存储小章的盒子就对应网页中指定的字符集, 在网页中我们常见的字符集有两个GBK/UTF-8, GBK就对应北方人存储的盒子, UTF-8就对应南方人存储的盒子 所以在网页中指定字符集的意义就在于告诉浏览器我用的是哪个盒子, 你应该如何去查找才能找到对应的正确的内容 4.GBK(GB2312)和UTF-8区别 GBK(GB2312)里面存储的字符比较少, 仅仅存储了汉字和一些常用外文 体积比较小 UTF-8里面存储的世界上所有的文字 提交比较大 5.那么在企业开发中我们应该使用GBK(GB2312)还是UTF-8呢? 如果你的网站仅仅包含中文, 那么推荐使用GB2312, 因为它的体积更小, 访问速度更快 如果你的网站除了中文以外, 还包含了一些其它国家的语言 , 那么推荐使用UTF-8 懒人推荐: 不管三七二十一, 一律写UTF-8即可 6.注意点: 在HTML文件中指定的字符集必须和保存这个文件的字符集一致, 否则还是会出现乱码 所以仅仅指定字符集不一定能解决乱码问题, 还需要保存文件的时候, 文件的保存格式必须和指定的字符集一致才能保证没有乱码问题 HTML标签 HTML标签分类 单标签 只有开始标签没有结束标签, 也就是由一个<>组成的 <meta charset="UTF-8" /> 双标签 有开始标签和结束标签, 也就是由一个<>和一个</>组成的 <html> </html> HTML标签关系分类 并列关系(兄弟/平级) <head> </head> <body> </body> 嵌套关系(父子/上下级) <head> <meta charset="UTF-8" /> <title>百度一下,你就知道123</title> </head> DTD文档声明 什么是DTD文档声明? 由于HTML有很多个版本的规范, 每个版本的规范之间又有一定的差异. 所以为了让浏览器能够正确的编译/解析/渲染我们的网页, 我们需要在HTML文件的第一行告诉浏览器, 我们当前这个网页是用哪一个版本的HTML规范来编写的. 浏览器只要知道了我们是用哪一个版本的规范来编写之后, 它就能够正确的编译/解析/渲染我们的网页 DTD文档声明格式: <!DOCTYPE html> 注意事项: <!DOCTYPE>声明必须是 HTML 文档的第一行,位于 <html> 标签之前 <!DOCTYPE> 声明不是 HTML 标签 <!DOCTYPE> 声明没有结束标签 <!DOCTYPE> 声明对大小写不敏感 这个声明浏览器会看, 但是并不是完全依赖于这个声明, 浏览器有一套自己的默认的处理机制 不写也能运行 H5网页里面用H4也能运行 HTML5之前有2大种规范, 每种规范中又有3小种规范 大规范 小规范 HTML Strict (严格的) HTML Transitional(过度的,普通的,宽松的) HTML Frameset(带有框架的页面) XHTML Strict (严格的) XHTML Transitional(过度的,普通的,宽松的) XHTML Frameset(带有框架的页面) HTML的DTD文档声明和XHTML的DTD文档声明有何区别? XHTML本身规定比如标签必须小写、必须严格闭合、必须使用引号引起属性等等, 而HTML会更加松散没有这么严格 Strict表示严格的, 这种模式里面的要求更为严格.这种严格主要体现在有一些标签不能使用 例如font标签/u标签等 font标签可以修改一个文本的字号、颜色、字体,但这和HTML的本质有冲突,因为HTML只能负责语义,不能负责样式,而font标签是用于修改样式的,所以在Strict中是不能使用font标签 u标签可以给一个文本加上下划线,但这和HTML的本质有冲突,因为HTML只能负责语义,不能负责样式,而u标签是用于添加下划线是样式.所以在Strict中是不能使用u标签 Transitional表示普通的, 这种模式是没有一些别的要求 例如可以使用font标签、u标签等 但是在企业开发中不会使用这些标签,因为这违背了HTML的本质, 而是将这些标签作为css的钩子使用 Frameset表示框架, 在框架的页面使用 后面学到框架/NodeJS 再做详细了解 常见的DOCTYPE有如下几种 HTML4.01: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> XHTML 1.0 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> HTML5: <!DOCTYPE html> 有这么多规范我们学习过程中到底使用哪一种比较合适呢? 无论是HTML还是XHTML,过去企业级开发中用的比较多的大部分都是Transitional类型的文档声明 但是HTML5的时代已经到来,以上6中规范仅仅作为了解, 以后都用HTML5类型的文档声明, HTML5向下兼容(求此刻WC3心里阴影面积) 目前国内一线网站都更新到了HTML5的文档声明, 所以后续授课也是全程使用HTML5的文档声明 www.baidu.com (B) www.taobao.com (A) www.qq.com (T) www.sohu.com(大奇葩) HTML和XHTML、HTML5区别 在HTML的早期发展中,大部分标准都是所谓的retro-spec,即先有实现后有标准。在这种情况下,HTML标准不是很规范,浏览器也对HTML页面中的错误相当宽容。这反过来又导致了HTML开发者写出了大量含有错误的HTML页面 html语言本身有一些缺陷(例如: 内容和形式不能分离;标签单一;数据不能复用等等),随着xml的兴起人们希望xml来弥补html的不足,但是目前有成千上万的网页都是用html编写的,所以完全使用xml来替代html还为时过早,于是W3C在2000年推出了xhtml1.0, 建立xhtml的目的就是实现从html向xml的过度 为了规范HTML,W3C结合XML制定了XHTML 1.0标准,这个标准没有增加任何新的标签,只是按照XML的要求来规范HTML,并定义了一个新的MIME type application/xhtml+xml。W3C的初衷是要求浏览器对这个MIME type实行强错误检查,如果页面有HTML错误,就要显示错误信息。但是由于已有的web页面中已经有了大量的错误,很多开发者拒绝使用新的MIME type。W3C不得已,在XHTML 1.0的标准之后增加了一个附录C,允许开发者使用XHTML语法来写页面,同时使用旧的MIME type,application/html,来分发页面 W3C随后在XHTML 1.1中取消了附录C,即使用XHTML 1.1标准的页面必须用新的MIME type来分发。于是这个标准并没有很多人采用 有了XHTML的教训,W3C在制定下一代HTML标准时(HTML5),就将向后兼容作为了一个很重要的原则。HTML5确实引入了许多新的特性,但是它最重要的一个特性是,不会break已有的网页。你可以将任何已有的网页的第一行改成<!DOCTYPE html>,它就成也一个HTML5页面,并且可以照样在浏览器里正常的展示。 简而言之 HTML语法非常宽松容错性强; XHTML更为严格,它要求标签必须小写、必须严格闭合、标签中的属性必须使用引号引起等等; HTML5是HTML的下一个版本所以除了非常宽松容错性强以外,还增加许多新的特性 .htm 和 .html扩展名区别 DOS操作系统(win95或win98)下只能支持长度为3的后缀名,所以是htm 但在windows后缀长度可以大于3位,所以windows下无所谓htm与html,html是为长文件的格式命名的 所以htm是为了兼容过去的DOS命名格式存在的 学习交流方式: 1.微信公众账号搜索: 李南江(配套视频,代码,资料各种福利获取) 2.加入前端学习交流群: 302942894 / 289964053 / 11550038