c++Learning02

引用数组是不合法的,引用不是对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
////结构体的大小
//
////struct myStr {
////// int c;
////// double a;
////// char b; 24
////
//// double a;
//// char b;
//// int c;// 16
//// void go(){
//// cout<<'h'<<endl;
//// };
////};
////
//////结构体的存储分配
////
////
//////如果sizeof一个引用,这个sizeof直接作用于引用的对象
////
////
////
////struct myStr1 {
////// int c;
////// double a;
////// char b; 24
////
////// double & a;
////// char & b;
//// int & c;// 16
//// void go(){
//// cout<<'h'<<endl;
//// };
////};
//////sizeof类中的引用时,是指针的值(64位是8)
////int main(){
//// cout<< sizeof(myStr1)<<endl; //8
//// int a = 10;
//// int &ra = a;
//// cout<< sizeof(ra)<<endl; //4
////
//// return 0;
////}
//
////大小是8,因为函数储存在代码区,不计入sizeof
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
////newdelete全局重载
//
//
////在维护大型项目中需要
////局部new其实还是调用了全局new,分配内存还是全局new完成
////类内部的new没有完成分配内存的工作,只是做了一个劫持
//
////class MyClass{
////
////};
////int main(){
//// cout<<"size of myclass: "<< sizeof(MyClass)<<endl;
//// //sizeof(MyClass) 1,表明存在
//// return 0;
////}
////空类占一个字节
//
//
////class MyClass{
//// int a;
////};
////int main(){
//// cout<<"size of myclass: "<< sizeof(MyClass)<<endl;
//// //sizeof(MyClass) 4
//// return 0;
////}
//
////class MyClass{
//// int a;
//// MyClass(){
//// a = 10;
//// }
////};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
////void *operator new (size_t size){
//// cout<<"全局new重载"<<endl;
//// if(size == 0){
//// return 0;
//// }
//// void *p = malloc(size);
//// return p;
////}
////
////void operator delete(void * p){
//// cout<<"全局delete重载"<<endl;
//// free(p);
////}
////
////int main(){
//// int * p = new int(4);
//// delete p;
////// MyClass * CC = new MyClass();
//// return 0;
////}
//
////创建的时候是局部new---全局new---malloc---调用构造函数
////销毁的时候是
//
//
//class tansheng {
//public:
// int *p;
// int length;
//public:
// tansheng()//构建的时候初始化
// {
// std::cout << "构造函数谭胜被创建" << std::endl;
// }
//
// ~tansheng()//删除的时候释放内存
// {
// std::cout << "析构函数谭胜被销毁" << std::endl;
// }
//
// static void *operator new(size_t size) {
// std::cout << "局部new0" << std::endl;
// tansheng *ptemp = ::new tansheng;//劫持
// std::cout << "局部new1" << std::endl;
// return ptemp;
// }
// static void operator delete(void *p) {
// std::cout << "局部delete" << std::endl;
// ::delete p;//::全局
// }
//
//
// //添加局部new用于数组上
// static void *operator new[](size_t size) {
// std::cout << "局部数组new0" << std::endl;
// void * p = operator new(size);
// std::cout << "局部数组new1" << std::endl;
// return p;
// }
// static void operator delete[](void *p) {
// std::cout << "局部数组delete" << std::endl;
// return operator delete(p);
// }
//};
//
//void *operator new (size_t size){
// cout<<"全局new"<<endl;
// if(size == 0){
// return 0;
// }
// void *p = malloc(size);
// return p;
//}
//
//void operator delete(void * p){
// cout<<"全局delete"<<endl;
// free(p);
//}
//
//int main2(){
//
//
// tansheng * p = new tansheng();
//
//
//
// delete(p);
// return 0;
//}
//
///*
// *
//局部new对象被创建0
//全局new重载
//构造函数谭胜被创建
//局部new对象被创建1
//构造函数谭胜被创建
//析构函数谭胜被销毁
//局部delete对象被销毁
//全局delete重载
// *
// *
// * */
////构造函数谭胜被创建 有两次为什么
//
//
////全局的new 和delete监视所有的
//
//
////如果是数组,全局重载
//
//void *operator new[](size_t size){
// cout<<"数组全局new"<<endl;
// return operator new (size);//每个对象挨个调用
//}
//void operator delete[](void*p){
// cout<<"数组全局delete"<<endl;
// return operator delete(p);//每个对象挨个调用已经重载好的delete
//}
//int main3(){
//
// tansheng * p = new tansheng[5];
//// tansheng * p1 = new tansheng[5]();是什么意思?
//
//
// delete []p;
//// delete p;//对于基本类型可以这样,但是非基本类型不可以
//
//}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
////大数乘法与结构体
//
//
//
////C语言结构体和C++结构体的区别
//
//
//
struct MyStruct1 {
int num1;
int num2;

void gogo() {

}
};

