类和对象:完结

1.再深构造函数

之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种⽅
式,就是初始化列表,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成
员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式;
每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义 初始化的地⽅
引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始 化,否则会编译报错;(初始化列表就是他们俩初始化的地方)
#include<iostream>
using namespace std;
class A
{
public://如果不想让类型转换发生,可以在构造函数前加explicit函数/*explicit A(int a = 0){_a1 = a;}*///多参数也是如此A(int a = 0)//单参数可以{_a1 = a;}A(int a1, int a2){_a1 = a1;_a2 = a2;}A(const A& aa){_a1 = aa._a1;}void Print(){cout << _a1 << " " << _a2 << endl;}
private:int _a1 = 1;int _a2 = 2;
};class Stack
{
public:void Push(const A& aa)//临时对象具有常性,要加const{//...}
private:A _arr[10];int _top;
};
int main()
{A aa1(1);//调用构造aa1.Print();// 隐式类型转换,类型转换会生成中间变量// 2先构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa2// 编译器遇到连续构造+拷⻉构造->优化为直接构造//调试观察A aa2 = 2;aa2.Print();A& raa1 = aa2;//A& aa2 = 2;可以验证://并不是直接构造,会生成临时变量,具有常性const A& aa2 = 2;//真正用意:Stack st;A aa3(3);st.Push(aa3);//写起来更简单,消耗是一样的st.Push(3);//多参数://A aa4 = 1, 1;//不支持//但是:C++11后才支持多参数转换A aa4 = { 1, 1 };const A& raa5 = { 2,2 };st.Push(aa4);st.Push({ 2,2 });return 0;
}

注意:初始化列表是他定义的地方,那么每个成员都会去走初始化列表,哪怕是不写

还有一类:自定义类型:

EG:

class Time
{
public:Time(int hour = 1):_hour(hour){cout << "Time()" << endl;}
private:int _hour;
};

不在初始化列表中定义, 不写也走:只调用Time的默认构造函数(无参/全缺省)

如果Time没有默认构造,调不到,只能自己传,爱传什么传什么:

无默认构造:

class Time
{
public:Time(int hour):_hour(hour){cout << "Time()" << endl;}
private:int _hour;
};
class Date
{
public:Date(int& xx, int year, int month, int day): _year(year), _month(month), _day(day), _n(1), _ref(xx), _t(1){}void Print()const{cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private://声明int _year;int _month;int _day;const int _n;int& _ref;Time _t;
};

初始化列表和函数体内可以混着赋值:
EG:(初始化列表和函数体可以打配合)

Date():_ptr((int*)malloc(12))
{//初始化列表和函数体内可以混着赋值if (_ptr == nullptr){perror("malloc fail");}else{memset(_ptr, 0, 12);}
}
C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的 成员使⽤的;
(注:定义是有开空间,下列是声明)C++11及后
private://声明,后面为缺省值->初始化列表用的int _year = 1;int _month = 1;int _day = 1;;
可以通过缺省函数理解,只不过是给初始化类表用的;
(结合下图理解):
尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这 个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没 有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有 显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构 造会编译错误;
注意与默认构造函数区分:
可以无参调用的才是默认构造:(以下并不是)
#include<iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month)//, _day(day)对于内置类型,是不做处理的,可能是个随机值{}void Print()const{cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private://声明,后面为缺省值->初始化列表用的int _year = 1;int _month = 1;int _day = 1;;
};int main()
{Date d1(2024, 7, 14);d1.Print();Date d2;//会报错,所以并不是默认构造return 0;
}
因此:让编译器自动生成默认构造便可行:
还有:不仅仅可以给以上这种缺省值:
private:int _year = 1;int _month = 1;int _day = 1;;int* _ptr = (int*)malloc(12);Time _t = 1;
初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆ 关。建议声明顺序和初始化列表顺序保持⼀致;
例题:
下⾯程序的运⾏结果是什么(D)
A. 输出 1 1
B. 输出 2 2
C. 编译报错
D. 输出 1 随机值
E. 输出 1 2
F. 输出 2 1
#include<iostream>
using namespace std;
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2 = 2;int _a1 = 2;
};
int main()
{A aa(1);aa.Print();
}
因为定义是先_a2后_a1,先对_a2进行初始化,这时候_a1的缺省值还没发现,给_a2的初始化就是编译器自己决定的,为_a1的随机值,将_a1的值给了_a2,再到_a1,将1的值给了_a1,所以answer是D!(调试很快发现)
总结:尽可能写初始化列表
每个成员都要走初始化列表,每个构造函数都有初始化列表
1.在初始化列表初始化的成员:直接用;(显示写)
2.没有在初始化列表的成员(不显示写)
----a.声明的地方有缺省值用缺省值
----b.没有缺省值
--------x.内置类型:不确定,看编译器,大概率是随机值
--------y.自定义类型:调用默认构造,没有默认构造就编译报错
3.引用,const没有默认构造的自定义,必须在初始换列表初始化
4.不要把初始化列表和默认构造扯上关系
5. 初始化列表中按照成员变量在类中声明顺序(在内存中存放的顺序)进⾏初始化
6.声明中的缺省值是给初始化列表用的,缺省值是不走初始化列表的

