字符集:字符的集合—ASCII码表
字符编码:字符的种类非常多,需要按照一定的规则对字符进行存储
在写代码的时候,可能会存在不同类型之间的相互操作或者赋值
隐式类型转换:在编译代码时,编译器进行的转换----表现:小类型给大类型赋值
显示类型转换:需要用户自己手动进行—表现:将大类型赋值给小类型----默认情况下编译器会报错-----手动进行
前置 ++ 先加1 ,在使用
后置++ 先使用,在加1
前置 ++和后置++ 单独使用,两个效果一样
public class Practise {
public static void main(String[] args) {
int a = 10;
a++; // 后置++
System.out.println(a); //11
int ret = a++;
System.out.println(a); //12
System.out.println(ret); // 11
//前置++
++a;
System.out.println(a); // 13
ret = ++a; //14
System.out.println(a); //14
System.out.println(ret); //14
a = a++;
System.out.println(a); //14
}
}
在java当中,每个方法在运行时,必须要有自己独立的环境-----栈帧
栈帧:局部变量表、操作数栈
局部变量表:主要用来存储函数的参数以及函数当中定义的局部变量
操作数栈:主要用来存储函数运行时需要用到的中间结果
二进制格式的文件(专门给jvm看)利用javap(反汇编工具)----解析用户可以识别的字节码内容
1. 能存储的数据类型不同
文本文件只能存储char型字符变量。二进制文件可以存储char/int/short/long/float/……各种变量值。
2. 每条数据的长度
文本文件每条数据通常是固定长度的。以ASCII为例,每条数据(每个字符)都是1个字节。进制文件每条数据不固定。如short占两个字节,int占四个字节,float占8个字节
3. 读取的软件不同
文本文件编辑器就可以读写。比如记事本、NotePad++、Vim等。二进制文件需要特别的解码器。比如bmp文件需要图像查看器,rmvb需要播放器……
5.逻辑运算符
逻辑运算符:&& || (二元运算符) !(一元运算符)
该运算符对应的表达式都需要是 boolean 类型的
语法格式:表达式1 && 表达式2
注意:表达式1和表达式2必须要是boolean类型的结果
全真才为真
语法格式:表达式1 || 表达式2
注意:左右表达式必须是boolean类型的结果
有真就为真
语法格式:!表达式
真变假 假变真
data >> n : 将data对应的二进制个数结果 往右移动n位 —>低n位丢弃 高n位补0或者1 data正数:0 data负数:1
data << n : 将高n位丢弃掉,din位补0
原码:数据的二进制格式
反码:将原码取反
补码:反码+1
正数:原码 反码 补码一样
负数:原码 反码 补码不一样
break:结束当前循环 使流程跳出switch语句体,也可以用break语句在循环结构终止本层循环体,从而提前结束本层循环
continue:结束单趟循环 跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环
break表示结束整个循环(break所在的最近的那个循环)
continue表示结束本次循环(continue所在的最近的那个循环)
Scanner sc = new Scanner(System.in); //标准固定输入写法
int data = sc.nextInt(); //要求用户用键盘输入int类型的数据
double data = sc.nextDouble(); //要求用户用键盘输入double类型的数据
String data = sc.nextString(); //要求用户用键盘输入string 类型的字符串
string s = sc.next(); //接受字符串,但是在接收时,遇到空格之后就终止接收。空格之后的内容不会接收
string s = sc.nextLine(); //用来接收字符串,将整行的字符串全部接收
相当于c语言中的函数 将好些条语句打包成一个整体,可以复用
修饰符 返回值类型 方法名字(参数列表){
语句;
}
注意事项:
1.修饰符:可以对一些方法进行一些限定—>谁可以用 修饰符也是可选的
2.返回值类型:方法执行完了之后,是否需要将结果带出去
注意: 有些方法有返回值-----在定义方法时必须要给出返回值类型有些方法没有返回值----在定义方法时,返回值类型的位置使用 void代替
3.方法的名字:注意按照小驼峰规则命名
4.参数列表: 有些方法可能没有参数-----注意方法名之后的()不能省略
减少重复性,提供代码的利用率,可维护性
Debug:让程序以调试的方式运行:
程序运行时遇到断点会停下来—单步执行
并且在单步执行过程当中可以查看变量的值
F8:逐步往下运行,遇到方法,不会静茹到方法当中 直接将方法执行完毕
F7:逐行往下运行,遇到方法,会进入到方法中
调用方法–传递参数–找到方法地址–执行被调用方法的方法体–被调方法结束返回–回到主调方法继续往下执行
方法的形参相当于数学函数中的自变量
注意:在java中传参都是以值的方式传递的
形参时实参的一份拷贝,在方法中将形参修改了之后,对实参没有任何影响
每个方法在运行时,必须要有自己的运行环境,该运行环境----栈帧
栈帧实际是一个数据结构将方法运行时所需要的一些该方法相关的信息组织起来—比如:形参 局部变量
栈帧随着方法的执行而创建,随着方法的结束而销毁,栈帧一生与方法的一生是一样的
虚拟机栈—一块特殊的内存空间
类以统一的方式处理不同类型数据的一种手段
重载:自然语言中的一词多义 方法名必须相同,参数列表必须不相同(个数 类型 类型次序)
1.构成重载方法名字是相同的,jvm是如何知道要调用那个方法?
对于重载方法到底要调用哪一个是在编译期间就确定好的
在编译时,编译器会对传递的实参类型进行推演 add(10,20);
推演结果:int int 然后再类中找两个参数都是int类型的方法add(1.2,1.1);
推演结果为 double double
然后在类中中找两个参数都是double类型的add方法,如果找到了,则进行调用如果找到了,则进行调用
如果没有找到,编译器会尝试进行隐式类型转换,如果类型转换之后有合适的方式可进行调用,则调用,否则直接报错
2.在函数中不能存在名字相同的变量,为什么在类中可以存在方法同名的方法?
jvm----经过编译器处理之后的字节码文件 并不是定义了两个add函数,看到的是被编译器修改过之后的add函数
java源代码经过javac编译之后,编译器会对方法的名字进行修饰;
最终名字中:全路径+方法名字+参数类型
3.方法重写
方法重写:发生在继承关系之间,子类继承父类之后,发现父类某些功能不能满足子类的需求,就重写这个方法
满足条件:
两个类要有继承关系
相同的返回值类型 相同方法名 相同的形参列表
class Circle{
public double findArea(){}//求面积
}
class Cylinder extends Circle{
public double findArea(){}//求表面积
}
4.方法重写和方法重载的区别?
方法重写:发生在继承关系之间
方法自己调用自己(一个方法直接或间接的调用自己)
使用场景:把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题求解
递归的优点:让代码简洁
递归的缺陷:不太好理解
1.对原问题进行拆分,拆分成一个一个小问题,而且小问题要与大问题解法相同
2.必须要有递归出口
1.问题本身就是递归的
2.数据结构是递归的
3.概念是递归的 比如:1+2+3+4+5+6+7…+n的和
public class Recursion {
// 求和---递归方式实现
public static long sum(int n){
if(n == 1){
return 1;
}else{
return sum(n-1)+n;
}
}
// 以递归的方式求阶乘
public static long fac(int n){
if(0 == n || 1 == n){
return 1;
}
return fac(n-1)*n;
}
// 递归打印num
// data:1234567
public static void printNumok(int data){
// 1. 出口
// 2. 如何调用自身---分拆
// 先通过递归方式打印高n-1位
if(data > 9){
printNumok(data/10); // 123456
}
// 最后打印第n位
System.out.println(data%10);
}
数组:是一个相同类型元素的集合,数组在底层的存储空间是连续的
1.所有元素的类型相同
2.存储元素的空间是连续的
3.每个空间都有自己的编号----数组的下标
T[] array = new T[N];
定义了一个数组,但是还没有进行显式的初始化,在java当中数组中每个元素都有其默认值
在创建数组的时候,需要指定一定特定的值----->定义数组并对数组进行初始化
public static void initArray(){
// 1. 动态的初始化----在创建数组时,直接指定了数字的大小
int[] array1 = new int[10];
// 2. 静态的初始化---在创建数组时,并没有明确的指明数组中元素的个数,而是给出了数组中要放的元素
int[] array2 = new int[]{1,2,3,4,5,6,7,8,9,0};
// 在代码层面array2和array3书写上有区别,但是在底层array2和array3是等价的
int[] array3 = {1,2,3,4,5,6,7,8,9,0}; // int[] array3 = new int[]{1,2,3,4,5,6,7,8,9,0};
// 注意,不能将静态和动态初始化结合在一起使用----代码编译失败
// int[] array4 = new int[5]{1,2,3,4,5};
// {}当中的元素的类型必须要与数组当中存储的元素的类型一致
// int[] array5 = {1.1,2.2,3.3}; // 编译失败
// 也可以以类似C语言的方式来定义并初始化数组----不推荐
int array6[] = new int\[10\];
int array7[] = {1,2,3,4,5};
// 在某些情况下,明确知道需要一个数组,但是前期可能暂时不知道需要多大的空间
int[] array8; // 此处并么有给数组申请空间
//...
array8 = new int[100]; // 当jvm看到new之后,才会真正从堆上给数组开辟内存空间
因为数组式一段连续的内存空间,每个位置都有自己的编号---->数组的下标是从0开始的
int [] array = new int[10];
array数组中最多可以存储10个元素,小标的范围[0,10)
数组的遍历:对数组当中的每个元素进行响应的操作:
1.打印
2.元素+10
程序在没有运行的时候,就是保存在磁盘上的一对数据
程序运行时,程序有代码以及数据需要加载到内存当中
jvm虚拟机运行时数据区:
基本变量类型:
方法中布局变量:空间就在虚拟机栈上,该变量存在在局部变量当中,保存到是变量的值
引用类型变量:
方法中局部变量:空间就在虚拟机栈上,该引用变量在局部变量中,保存的是类似于地址一样的东西
对于java而言,所有的传参方式都是以值的方式传递 (在方法当中对形参本身进行修改,对外部的实参没有任何影响)
数组中元素是基本类型
1. clone
int[] a1 = {1, 3};
int[] a2 = a1.clone();
a1[0] = 666;
System.out.println(Arrays.toString(a1)); //[666, 3]
System.out.println(Arrays.toString(a2)); //[1, 3]
3. System.arraycopy(源码)
public static native void arraycopy(Object src, int srcPos, Object dest, int desPos, int length)
//(原数组, 原数组的开始位置, 目标数组, 目标数组的开始位置, 拷贝个数)
4. Arrays.copyOf(Arrays.copyOf底层其实也是用的System.arraycopy )
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
5. Arrays.copyOfRange
大致的效率由高到低应该是System.arraycopy 、 clone 、 (Arrays.copyOf、Arrays.copyOfRange) 、 for循环。
1.对场景先要进行分析 ---- 抽留出对象
2.再让对象之间去进行交互
面向过程:蛋炒饭 面向对象:盖浇饭
java的三大特性: 封装 继承 多态
类是描述 (属性 功能)
对象是实体
抽象:对一个复杂事务的认知过程
类通过描述实体反映(C JAVA语言)给计算机,计算机反映现实
实体------类------计算机
class ClassName{
field; //类的主体
method;
}
ClassName:类的名称 class :创建类
• 是一种自定义类型 • 类型是描述某种类别(群体)的
• 类型是描述某种类别(群体)-----> 属性 一个类可以实例化多个对象
• 类没有实际空间
• 用类类型 new 出来的变量称为对象
ClassName lala = new ClassName ();
• 对象就是类实际的一个体现
• 实实在在存在的
• 可以使用类创建相同类型的好多个对象
• 对象创建好了之后,要占实际的内存空间来保存字段值
== 用类类型创建对象的过程==
类名 对象名称 = new 类名();
1.为什么需要this引用
如果形参和成员变量的名字重名,调用时就会分不清 成员方法要知道再操作那个对象
2.什么是this引用
在成员方法中:有一个隐藏的this引用,this引用的是当前对象–>调用这个方法的对象
3.this引用特性
• this类型:对应类型引用,即那个对象调用就是那个对象的引用类型
• this只能在“成员方法“中使用
•在”成员方法“中,this只能引用当前对象,不能再引用其他对象,具有final属性(常量的属性)
•
this是”成员方法“第一个隐藏的参数,编译器会自动传递,再成员方法执行时,编译器负责将调用成员方法对象的引用传递给该成员方法,this负责接收
4.this可以为空嘛?
不可以
面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
面向对象是一种以对象为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。
特殊的成员方法:
1.名字必须与类名相同
2.没有返回值类型,设置成void也不行
3.调用时机:只有在创建对象时,由编译器自动调用,而且再整个生命周期内只调用一次
4.构造方法可以重载(同一个类,同一个方法名,参数列表不同。【类型,个数,顺序不同】)
private : Java语言中对访问权限限制的最窄的修饰符,一般称之为“私有的”。被其修饰的属性以及方法只能被该类的对象访问,其子类不能访问,更不能允许跨包访问。
default: 即不加任何访问修饰符,通常称为“默认访问权限“或者“包访问权限”。该模式下,只允许在同一个包中进行访问。
protected: 介于public 和 private 之间的一种访问修饰符,一般称之为“保护访问权限”。被其修饰的属性以及方法只能被类本身的方法及子类访问,即使子类在不同的包中也可以访问。
public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”。被其修饰的类、属性以及方法不仅可以跨类访问,而且允许跨包访问。
public class Main{
public static void main(String[] args) {
{
...普通代码块
}
}
}
用 static{ } 包裹起来的代码片段
只会执行一次,静态代码块优先于构造代码块执行,一般用于初始化静态成员变量
//静态代码块
static {
System.out.println(money);
money = 100;
System.out.println(money);
System.out.println("我是静态代码块!!!");
}
在类加载的时候执行一次
定义在类中的代码块(不加修饰符)
构造代码块一般用于初始化实例成员变量
public class Student{
String name;
String sex;
int age;
{ //构造代码块
name = "花花";
sex = "女";
age = 3;
}
}
构造代码块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行
java封装类通过三个步骤实现:
(1)修改属性的可见性,限制访问。
(2)设置属性的读取方法。
(3)在读取属性的方法中,添加对属性读取的限制。
java中是单继承
继承机制:面向对象设计可以使 代码可以复用,允许程序员在保持原有类特性的基础上进行扩展,增加新功能。这样产生的类,称为派生类。继承呈现了面向对象的层次结构,体现了从简单到复杂的认知过程。继承解决的主要问题:共性的抽取,实现代码复用
注意:
1.子类会将父类中的成员变量或者成员方法继承到子类当中
2.子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没必要继承了
在子类方法中,或者通过子类对象访问这些同名成员时,优先访问的是子类自己同名的成员 就相当于子类同名成员将基类同名成员变量隐藏了
访问成员变量
子类与父类没有同名成员变量----优先访问自己的,如果自己没有,则到父类中找,如果父类中也没有则报错
子类与父类具有相同名称的成员变量----与类型是否相同没有任何的关系
在子类方法中,访问成员变量----优先访问自己的,如果自己没有,则到父类中找,如果父类中也没有则报错
访问成员方法
子类与父类没有同名的成员方法—优先访问自己的,如果自己没有,则到父类中去找,如果父类中也没有则报错
1.父类如果没有显式定义任何构造方法,此时编译器会为父类生成一份无参默认的构造方法,此时子类可以根据自己是否需要来选择实现
2.父类如果显式提供了无参的构造方法,此时子类可以根据自己是否需要来选择实现–如果用户没有显示提供,则编译器会给子类生成一个无参的默认构造方法
3.如果父类显式提供了带有参数的构造方法,则此时子类必须要显式提供构造方法–因为子类必须要在其构造方法中通过super调用基类的构造方法完成子类对象中从基类继承下来的成员的构造
用来在子类方法中访问基类继承成员
(1)super能出现在实例方法和构造方法中。
(2)super的语法是“super.”和“super()”。
(3) super不能出现在静态方法中。
(4) super大部分情况下是可以省略的。
(5)super()只能出现在构造方法的第一行
不管你创建什么对象,Object对象的无参数构造方法一定会先执行,因为Object是所有类的根类。
1、this和super一样,都是对象内部的引用变量,只能出现在对象内部;
2、 this指向当前对象自己,super指向当前对象的父类型特征,故this的东西比super多,也就是super是this的一部分;
3、 this()和super()都只能出现在构造方法的第一行,故this()和super()方法不能共存,当一个类的构造方法第一行中没有this(),也没有super(),系统默认有super()方法;
4、this()是构造方法中调用本类其他的构造方法,super()是当前对象构造方法中去调用自己父类的构造方法。
1.如果基类的构造方法是无参或者是默认的构造方法,此时子类的构造方法可以不用定义----编译器会生成一个无参的默认构造方法,如果用户有需求,在定义也不迟
2.如果基类的构造方法是带有参数的,此时编译器不会给子类生成默认的构造方法了
构造哪个类的对象,就调用那个类的构造方法
父类静态代码块 ->子类静态代码块 ->父类非静态代码块 -> 父类构造函数 -> 子类非静态代码块 -> 子类构造函数
package com.java;
public class BassClass {
public BassClass() {
}
{
System.out.println("I'm BassClass calss");
}
static {
System.out.println("static BaseClass");
}
}
class Base extends BassClass {
public Base() {
}
{
System.out.println("I'm Base class");
}
static {
System.out.println("static Base");
}
public static void main(String[] args) {
new Base();
}
}
执行结果:
static BaseClass
static Base
I’m BassClass calss
I’m Base class
① 可以用来修饰一个类
public final class 类名称 { ... }
作用:使当前这个类不能有任何子类。(“太监类”)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写,该类不能被继承
② 可以用来修饰一个方法
修饰符 final 返回值类型 方法名称(参数列表) { 方法体 }
作用:当final关键字用来修饰一个方法的时候,这个方法就是最终方法,不能够被覆盖重写
注意:对于类、方法来说,abstract关键字和final关键字不能同时使用,因为作用相互矛盾
③ 可以用来修饰一个局部变量
// ① 第一种基本数据类型情况下的格式
final 基本数据类型 数据名称 = 值;
// ② 引用数据类型情况下的格式
final 类型 对象名 = new 类名();
//例如:final Student stu = new Student();
作用:当final关键字用于修饰局部变量的时候,这个局部变量就不能更改,“一次赋值,终生不变”
注意:对于 基本类型 来说,不可改变指的是变量当中的数据不可改变,但是对于 引用类型 来说,不可改变的指的是变量当中的地址值不可改变
④ 可以用来修饰一个成员变量
对于成员变量来说,如果使用了final关键字修饰,那么这个变量也照样是不可变的
① 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了
② 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值,只能二选一
③ 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值
多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
静态多态(早绑定):在程序编译阶段,已经确定了方法的具体行为:已经确定了具体要调用哪个方法—典型代码:方法重载(overload)
动态多态(晚绑定):在子类和父类的类体中均定义了基本相同声明的非静态成员方法。所谓非静态成员方法指的是在成员方法定义中成员方法的声明不含方法修饰词static。这时也称为子类的成员方法对其父类基本相同声明的成员方法的重写(override)。
1.必须要在继承条件下
2.子类必须对父类想要实现多态的方法进行重写–子类必须要对父类的方法进行重写
3.对重写方法调用:只能通过基类的引用去调用被重写(将基类方法原型拷贝一份到子类中,然后再子类中重新来实现)的方法 在执行时,根据基类引用不用类的对象,就会调用对应类中被重写方法
一定是子类对基类中方法进行重写
子类和基类中被重写方法的原型必须一致:
修饰符 返回值类型 方法名字(参数列表)
例外:
1.被重写的方法返回值类型可以不相同,基类方法返回基类的引用 子类的方法返回子类的引用----返回值类类型不同
2.被重写的方法访问权限可以不同,限制:子类重写方法访问权限不能比基类访问权限低 public>default>protected>private
基类中被privata final static 修饰的和构造方法都不能被子类重写
向上转型:父类引用指向子类对象(安全的,有了向上转型,才可以实现多态)
向下转型 :子类对象指向父类引用
有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
接口(interface)是抽象方法和常量值的定义的集合。
从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
• 用 interface 来定义。
• 接口中的所有成员变量都默认是由public static final修饰的。
•接口中的所有方法都默认是由public abstract修饰的。
• 接口没有构造方法。构造方法用于创建对象
•实现接口的类中必须提供接口中所有方法的具体实现内容。
• 多个无关的类可以实现同一个接口
• 一个类可以实现多个无关的接口
•与继承关系类似,接口与实现类之间存在多态性
• 接口也可以继承另一个接口,使用extends关键字。
•实现接口的类中必须提供接口中所有方法的具体实现内容。
• 多个无关的类可以实现同一个接口
• 一个类可以实现多个无关的接口
•与继承关系类似,接口与实现类之间存在多态性
对于基本类型没有深浅拷贝,主要针对的是自定义类型
String类不能被继承
String s1 = "hello";
String s2 = new String("hello");
char ch = {a,b,c};
String s3 = new String(ch);
字节码文件中的常量池----->类似是一个数组
运行时常量池:字节码文件加载到虚拟机中时,字节码文件中的常量池也被加载进来了
注意:每个类都有一份
字符串常量池:全局共享
StringTable---->固定大小的哈希表(哈希表是一种非常高效用来查找的数据结构)
字符串常量池
字节码文件再加载的时候,字节码文件重点常量池也会被加载到JVM中,称为运行时常量池,并且将会常量字符串加载到字符串常量池
1)String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列。
2)执行速度StringBuilder > StringBuffer > String。
3)StringBuilder是非线程安全的,StringBuffer是线程安全的。
public static void main(String[] args) {
String s1 = "AB";
String s2 = new String("AB");
String s3 = "A";
String s4 = "B";
String s5 = "A" + "B";
String s6 = s3 + s4;
System.out.println(s1 == s2); F
System.out.println(s1 == s5); T
System.out.println(s1 == s6); F
System.out.println(s1 == s6.intern()); T
System.out.println(s2 == s2.intern()); F
}
Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。
Exception(异常)分两大类:运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
1.运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常) IndexOutOfBoundsException(下标越界异常)等,程序中可以选择捕获处理,也可以不处理。当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
2.非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
总结了好久好久!!!谢谢大家的支持。程序员真的好累,脖子快断了!!!救命 我去贴膏药了,记得给我点个赞,嘿嘿!也不知道有没有人能看到这里,看到的朋友请夸夸我!嘿嘿