//可以继承
//可以有权限控制
//
int main66() {
// MyStruct1 s1;
// struct MyStruct1 ss;
//有没有struct
return 0;
}



//函数模板以及自动数据类型


//T是通用数据类型
template<typename T>
T Max(T *p, const int n) {
T maxdata(p[0]);
for (int i = 0; i < n; ++i) {
if (maxdata < p[i]) {
maxdata = p[i];
}
}
return maxdata;
}


//int getAll(int n,...){
//
//}


//处理可变长参数需要使用
#include <iostream>
#include "cstdarg"
template<typename NT>
//参数至少要有一个是模板类型?
//NT date1没有什么用处就是做一个标志
NT sum(int num,NT date1,...) {
va_list arg_ptr;//参数列表指针
va_start(arg_ptr,num);//限定从num开始,限定多少个参数
NT sumres(0);
for (int i = 0; i < num; ++i) {
sumres+=va_arg(arg_ptr,NT);
}
va_end(arg_ptr);//结束
return sumres;
}

int main32() {
std::cout<<sum<int>(5,1,2,3,4,5)<<std::endl;
std::cout<<sum<double>(5,1.2,2.2,3.2,4.2,5.3)<<std::endl;

}


//auto与函数模板

//函数参数不能使用auto,而
//auto get(int data,double bigdata)-> decltype(data*bigdata){
//
//}



//自动数据类型,根据实际推导出类型
template<class T1, class T2>//根据类型获取类型
auto get(T1 t1, T2 t2) -> decltype(t1 * t2) {

};


//宽字符
//本地
#include "locale"
int main12312(){
setlocale(LC_ALL,"chs");//设置本地化
wchar_t *p = L"123456";
wchar_t *p1 = L"你好23456";
std::wcout<<p<<std::endl;
std::wcout<<p1<<std::endl;


return 1;
}

//inline函数
//和define效果一样
//但是define无法保证类型安全

#include "iostream"
#define GETX3(N) N*N*N
//内联函数,会在内部展开,相对于define类型安全
inline int getx3(int n){
return n*n*n;
}

//inline使用模板化
template<typename T>
inline T getX2(T t) {//类型不匹配会出错,不是单纯的替换
return t * t;
}

//inline只是对编译器的建议
/*
* 一般情况下我们队内联函数做出以下限制
* 不能有递归
* 不能包含静态数据
* 不能包含循环
* 不包含数组
* 不包含goto和switch
*若一个内联函数不满足以上限制,编译器会把它当做普通函数
*
*
*
*
* */
//
//int main(){
// std::cout<<GETX3(1+2)<<std::endl;
// //7------1+2*1+2*1+2
// return 1;
//}





//c和c++的不同

/*
* 常量表示方法不同
C 语言不支持引用的概念,而 C++支持
注释不同,C89 不支持单行注释
(++i) ++在 C 中不合法
(a=3) =4 在 C 中不合法,而在这种情况下c++会将(a=3)转换成左值 a=4 c++检测右值在内存中有实体,会自动转换成左值.c语言不会把左值转换为右值

不能在 for 循环头部定义变量
*
*
*
* */



//c语言中全局变量有声明和定义之间的区别

//c++中这样是错误的,全局变量没有声明的定义之间的区别
//int c;
//int c;
//int c;

//static全局变量
//c语言可以,c++错误
//静态全局变量没有声明的定义之间的区别
//static int vv;
//static int vv;


//因为c++是强类型的,所以函数返回值必须要有类型main()

int main2222(){
int a =3;
(++a)++;
// (a+1)++; a+1在内存中没有实体 在寄存器中计算出来
return 0;
}