2.类型转换

C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数
构造函数前⾯加explicit就不再⽀持隐式类型转换

3.static成员

⽤static修饰的成员变量,称之为静态成员变量, 静态成员变量⼀定要在类外进⾏初始化 ;
#include<iostream>
using namespace std;
class A
{
public:A(){++_scount;}A(const A& t){++_scount;}~A(){--_scount;}
private:// 类⾥⾯声明static int _scount;
};
// 类外⾯初始化
int A::_scount = 0;
int main()
{return 0;
}
静态成员变量为当前类的所有对象所共享, 不属于某个具体的对象 ,不存在对象中,存放在静态区;
⽤static修饰的成员函数,称之为静态成员函数, 静态成员函数没有this指针 ;
如果:_scount是私有的,那么指定类域的方式就行不通了,这时候就可以提供一个静态的成员函数:
static int GetACount()
{return _scount;
}
静态成员函数中可以访问其他的静态成员,但是 不能访问⾮静态 的,因为没有this指针;
static int GetACount()
{_a++;//报错//不能访问⾮静态的,因为没有this指针return _scount;
}
⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数;
void func()
{cout << _scount << endl;cout << GetACount() << endl;
}
突破类域就可以访问静态成员,可以通过"类名::静态成员"或者"对象.静态成员"来访问静态成员变量和静态成员函数;
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
静态成员也是类的成员,受public、protected、private 访问限定符的限制;(类里面定义的静态成员变量某种程度上可以理解为静态的全局,只是受类域的限制,也就是他的生命周期是全局的,且不属于某个对象,严格来说只能在所在的类里面使用,类外面也行,只是受访问限定符的限制)
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表;

4.友元

友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯;
外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数;
友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制;
⼀个函数可以是多个类的友元函数;(我不仅仅和你,还要和别人做朋友)
#include<iostream>
using namespace std;
// 前置声明,不然则A的友元函数声明编译器不认识B//-----注意点
class B;//-----解决点
class A
{// 友元声明friend void func(const A& aa, const B& bb);//-----问题点
private:int _a1 = 1;int _a2 = 2;
};
class B
{// 友元声明friend void func(const A& aa, const B& bb);
private:int _b1 = 3;int _b2 = 4;
};
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;//~~~~~~~~~~~~~~~~~~~~~~~~~cout << bb._b1 << endl;//一个函数成为多个类的友元
}
int main()
{A aa;B bb;func(aa, bb);return 0;
}

友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员;

#include<iostream>
using namespace std;
class A
{// 友元声明friend class B;//把B定义成A的友元类-----解决点---B的成员函数都成为了A的友元函数
private:int _a1 = 1;int _a2 = 2;
};
class B
{
public://B要一直访问A类的私有保护成员-----问题点void func1(const A& aa){cout << aa._a1 << endl;cout << _b1 << endl;}void func2(const A& aa){cout << aa._a2 << endl;cout << _b2 << endl;}
private:int _b1 = 3;int _b2 = 4;
};
int main()
{A aa;B bb;bb.func1(aa);bb.func1(aa);return 0;
}