//寄存器变量
int main33(){
register int a(10);
//register做了优化,其实也是建议,可以取地址
//c中是不行的
std::cout<<&a<<std::endl;
}

//c++编译器的宽泛,c中就不可以
//为了修改源代码,后面留下拓展
//占位
void test(int a , double, double){

}


//delete以后尽量设置为NULL
int main(){
int *p = new int(5);
// delete p;
// p = NULL;防止重复删除
// delete p;
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//
// Created by 李克兢 on 2018/6/11.
//
#include <stdio.h>
#include "stdlib.h"
#include "string.h"

//初始化的时候必须这样,加上关键字
//结构体成员不能有函数
//没有公有私有
//没有继承
struct MyStruct {
int num1;
int num2;
};
//有小数该如何处理呢
//除法如何处理呢



//getBigData有点小问题
void getBigData(char *dataa, char *datab) {
int lengthA = strlen(dataa);
int lengthB = strlen(datab);
int *pres = (int *) malloc(sizeof(int) * (lengthA + lengthB));
/*初始化数组*/
int lengthR = lengthA + lengthB;
// for (int i = 0; i < lengthR; ++i) {
// pres[i] = 0;
// }
memset(pres, 0, sizeof(int) * (lengthA + lengthB));
//实现了累乘
for (int i = 0; i < lengthA; ++i) {
for (int j = 0; j < lengthB; ++j) {
//预留一位
pres[i + j + 1] += (dataa[i] = '0') * (dataa[j] = '0');
}
}
//进位
for (int k = lengthR - 1; k > 0; --k) {
if (pres[k] > 10) {
pres[k] %= 10;
pres[k - 1] += pres[k] / 10;
}
}
//打印
int i = 0;
while(pres[i] == 0){
i++;
}
char * cres = (char*)malloc(sizeof(char)*(lengthR));
int l;
for (l= 0; l < lengthR; ++l) {
cres[l] = pres[i] +'0';
i++;
}
cres[l] = '\0';

printf("打印:%s\n",cres);
// for (int l = 0; l < strlen(cres); ++l) {
// printf("%s",cres[l]);
// }
}

int main22() {
// struct MyStruct st;
char str1[100] = {0};
// char str1 [100] = {0};初始化?
char str2[100] = {0};
scanf("%s%s", &str1, &str2);
printf("%s\n", str1);
printf("%s\n", str2);
getBigData(str1,str2);
printf("over11");
return 0;
}

c和指针13

高级指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
int *f();
// //*f()是如何求值的,首先执行的是函数调用操作(因为()优先级高于*),因此 f 是一个返回值为指向整型的指针的函数
// //用于声明比那辆的表达式和普通的表达式在求值所使用的规则相同
int (*f)();
// //f 成为一个函数指针,这个函数返回一个 int 类型
int *(*f)();
// //f 成为一个函数指针,这个函数返回一个 int* 类型
//


//// int f[10];
//
//// int f[];
//
// //如果链接属性时 external 或者是作用函数的参数,即使它们在声明时未注明长度,也是合法的



//// int *f[10];
// //[]的优先级高, f 是一个数组,里面存着 int*
//// int f()[10];
// //f是一个函数,返回值是int数组 错误!返回值只能是标量值

//// int f[10]();
// //f是一个数组,元素类型是返回值为 int 类型的函数 错误!数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度
//// int (*f[10])();
// //左括号----- f 是一个元素为某种类型的指针的数组 元素的类型是函数指针,它指向的函数的返回值是 int 类型
//// int *(*f[])();
// //左括号----- f 是一个元素为某种类型的指针的数组 元素的类型是函数指针,它指向的函数的返回值是 int* 类型
//


////函数指针最常用的两个用途是转换表和作为参数传递给另一个函数
//
////!!!简单声明一个函数指针并不意味着它马上就可以使用.和其它指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数
////int f(int);
////int (*pf)(int) = &f;
// //在函数指针初始化之前具有 f 的原型是很重要的
// //int (*pf)(int) = &f;中的&可以省略,因为函数名被使用时总是由编译器把它转换成函数指针
//
//
// printf("Hello, World!\n");
// return 0;
//}

//查找
//#include <stdio.h>
//#include <node.h>
//Node *
//search_list (Node * node void const * value,
//int (*compare) (void const *,void const *)){
// while (node != NULL){
// if (compare(&node->value,value) == 0){
// break;
// }
// node = node->link;
// }
// return node;
//}
////和类型无关的查找
//int
//compare_ints(void const *a,void const *b){
// if (*(int *)a == *(int *)b){
// return 0;
// } else{
// return 1;
// }
//}

//转换表

//把具体操作和选择操作分开是一种良好的设计方案


//double add(double,double);
//double sub(double,double);
//double mul(double,double);
//double div(double,double);
//double (*oper_func[])(double, double) = {
// add,sub,mul,div,
//};
//result = oper_func[oper](op1,op2);

//如果你怀疑转移表有问题,可以在那个函数调用之前和之后各打印一条信息.如果被调用函数不返回,人们很难认识到程序某个部分的失败可以是位于程序中相隔甚远的且不相关部分的一个转移表错误所引起的.
//保证转移表所使用的下标位于合法的范围!


//字符串常量出现于表达式中时,它的值是一个指针常量
//"xyz"+1;结果是个指针,指向字符串中的第二个字符 y

c++Learning01

char和 w_char的区别

w_char是Unicode 字符的数据类型

1
typedef unsigned short wchar_t;

一般在在头文件中有这样的定义

1
typedef wchar_t WCHAR

为了让编译器识别 Unicode 字符串,需要在字符串前加上 L

1
wchar_t * a(L"china");

命名空间

命名空间的作用是解决变量,函数重名问题

没有命名的命名空间(匿名命名空间)可以直接使用,此时编译器会在内部为这个命名空间生成一个唯一的名字,而且还会自动生成一句 using 语句

1
2
3
4
5
namespce {
char c;
int i;
double d;
}

等价于

1
2
3
4
5
6
namespace __UNIQUE_NAME_ {
char c;
int i;
double d;
}
using namespace __UNIQUE_NAME_;

注意:命名空间都是具有external 连接属性的,只是匿名的命名空间产生的UNIQUE_NAME在别的文件中无法得到,这个唯一的名字是不可见的.
C++ 新的标准中提倡使用匿名命名空间,而不推荐使用static,因为static用在不同的地方,涵义不同,容易造成混淆.另外,static不能修饰class.

命名空间还可以使用别名

1
namespace a = mySpace;

命名空间可以使用嵌套,使用嵌套的命名空间时需要用到::操作符

命名空间可以拓展,用于软件的迭代式开发
一般不在命名空间中定义函数,而是声明一个函数指针,这个函数指针起到一个接口的作用
namespace 所有数据都是共有,不能加 private 修饰符,全是共有的

using 的作用域,从上往下,必须在命名空间的后方,

当 using 多个时,如果变量重名则命名空间冲突,会出现不明确错误.
命名空间如果在块语句内部using,则它的作用域在块结束后同时结束

###命名空间和全局变量的区别

c中全局变量可能会被局部变量屏蔽,在c++ 中可以使用::,”::”前面不加命名空间等价于取全局变量

函数重载是设计了委托,根据参数不同委托到不同的地址执行不同的函数

函数处理参数从右到左
默认参数放在右边,默认参数中间不允许加入不默认的,这样做是因为实参传给系统之后,系统会自左向右与形参进行匹配。如果函数时add(1,2),那么a=1,b=2,c等于多少呢?我们只有把参数全部传递过去之后,系统才能获得c的值,但这样做的话,为什么还要设置函数的默认参数呢?

所以编译系统不允许程序员这么做,因为这么做是毫无意义的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void printf(int c,int a = 5){
std::cout<<a<<endl;
}

void printf(int c){
std::cout<<c<<endl;
}
int main(){
// printf(6);
//此时会有歧义,所以要用函数指针调用
void (*p1)(int c, int a) = printf;
// void (*p2)(int c) = printf;
// void (*pp)(int c) = printf;
p1(6,7);
//参数需要补全,函数指针没有默认参数,需要全部输入数据,如果想使用默认参数特性最好使用命名空间去解决这个问题
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
void printf(int c,int a = 5){
std::cout<<a<<endl;
}

void printf(double c){
std::cout<<c<<endl;
}
int main(){
printf(6);
return 0;
}

这种情况下不冲突,如果需要输入的参数类型不一样,数目不一样,顺序不一样,则不会出现冲突

1
一个引用符引用左值,两个引用符引用右值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
int& getA(){
int a = 10;//a在栈上,
int&ra = a;
return ra;
}
int main(){
int & ra = getA();
std::cout<<ra<<endl;
std::cout<<"hello"<<endl;
std::cout<<ra<<endl;
/*
* 10
* hello
* 32767
* */
return 1;
}

int* & getA(){
int * p = new int(5);
int * &rp = p;
return rp;
}
//返回引用指针
int main(){
int *& ra = getA();
// int *p = ra;使用一个变量接着,
int * p1 = ra;


std::cout<<*ra<<endl;
std::cout<<*p1<<endl;
std::cout<<"hello"<<endl;
// delete (p1);
std::cout<<*p1<<endl;
std::cout<<*ra<<endl;
//c++删除内存后禁止访问,而且指针
delete(*p1);
std::cout<<p1<<endl;
std::cout<<*p1<<endl;
std::cout<<*ra<<endl;
/*
* 10
* hello
* 32767
* */
return 1;
}

在栈上面的内存会在函数调用完就清除掉

1
2
3
4
5
6
7
8
9
10
//const
//c语言中const 变量可以被指针间接改变
int main() {
const int num = 5;
int *p = &num;
*p = 3;

printf("%d\n",*p);
return 0;
}

c++中不行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(){
const int num = 5;
int *p = (int *)&num;
*p = 2;
std::cout<<num<<endl;
}
//所得结果仍然是5,因为int *p = (int *)&num;()强转是C语言的转换方式
//虽然编译成功但是c++不会执行这段代码?还是执行了但是维护了一个常量表,通过 VS 反编译看看吧
int main(){
const int num = 5;
int *p = const_cast<int*>(&num);
*p = 2;//编译通过,但是貌似并没有起作用
std::cout<<num<<endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main(){
int a = 10;
const int b =5;
// int const * p1(&a);//经常使用这种形式,用于限定权限(所以不会严格的检查类型),只能读不能写,所以可以操作
// const int * p2(&a);
// *p1 = 3;
// *p2 = 6;
// const在*左边,指向常量的指针变量,常量指针,不能改变指向的值
int * const p1(&a);
//指向变量的指针
int * const p2(&b);//cannot initialize a variable of type 'int *const' with an rvalue of type 'const int *'
//&b是 const int * p2是一个指向变量的指针常量

// const int * p2;
//
// int * const pp;
// const int const * p3;


}

int select(const int &rum){
// rum--;
return rum;
}


int main(){
const int num1 =10;
int num2 = 20;
std::cout<<select(num2)<<endl;
return 1;
}

//真实的内存已经被修改,从常量表里获取了值
//const 编译器会优化,不会从内存读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 int main(){
//不能 delete两次

// int a[10];
// int * p = a;
// int i = 0 ;
// for (auto date:p){
//
// }
int *p = new int[10];
int i = 0;
for (int j = 0; j < 10; ++j) {
std::cout<<p[j]<<endl;
}
std::cout<<endl;
for (int j = 0; j < 10; ++j) {
p[j] = i++;
std::cout<<p[j]<<endl;
}
std::cout<<endl;
std::cout<<p<<endl;
delete []p;//删除数组的空间,基本数据类型可以直接 delete, 复杂类型必须[]
std::cout<<p<<endl;
// for (int j = 0; j < 10; ++j) {
// std::cout<<p[j]<<endl;
// }

return 1;
}
1
2
3
4
5
6
7
8
int main(){
int *p = new int[80];
int (*px) [10] = (int (*) [10])p;
//new只能分配线性

// px[i][j];
return 1;
}

删除数组的空间,基本数据类型可以直接 delete, 复杂类型必须[]

打砖块-1

#打砖块-1

##Tutorial部分

首先完成功能”一个通过’a’和’d’控制左右移动的图片”

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style media="screen">
canvas{
border : 1px black solid;
}
</style>
</head>
<body>
<canvas id="id-canvas" width="400" height="300">
</canvas>
<script>
var x= 100
var y =200
var speed = 5
var log = console.log.bind(console)
var canvas = document.querySelector("#id-canvas")
var ctx = canvas.getContext('2d')
var leftPressed = false
var rightPressed = false
var img = new Image(); // 创建一个<img>元素
img.src = 'res/paddle.png'; // 设置图片源地址
img.onload = function(){
ctx.drawImage(img,x,y)
}
window.addEventListener("keydown",function (event) {
log(event.key)
switch (event.key){
case 'a':
leftPressed = true
break
case 'd':
rightPressed = true
break
default:
break
}
})
window.addEventListener("keyup",function (event) {
log(event.key)
switch (event.key){
case 'a':
leftPressed = false
break
case 'd':
rightPressed = false
break
default:
break
}
})
setInterval(function () {
//update x y
if (leftPressed){
x -= speed
}
else if (rightPressed){
x += speed
}
//draw
ctx.clearRect(0,0,400,300)
ctx.drawImage(img,x,y)
},1000/30)
</script>
</body>
</html>

然后对以上代码进行优化抽象

  1. 定义一个入口_main(),将所有代码放入

  2. 将代码拆成几个函数,

    • paddle作为一个独立的个体,它的所有属性方法应该放到一起.

    • 创建 guagame, 控制所有注册点击事件和setInterval函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var getImageFromStr = function(path) {
var img = new Image()
img.src = path
return img
}
//将常用的代码块抽取成为函数
Paddle = function () {
var o = {
x: 100,
y: 200,
speed: 5,
}
o.image = getImageFromStr('res/paddle.png')
o.moveLeft = function () {
o.x -= o.speed
}
o.moveRight = function () {
o.x += o.speed
}
return o;
}

paddle 自己的逻辑应该让自己控制,所以

1
2
3
4
5
6
if (leftPressed){
x -= speed
}
else if (rightPressed){
x += speed
}

应该用对象自己的方法moveLeft和moveRight替换

将事件注册, setInterval 、update和 draw 整体封装成一个GuaGame,作为控制对象

控制对象里面需要有

1
2
var canvas = document.querySelector("#id-canvas")
var ctx = canvas.getContext('2d')

把setInterval放在GuaGame中

1
2
3
4
5
6
7
8
setInterval(function(){
//update
g.update()
//clear

//draw
g.draw
},1000/30)

而update中的一些事件是通过注册来的,此时可以使用以下的巧妙方法完成

首先

1
2
3
4
5
6
7
var g = {
actions: {},
keydowns: {},
}
g.registerAction = function(key, callback) {
g.actions[key] = callback
}

然后在setInterval中遍历并处理事件

1
2
3
4
5
6
7
8
var actions = Object.keys(g.actions)
for (var i = 0; i < actions.length; i++) {
var key = actions[i]
if(g.keydowns[key]) {
// 如果按键被按下, 调用注册的 action
g.actions[key]()
}
}

##myWay部分

完成了基本环节的搭建,对象只做了挡板

思考:1. 用if语句根据条件执行方法的,可以考虑改用actions[key]()映射

1
actions[action] && actions[action](event)

本篇中使用了actions: {}, keydowns: {},两个对象来完成功能,主要原因是在于回调方法是在setInterval中调用的,而回调方法是否调用的判断和按键有关,所以需要两个对象完成

JavaScript忍者秘籍-第七章

面向对象与原型

###理解原型

在 JS 中,可以通过原型实现继承

原型的概念很简单.当查找属性时,若对象本身不具有该属性,则会查找原型上是否有该属性.

通过操作符 in 我们可以测试对象是否拥有某属性

Object.setPrototypeOf(a,b)方法将 b 对象设置为第一个对象的原型,对象的原型属性是内置属性(__proto__)

每个对象都可以有一个原型,每个对象的原型也可以拥有一个原型,以此类推.形成了原型链

对象构造器与原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Ninja() {
}
console.log(Ninja) //[Function: Ninja]
console.log(typeof Ninja.prototype) //object
//Ninja.prototype 是对象 函数的prototype是对象
Ninja.prototype.swingSword = function () {
return true
}
console.log("-------")
console.log(Ninja) //[Function: Ninja]
console.log(Ninja.prototype) //Ninja { swingSword: [Function] }

//ninja1=Ninja() ninja1 undefine 加上()之后ninja1的赋值是函数的返回值
//ninja1=Ninja ninja1 [Function: Ninja]

console.log("-------")
const ninja1 = Ninja()
console.log(ninja1)
const ninja2 = new Ninja()
console.log("--112-----")
console.log(ninja2.__proto__) //Ninja { swingSword: [Function] }
console.log(Ninja.prototype) //Ninja { swingSword: [Function] }
console.log(Ninja) //[Function: Ninja]
console.log(Ninja.prototype.constructor)//[Function: Ninja]
// console.log(Ninja.prototype === ninja2.__proto__)
// console.log(Ninja.prototype.__proto__.__proto__)
//Ninja.prototype === ninja2.__proto__
//函数对应的prototype是 new 出来的对象的__proto__
//Ninja.prototype.__proto__ == {}
//Ninja.prototype.__proto__.__proto__ == null
console.log(ninja2)
console.log(ninja2.swingSword)
console.log(ninja2.swingSword())

通过 new 操作符调用函数意味着作为构造器调用

  • 每一个函数都具有一个原型对象(Ninja.prototype)
  • 每一个函数的原型都具有一个 constructor 属性,该属性指向函数本身(Ninja.prototype.constructor === Ninja)
  • constructor对象的原型(NInja.prototype)设置为新创建的对象的原型

我们创建的每一个函数都具有一个新的原型对象.最初的原型对象只有一个属性,即 constructor属性.该属性指向函数本身

实例属性和原型属性之间的区别

实例会隐藏原型中与实例方法重名的方法

实例中可以查找到的属性也不会查找原型

JS 动态特性的副作用

对象与函数原型之间引用关系是在对象创建时建立的,如果原型在代码过程中被篡改,新创建的对象将引用新的原型,原来旧的对象保持着原有的原型的引用

1
var log = console.log.bind(this)

因为后面大量使用了console.log()所以可以将其简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var log = console.log.bind(this)

function Ninja() {
this.swung = true
}
const ninja1 = new Ninja()
Ninja.prototype.swingSword = function () {
return this.swung
}
log(ninja1.__proto__) //Ninja { swingSword: [Function] }
Ninja.prototype = {
pierce : function () {
return true
}
}
log(ninja1.__proto__) //Ninja { swingSword: [Function] }
const ninja2 = new Ninja()
log(ninja2.__proto__) //{ pierce: [Function: pierce] }

函数的原型可以被任意调换,而已经构建的实例引用旧的原型

通过构造函数实现对象类型

通过使用 constructor 属性,我们可以访问创建该对象时使用的函数.这个特性可以用于类型校验

1
2
3
4
5
function Ninja() {
}
const ninja1 = new Ninja()
log(ninja1 instanceof Ninja)
log(ninja1.constructor === Ninja)

instanceof:提供了一种用于检测一个实例是否由特定构造函数创建的方法

因为所有实例对象都可以访问 constructor 属性所以可以使用 const ninja2 = new ninja1.constructor()来初始化

实现继承

我们真正想要实现的是一个完整的原型链.创建这样的原型链的最佳技术方案是一个对象的原型直接是另一个对象的实例:

SubClass.prototype = new SuperClass()

通过执行 instanceof 操作符,我们可以判定函数是否继承原型链上的对象功能

instanceof 操作符的实质是:检查操作符右边的函数的原型是否存在于操作符左边的对象的原型链上

!!!不建议直接使用 Person 的原型对象作为 Ninja 的原型(Ninja.prototype = Person.prototype)这样做会导致在 Person 原型上所发生的所有变化都被同步到 Ninja 原型上(Person 原型与 Ninja 原型是同一个对象)一定会有不良的副作用

重写 constructor 属性的问题

通过设置 Person 实例对象作为 Ninja 构造器的原型时,我们已经丢失了 Ninja 与 Ninja 初始原型之间的关联.这个问题需要修复.

调整属性的配置信息可以使用内置的Object.defineProperty() .

将配置项 enumerable 设为 false, 在 for-in 循环中无法遍历该属性.

在 ES6使用 class 关键字

底层仍然基于原型继承

实现”静态”方法

使用Static关键字

或者:

1
2
3
4
5
6
7
function Ninja() {
}
Ninja.xxxcompare = function (ninja1, ninja2) {
}
const ninja1 = new Ninja()
const ninja2 = new Ninja()
log(ninja1.constructor.xxxcompare()) //找不到的,静态方法

算法-2

题目描述

You have been given a string s, which is supposed to be a sentence. However, someone forgot to put spaces between the different words, and for some reason they capitalized the first letter of every word. Return the sentence after making the following amendments:

  • Put a single space between the words.
  • Convert the uppercase letters to lowercase.

Example

  • For s = "CodefightsIsAwesome", the output should be
    amendTheSentence(s) = "codefights is awesome";
  • For s = "Hello", the output should be
    amendTheSentence(s) = "hello".

Input/Output

  • [execution time limit] 4 seconds (js)

  • [input] string s

    A string containing uppercase and lowercase English letters.

    Guaranteed constraints:
    3 ≤ s.length ≤ 100.

  • [output] string

    The amended sentence.

算法(1)

求字符串中第一个没有重复的字符

###题目描述

Given a string s, find and return the first instance of a non-repeating character in it. If there is no such character, return '_'.

Example

  • For s = "abacabad", the output should be
    firstNotRepeatingCharacter(s) = 'c'.

    There are 2 non-repeating characters in the string: 'c' and 'd'. Return c since it appears in the string first.

  • For s = "abacabaabacaba", the output should be
    firstNotRepeatingCharacter(s) = '_'.

    There are no characters in this string that do not repeat.

Input/Output

  • [execution time limit] 4 seconds (js)

  • [input] string s

    A string that contains only lowercase English letters.

    Guaranteed constraints:
    1 ≤ s.length ≤ 105.

  • [output] char

    The first non-repeating character in s, or '_' if there are no characters that do not repeat.

###解法

  • 暴力解法:

两层循环,很低效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function firstNotRepeatingCharacter1(s) {
for (var i = 0 ; i < s.length ; i++){
var flag = false
for (var j = 0 ; j < s.length ; j++){
if (i == j){
continue
}
if (s.charAt(i) == s.charAt(j)){
flag = true
break
}
}
if (!flag){
return s.charAt(i)
}
}
return '_'
}

因为字母的数目是确定的,其实还可以借助哈希表来做

  • 简单解法:
1
2
3
4
5
6
7
8
9
//只需要一层循环
function firstNotRepeatingCharacter(s) {
for (var i = 0 ; i < s.length ; i++){
if (s.lastIndexOf(s.charAt(i)) == s.indexOf(s.charAt(i))){
return s.charAt(i)
}
}
return '_'
}

难点在于lastIndexOf和indexOf两个 API不知道,一个是求出最后一次出现的索引值, 一个是首次出现的 index, 两个 索引值相同表明该元素只出现了一次

##将数组顺时针旋转90度

题目描述

You are given an n x n 2D matrix that represents an image. Rotate the image by 90 degrees (clockwise).

Example

For

1
2
3
a = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

the output should be

1
2
3
4
rotateImage(a) =
[[7, 4, 1],
[8, 5, 2],
[9, 6, 3]]

Input/Output

  • [execution time limit] 4 seconds (js)

  • [input] array.array.integer a

    Guaranteed constraints:
    1 ≤ a.length ≤ 100,
    a[i].length = a.length,
    1 ≤ a[i][j] ≤ 104.

  • [output] array.array.integer

    解法

1
2
3
4
5
6
7
8
9
10
11
12
function rotateImage(a) {
var n = a.length
var target = []
for (var i = 0 ; i< n;i++){
var row = []
for (var j = 0 ; j< n;j++){
row[j] = a[n-1-j][i]
}
target.push(row)
}
return target
}

仔细观察row[j] = a[n-1-j][i],其实可以看成row[i][j] = a[n-1-j][i],

所以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//评分最高的方法
function rotateImage(a) {
// Transpose
for(var i = 0;i<a.length;i++){
for(var j = 0;j<i;j++){
// Switch a[i][j] and a[j][i]
// With XOR swap
a[i][j] ^= a[j][i]
a[j][i] ^= a[i][j]
a[i][j] ^= a[j][i]
}
}

// Reverse columns
for(var i in a){
a[i] = a[i].reverse()
}

return a
}

值得注意的是:

1
2
3
a[i][j] ^= a[j][i]
a[j][i] ^= a[i][j]
a[i][j] ^= a[j][i]

不使用中间值,交换两个数的值

所有不使用临时变量的思路都是让其中一个变量变成一个a和b都有关系的值这样可以先改变另一个变量值,最后改变原修改的变量值