友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元;

友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不一定是C的友元;
有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤;

5.内部类 

如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在全局相⽐,他仅仅只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类;(内类不是属于外类的成员)
内部类默认是外部类的友元类;(我是你的朋友,所以我可以访问你的私有)
内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的 专属内部类 ,其
他地⽅都⽤不了;
#include<iostream>
using namespace std;
class A
{
private:static int _k;int _h = 1;
public:class B // B默认就是A的友元{public:void foo(const A& a){cout << _k << endl; //OKcout << a._h << endl; //OK}private:int _b = 1;};
};
int A::_k = 1;
int main()
{cout << sizeof(A) << endl;//4:实际上只有一个_h,没带_b//B不是A的成员A::B b;//指定B是属于A的类域(受访问限定符限制)A aa;b.foo(aa);return 0;
}

6.匿名对象

⽤"类型(实参)"定义出来的对象叫做匿名对象,相⽐之前我们定义的"类型 对象名(实参)"定义出来的叫有名对象;
匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象;( 他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数 )
就是为了更方便;
#include<iostream>
using namespace std;class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;
};
class Solution {
public:int Sum_Solution(int n) {//...return n;}
};
int main()
{A aa1;A aa2(2);// 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义//A aa1();// 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字,// 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数A();A(1);// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说cout << Solution().Sum_Solution(10) << endl;//就是为了更方便//有名Solution st;cout << st.Sum_Solution(10) << endl;return 0;
}

7.对象拷贝时的编译器优化

现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返回值过程中可以省略的拷⻉;
如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译还会进⾏跨⾏跨表达式的合并优化;
示例类:
#include<iostream>
using namespace std;
class A
{
public:A(int a = 0):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}A& operator=(const A& aa){cout << "A& operator=(const A& aa)" << endl;if (this != &aa){_a1 = aa._a1;}return *this;}~A(){cout << "~A()" << endl;}void Print(){cout << "A::Print " << _a1 << endl;}
private:int _a1 = 1;
};
单参数类型的构造函数的隐式类型转化的优化示例:
int main()
{//单参数类型的构造函数的隐式类型转化A aa1 = 1;//构造加拷贝构造(构造临时对象,对临时对象进行拷贝构造)(因为类型转化会产生临时对象)//开始执行发现:编译器优化成直接拷贝const A& aa2 = 1;//生成临时对象,是引用,没有拷贝构造return 0;
}

传参优化示例:

void f1(A aa)//传值传参---会产生一个拷贝
{}
void f2(A& aa)
{}
int main()
{A aa1(1);//构造//f1(aa1);//实参传给形参进行拷贝:调用构造,拷贝构造//不会优化:一般是在连续一个步骤才有优化//但是不排除其他更激进的cout << endl;//用引用:减少拷贝构造f2(aa1);cout << endl;//用匿名对象:在连续过程f1(A(1));//优化成直接构造cout << endl;f1(1);//本意是1走隐式类型转化,生成临时对象,再拷贝构造:优化成直接构造cout << endl;return 0;
}

 传返回值优化示例:

A f3()//传值传参返回:会产生一个临时对象:因为aa出了作用域就销毁了
{A aa(1);return aa;//产生临时对象
}
//应该是aa作为局部对象,出了作用域销毁,调用~A(),由aa产生临时对象调用Print
//VS2022优化:严格来说没有生成aa,合二为一,只生成临时对象:看执行结果可推
int main()
{f3().Print();//因为临死对象:构造+拷贝构造//用产生的临时对象调用Print//临时对象的周期就在这一行cout << endl;return 0;
}
A f4()
{A aa(1);//构造return aa;//拷贝构造
}
//优化:省略了临时对象,VS2022更激进,连aa都省掉了建议用2019-debug观察
int main()
{A ret = f4();//拷贝构造ret.Print();	cout << endl;return 0;
}
A f4()
{A aa(1);//构造cout << "~~~~~~~~~" << endl;//构造了aa,aa充当临时对象return aa;//拷贝构造
}
int main()
{A ret;ret = f4();//赋值//赋值完后。临时对象析构ret.Print();	cout << endl;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1486109.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式C/C++、FreeRTOS、STM32F407VGT6和TCP:智能家居安防系统的全流程介绍(代码示例)

1. 项目概述 随着物联网技术的快速发展,智能家居安防系统越来越受到人们的重视。本文介绍了一种基于STM32单片机的嵌入式安防中控系统的设计与实现方案。该系统集成了多种传感器,实现了实时监控、报警和远程控制等功能,为用户提供了一个安全、可靠的家居安防解决方案。 1.1 系…

c++ 高精度加法(只支持正整数)

再给大家带来一篇高精度&#xff0c;不过这次是高精度加法&#xff01;话不多说&#xff0c;开整&#xff01; 声明 与之前那篇文章一样&#xff0c;如果看起来费劲可以结合总代码来看 定义 由于加法进位最多进1位&#xff0c;所以我们的结果ans[]的长度定义为两个加数中最…

【Linux】HTTP 协议

目录 1. URL2. HTTP 协议2.1. HTTP 请求2.2. HTTP 响应 1. URL URL 表示着是统一资源定位符(Uniform Resource Locator), 就是 web 地址&#xff0c;俗称“网址”; 每个有效的 URL 可以通过互联网访问唯一的资源, 是互联网上标准资源的地址; URL 的主要由四个部分组成: sche…

如何查看jvm资源占用情况

如何设置jar的内存 java -XX:MetaspaceSize256M -XX:MaxMetaspaceSize256M -XX:AlwaysPreTouch -XX:ReservedCodeCacheSize128m -XX:InitialCodeCacheSize128m -Xss512k -Xmx2g -Xms2g -XX:UseG1GC -XX:G1HeapRegionSize4M -jar your-application.jar以上配置为堆内存4G jar项…

广州邀请媒体宣传(附媒体名单)

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 广州地区 媒体邀约&#xff1a; 记者现场采访&#xff0c;电视台到场报道&#xff0c;展览展会宣传&#xff0c;广交会企业宣传&#xff0c;工厂探班&#xff0c;媒体专访等。 适合广州…

自监督学习在言语障碍及老年语音识别中的应用

近几十年来针对正常言语的自动语音识别&#xff08;ASR&#xff09;技术取得了快速进展&#xff0c;但准确识别言语障碍&#xff08;dysarthric&#xff09;和老年言语仍然是一项极具挑战性的任务。言语障碍是一种由多种运动控制疾病引起的常见言语障碍类型&#xff0c;包括脑瘫…

android studio中svn的使用

第一步&#xff0c;建立一个项目。 第二步&#xff0c;share project。 第三步&#xff0c;选择存放的位置&#xff0c;然后添加提交信息&#xff0c;最后点击share。这样就可以在svn上面看到一个空的项目名称。 第四步&#xff0c;看到文件变成了绿色&#xff0c;点击commit图…

高翔【自动驾驶与机器人中的SLAM技术】学习笔记(三)基变换与坐标变换;微分方程;李群和李代数;雅可比矩阵

一、基变换与坐标变换 字小,事不小。 因为第一反应:坐标咋变,坐标轴就咋变呀。事实却与我们想象的相反。这俩互为逆矩阵。 第一次读没有读明白,后面到事上才明白。 起因是多传感器标定:多传感器,就代表了多个坐标系,多个基底。激光雷达和imu标定。这个标定程序,网上,…

Python机器学习入门:从理论到实践

文章目录 前言一、机器学习是什么&#xff1f;二、机器学习基本流程三、使用Python进行机器学习1.数据读取2.数据规范化3. 数据降维&#xff08;主成分分析&#xff09;4. 机器学习模型的选择5. 线性回归模型的实现6. 可视化结果 总结 前言 机器学习是人工智能的一个重要分支&…

pytorch 笔记:torch.optim.Adam

torch.optim.Adam 是一个实现 Adam 优化算法的类。Adam 是一个常用的梯度下降优化方法&#xff0c;特别适合处理大规模数据集和参数的深度学习模型 torch.optim.Adam(params, lr0.001, betas(0.9, 0.999), eps1e-08, weight_decay0, amsgradFalse, *, foreachNone, maximizeFa…

1小时上手Alibaba Sentinel流控安全组件

微服务的雪崩效应 假如我们开发了一套分布式应用系统&#xff0c;前端应用分别向A/H/I/P四个服务发起调用请求&#xff1a; 但随着时间推移&#xff0c;假如服务 I 因为优化问题&#xff0c;导致需要 20 秒才能返回响应&#xff0c;这就必然会导致20秒内该请求线程会一直处于阻…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第三十八章 驱动模块编译进内核

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

OpenAI从GPT-4V到GPT-4O,再到GPT-4OMini简介

OpenAI从GPT-4V到GPT-4O&#xff0c;再到GPT-4OMini简介 一、引言 在人工智能领域&#xff0c;OpenAI的GPT系列模型一直是自然语言处理的标杆。随着技术的不断进步&#xff0c;OpenAI推出了多个版本的GPT模型&#xff0c;包括视觉增强的GPT-4V&#xff08;GPT-4 with Vision&…

Sokit(TCP/UDP调试工具)

下载&#xff1a;http://www.winwin7.com/soft/56522.html#xiazai Sokit中文版是一款免费开源的TCP / UDP 测试&#xff08;调试&#xff09;工具&#xff0c;它主要可以用于接收和发送TCP/UDP数据包&#xff0c;让你更深的了解网络状况&#xff0c;能够有效地接收、发送、转…

Adobe国际认证详解-从零开始学做视频剪辑

从零开始学做视频剪辑&#xff0c;是许多初学者面临的挑战。在这个数字媒体时代&#xff0c;视频剪辑已经成为一种重要的技能&#xff0c;无论是个人爱好还是职业发展&#xff0c;掌握视频剪辑技能都是非常有价值的。 视频剪辑&#xff0c;简称“剪辑”&#xff0c;是视频制作过…

创建vue3项目,以及使用示例

1.在根目录下cmd&#xff1a;vue create myobj&#xff08;没有切换淘宝镜像记得切换&#xff0c;这样创建项目运行快&#xff09; 2. 3.(按空格键选中&#xff0c;选好回撤就到下一步了) 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.然后输入执行以下两步就已经运行项目了 以…

SpringMVC实现文件上传

导入文件上传相关依赖 <!--文件上传--> <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version> </dependency> <dependency><groupId>…

鱼眼摄像头-opencv校准(基于棋盘+畸变表)

一&#xff1a;主要参数说明 1&#xff1a;内参矩阵K 是3*3的矩阵&#xff0c;其类似格式 Knp.array([ [389.2109574522624, 0.0, 630.2525667489842], [0.0, 388.505701978078, 360.7886749292513], [0.0, 0.0, 1.0]]) 2&#xff1a;畸变系数 针对鱼眼相机&#xff1a;…

粘包问题、mmap和分片上传

一、粘包问题&#xff1a; 如果一端要把文件发给另一端&#xff0c;要发送两个部分的数据&#xff1a;其一是文件名&#xff0c;用于对端创建文件&#xff1b;另一个部分是文件内容。服务端在接收文件名&#xff0c;实际上并不知道有多长&#xff0c; 所以它会试图把网络缓冲区…

v-for 进行列表的 增删改查

通过对象下标替换属性值 但是通过实践此方法是错误的&#xff0c;Vue监听的是students这个对象&#xff0c;而不是这个对象里面的数组信息&#xff0c;也就是说&#xff0c;改变里面的值&#xff0c;并不能在页面上实现更新的功能 <!DOCTYPE html> <html lang"en…