自学内容网 自学内容网

C++ K2 (6)

提示:文章

前言

前期疑问:
本文目标:


一、背景

最近

二、

21、(多选)【auto】假设函数Foo返回类型为const A&,下列语句与 const A& r = Foo(); 等价的有()

A. auto r = Foo();

B. auto& r = Foo();

C. const auto& r = Foo();

D. decltype(Foo()) r = Foo();

Auto不能变成引用类型。

当有&的时候,const会默认复制过来

Decltype返回传入值的类型

Auto不能变成引用类型。

当有&的时候,const会默认复制过来

Decltype返回传入值的类型

自我解析:这个题目主要考的是Auto不能变成引用类型。

22、(多选)【auto】下面auto推导结果正确的有(ABC)

A.

auto a = 1;  // 推导出变量 a 的类型是 int

B.

int a = 1;
auto b = a;  // 推导出变量 b 的类型是 int

C.

int a =1;
const auto &c = a;  //推导出变量 c 的类型是 const int& 

D.

const int a =1;
auto d = a;    //推导出变量 d 的类型是const int

auto小记

auto 仅仅是一个占位符,在编译器期间它会被真正的类型所替代。或者说,C++ 中的变量必须是有明确类型的,只是这个类型是由编译器自己推导出来的。

当=右边的表达式是一个引用类型时,auto 会把引用抛弃,直接推导出它的原始类型。

当类型不为引用时,auto 的推导结果将不保留表达式的 const 属性;

当类型为引用时,auto 的推导结果将保留表达式的 const 属性。

编译

23、(单选)【编译】如下代码中,在编译阶段(非链接阶段)会发生错误的是【C】

A、

constexpr int GetValue();
int value = GetValue();

B、

constexpr int GetValue();
const int value = GetValue();

C、

constexpr int GetValue();                                                
constexpr int value = GetValue();

D、

constexpr int GetValue();
constexpr int GetValue() { return 0; };
constexpr int value = GetValue();

Constexpr 需要在编译阶段确定

这个题目在代码中可以得到验证。

C选项报错:Constexpr variable ‘value3’ must be initialized by a constant expression。

24、【编译】以下代码所定义的 Func 函数位于头文件且被多个.cpp文件所包含时,哪个会报编译错误():

A.

class Myclass {
    virtual void Func()
    { ... }
};

B.

constexpr void Func()
{ ... }

C.

class Myclass {
    void Func()
    { ... }
};

D.

class Myclass {
  static void Func();
};

void Myclass::Func()
{ ... }

解释:重定义错误

这个题目感觉莫名其妙。

25、(多选)【编译】下面调用Foo的代码,能够编译成功的有()

constexpr int Foo(int i)
{
return i + 5;
}

int main()
{
    // 下面选项的代码会插入到这个位置
    return 0;
}

A.

std::array<int, Foo(5)>arr;

B.

int i =10;
std::array<int, Foo(i)> arr;

C.

int i = 10;
Foo(i);

D.

constexpr int i = 5;
Foo(i);

答案

array要求编译期常值才能编译通过,这也是监测你写的代码是否真的产生编译期常值的方法。B选项中,只需将int i改为const int i或者constexpr int i即可通过编译

const和constexpr的区别:前者可以在运行时推出,后者要求在编译阶段就要能够推出。

26、(多选)【编译】下面哪些在编译期报错(非链接期)()

A.

constexpr int GetVal();
constexpr int GetVal() { return 0; }
constexpr int i = GetVal();

B

constexpr int GetVal();
constexpr int i = GetVal();

C

constexpr int GetVal();
int i = GetVal();

D

constexpr int GetVal();
constexpr int i = GetVal();
constexpr int GetVal() { return 0; }

答案

A 选项不会报错,GetVal() 返回的常量表达式,使用它初始化 i 没有问题。

B 选项在编译期报错,是因为 i 是一个 constexpr 修饰的变量,意味着它的值必须是编译期已知的,所以它会在编译期间对它的初始化表达式(即 GetVal())进行求值,但这个函数只有声明,没有定义,所以编译期间就报错了。

C 选项和 B 选项最大的区别就在于 i 没有使用 constexpr 修饰,是因为 i 不需要编译期已知,编译时不会去对 GetVal 求值,尽管 GetVal 没有定义,但编译器假设它可能定义在别的编译单元,留给链接器去决策。所以编译期不会报错,但会在链接期报错(undefined reference to ‘GetVal()’)。

D 选项也会编译期报错,原因和 B 选项是一样的。尽管在这里 GetVal 有了定义,但定义点在使用点之后,所以仍然会报错。

27、(多选)【编译】以下哪些选项中的代码独立填到横线处,可以使程序正常编译运行且不产生未定义行为()

#include <iostream>
#include <memory>

class Foo {
public:
void Show() { std::cout << data; }
private:
int data{1};
};

int main()
{
    std::unique_ptr<Foo> tom = std::make_unique<Foo>();
    std::unique_ptr<Foo> jerry = std::move(tom);
    ______________ // 选项中的代码填入此处
    return 0;
}

A: tom->Show();

B: std::unique_ptr mary = std::move(tom);

mary->Show();

C: jerry->Show();

D: tom = std::move(jerry);

答案

选项 A 和 B 会导致未定义行为,因为在 std::move(tom) 之后,tom 不再拥有 Foo 对象的所有权,访问 tom会导致未定义行为。

28、(多选)【断言】以下代码划线处填入哪个能编译成功()

#include<assert.h>
#include<stdbool.h>

#define AAA 0
#define BBB AAA||true

const int g_ttt = 2;

int Func()
{
    const short t = 3;
    ____;
    return 0;
}

A. static_assert(t > 0, “error”)

B. static_assert(g_ttt > AAA, “error”)

C. static_assert(sizeof(BBB), “error”)

D. static_assert(2&&3, “error”)

答案

静态断言

static_assert声明是C++11标准引入的特性,用于在程序编译阶段评估常量表达式并对返回false 的表达式断言,我们称为静态断言。它具有如下特性:

所有处理都必须在编译期间执行,不允许有空间和时间上的运行成本;

必须具有简单的语法;

断言失败能够显示丰富的错误诊断信息;

可以在命名空间、类和代码块内部使用;

失败的断言会在编译阶段报错

Assert检验前面的是否成功,不成功则输出后面的。

C语言是选CD,C++和C的const有区别,static_assert是一样的

static_assert(t < 0, "error");  //这样代码会提示错误,感觉是静态检查一样

29、(多选)关于inline函数的使用,以下哪些选项的描述是正确的()

A. 在一个.c文件中定义的inline函数可以在另一个.c文件中使用,且如果小于10行,编译器会把该inline函数展开,发挥其性能优势

B. inline函数应该简洁,如果语句较多,不适合于定义为内联函数

C. inline函数体中,一般不建议有循环、if或switch语句,否则,函数定义时即使有inline关键字,编译器也可能会把该函数作为非内联函数处理

D. inline函数要在函数被调用之前声明

答案

30、(多选)内联函数兼具函数和宏的优点(ABCD)

A. 内联函数/函数执行严格的类型检查

B. 内联函数/函数的参数求值只会进行一次

C. 内联函数就地展开,没有函数调用的开销

D. 内联函数比函数优化得更好

31、(多选)以下关于内联的说法,正确的是()

A. 含有inline的函数一定会被内联展开

B. 未包含inline的函数也可能被内联展开

C. 在内联函数中,所有函数定义中的局部静态对象在所有翻译单元间共享

D. 函数的内联替换会避免函数调用的开销(传递实参及返回结果),但它可能导致更大的可执行文件,因为函数体必须被复制多次

答案

解释:此题C选项需要注意,在内联函数中使用static,其实是类似全局变量的,即所有内联函数会公用这个static对象,所以不要在内联函数里面使用static对象

内联函数在什么时候展开 由编译器自己决定

32、(多选)关于变量,正确的说法是()

A. 若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度

B. 函数内部定义的自动变量过大,可能会导致堆栈溢出

C. 若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度

D. 设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时需要考虑重入问题

答案

自我解析:增加对这个题目的理解

对于A选项,提及若只在单个c文件中使用,可以将这个变量修改为静态全局变量。我的问题是,静态全局变量只能在当前这个编译单元中访问吗?

查一下百度,“静态全局变量只能当前编译单元使用吗?”,

是的,静态全局变量只能在当前编译单元中使用。在C语言中,静态全局变量(也称为全局静态变量)的作用域被限制在其定义的编译单元内。这意味着,如果在某个源文件中定义了一个静态全局变量,那么这个变量只能在定义它的源文件中被访问和使用。其他源文件无法通过extern关键字来引用这个变量,因为静态全局变量的作用域仅限于定义它的源文件‌。

具体来说,静态全局变量的定义方式是在变量前加上static关键字。这样,该变量就只能在定义它的源文件中被访问和使用,而不会影响到其他源文件中的同名变量。这种特性使得静态全局变量在模块化编程中非常有用,可以避免不同模块之间的变量冲突‌。

然后看到一篇文章说在C语言中没有全局变量这一说,只有外部变量和自动变量。

对于第一点,c语言中没有全局变量这一说,查了下好像没有得到太确切的文章。

对于第二点,外部变量

  1. 外部变量‌:
    • 外部变量是指在一个文件中定义,而在另一个文件中声明的全局变量。
    • 通过使用extern关键字,可以在一个文件中引用另一个文件中定义的全局变量。
    • 外部变量的作用域也是整个程序,但它特别用于在不同文件之间共享全局变量。
    • 外部变量实质上仍然是全局变量,只是其定义和声明可能分布在不同的源文件中。

此外,值得注意的是,虽然外部变量和全局变量在概念上有所关联,但过多地使用全局变量(包括外部变量)可能会降低程序的模块化和独立性,因此应谨慎使用。

以及什么是自动变量,

在C语言中,自动变量是指在函数内部声明的局部变量。

33、(多选)函数中使用全局变量将引入哪些问题()

A. 函数对全局变量产生依赖,存在耦合

B. 当多个函数读写全局变量时,某一时刻其取值可能不确定,函数处理逻辑可能更复杂

C. 并发编程环境中,此函数可重入性会被破坏

D. 函数性能会大大降低

答案

34、(多选)在32位系统中,有如下代码

char str[] = “\\\\”
char*p = str;
int n = 1000;

以下说法正确的是()

A. sizeof(str)为3

B. sizeof§为4

C. sizeof(n)为4

D. sizeof(str)为4

E. sizeof(str)为5

答案

str实际上是一个存放两个\的字符串,注意\转义为\。因此在32位下,str的大小为3(还有末尾的\0),指针大小是4,int大小是4

35、(单选)关于const的用法,错误的是()

A. 指针所指向的内容不会被更改,应该声明为指向const的指针

B. const只能用于修饰简单类型,不能用于修饰自定义类型

C. const类型的成员变量、引用类型的成员变量,必须在构造函数初始化列表中进行初始化

D. const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的

答案

自我解析,关于题目中的选项的意义我有点不太懂

对于选项A,按照A选项描述的,我的理解应该是int *const ptr;但是实际上不是的。应该是const int* ptr;ptr指向cont int;

至于int* const ptr是指向int类型的变量。

关于D选项,什么叫const对象通过指针维护的对象是可以修改的。

基于copilot的解释,结合自己的理解。可能就是如果这个const对象中有个指针,可以更改这个指针指向的内容。

下面的是copilot的解释:

这句话的意思是,当一个对象被声明为 const 时,该对象的成员变量是不可修改的。然而,如果这个 const 对象包含一个指向其他对象的指针,那么通过这个指针指向的对象是可以被修改的。

举个例子:

cpp

class MyClass {
public:
    int value;
    int* ptr;
};

int main() {
    int x = 10;
    int y = 20;
    const MyClass obj = {5, &x};

    // obj.value = 6; // 错误:不能修改 const 对象的成员变量
    *obj.ptr = y; // 合法:可以通过指针修改指向的对象

    return 0;
}

在这个例子中,obj 是一个 const 对象,因此我们不能修改 obj.value。但是,obj.ptr 是一个指向 int 的指针,通过这个指针我们可以修改它指向的对象 x 的值。

36、(多选)对于常量描述正确的是()

A.对于不变量,函数形参和成员函数使用const来保证数据为不变量

B.函数内的const局部变量必须在声明时初始化

C.在多个.cpp文件中定义的同名全局const变量将导致重复定义错误

D.在多个.h文件中定义的同名全局const变量将导致重复定义错误

答案

C:应该是一个 cpp 中一份

其中C选项我在Clion上验证了一下,确实多个编译单元中可以有相同的const定义。

37、(多选)关于循环的说法正确的是 ()

A. 使用range_base for循环时可以在循环体中增删元素

B. 使用range_base for循环时可以简化对元素相同代码的处理

C. range_base for可以用于STL容器

D. for循环中的表达式都可以省略,但是两个分号不能省略

答案

A会导致迭代器失效?

这边涉及了一个知识点,就是range_base for循环是什么意思,查了copilot,解释如下:

range-based for 循环是 C++11 引入的一种简化遍历容器元素的语法。它使得代码更加简洁和易读,特别是在处理标准模板库(STL)容器时。

以下是一个 range-based for循环的示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};

    // 使用 range-based for 循环遍历容器元素
    for (int value : vec) {
        std::cout << value << " ";
    }

    return 0;
}

在这个示例中,range-based for 循环遍历 vec容器中的每个元素,并将其值输出到控制台。与传统的 for 循环相比,range-based for 循环更加简洁,不需要显式地使用迭代器或索引。

面向对象知识(操作符重载,多态,成员声明使用,特殊成员函数,继承等)

覆盖的条件:

1. 基类函数带virtual关键字;

2. 派生类函数有相同的函数名、参数、返回值类型

以上两个条件全部满足才能覆盖,否则只能算隐藏(hide)

产生覆盖后,按具体的指针内容所属类型调用方法;一般都是调用派生类的方法

如果没产生覆盖,则按具体类对象的类型调用方法;父调父,子调子

l 基类和其派生类共享该基类的静态成员变量内存

l 父类声明,子类实现时,优先从父类找,父类为虚函数则找子类函数(无论是否为虚),父类不为虚函数则取父类函数。

l 如果父类里声明了某函数为虚函数,则在子类此函数的声明里不管有没有"vitrual"关键子,都是虚函数。即使访问权限发生变化。

1、(单选)类cAAA的一个对象cAAAobj,那么sizeof(cAAAobj) 的值为

A. 1

B. 4

C. 8

D. 16

答案

虚表4(就是一个指针) int4,静态变量不占用

这个题目有问题,应该本身题目有段代码,但是这边没有显示。这个题目实际是之前我在牛客上经常做的题目。

下面是我自己写的验证的代码

class Foo {
public:
    void Show() { std::cout << data; }
private:
    int data{1};
    int data2{1};
};

class Foo2 {
public:
    void Show() { std::cout << data; }
private:
    int data{1};
    int data2{1};
};

int main66()
{
    Foo foo;
printf("%d\n",sizeof(foo));
    printf("%d\n",sizeof(Foo));
    
    Foo2 foo2;
    printf("%d\n",sizeof(foo2));
    printf("%d\n",sizeof(Foo2));

    return 0;
}
//打印信息
4
4
8
8

由此可见,类对象的内存大小取决了类的成员情况。

2、(单选)下列关于纯虚函数的描述中,正确的是()

A. 纯虚函数是一种特殊的虚函数,它是个空函数

B. 具有纯虚函数的类称为虚基类

C. 一个基类中说明有纯虚函数,其派生类一定要实现该纯虚函数

D. 具有纯虚函数的类不能创建类对象

答案

A 纯虚函数还不是函数

B 是抽象类

C 可以不实现,实例化才实现

http://c.biancheng.net/view/2299.html

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。

C:派生类可以不实现该纯虚函数,但是该派生类如果要被实例化,就必须实现该纯虚函数了

3、(单选)要将类A说明是类B的虚基类,正确的描述是 ( )

A.class virtual B:public A

B.class B:virtual public A

C.virtual class B:public A

D.class B:public A virtual

答案

虚基类是什么?

查了下资料如下:

‌[虚基类](Virtual Base Class)在C++中用于解决多重继承中的菱形继承问题。菱形继承是指一个类通过两条不同的路径继承同一个基类,导致基类成员在派生类中出现多份拷贝,从而产生冗余和歧义。虚基类通过共享基类成员,避免了这种冗余和歧义。‌12

** 定义和用途**

虚基类是通过virtual关键字继承的基类。其用途在于解决多重继承中的菱形继承问题,确保基类成员在派生类中只有一份拷贝,从而避免冗余和歧义。

示例代码

以下是一个使用虚基类的示例代码:

cppCopy Codeclass Base {
public:
    int x;
    Base(int val) : x(val) {}
};

class Derived1 : virtual public Base {
public:
    Derived1(int val) : Base(val) {}
};

class Derived2 : virtual public Base {
public:
    Derived2(int val) : Base(val) {}
};

class FinalDerived : public Derived1, public Derived2 {
public:
    FinalDerived(int val) : Base(val), Derived1(val), Derived2(val) {}
};

在这个例子中,FinalDerived类通过两条路径继承自Base类。由于Base被声明为虚基类,FinalDerived中只包含一个Base类的实例,避免了冗余。

4、(单选)输出结果为:()

#include < iostream.h>

class A 
{   
    public:
    virtual void f() { cout<<"A::f()\n"; }
}; 

class B:public A
{
private:
char *buf;
public:
    B(int i) 
    {
        buf=new char[i]; 
    }

    void f()
    { 
        cout<<"B::f()\n";
    }B() { delete []buf; }
};

void main()
{   
    A *a = new A;
    a->f();   
    delete a;                                                     
    a = new B(15); 
    a->f();
}

A: A::f() A::f()

B: A::f() B::f()

C: B::f() B::f()

D: 程序会抛出异常

答案

B 虚函数、动态联编。

5、(单选)已下哪个选项中的代码段在输出section end 之前会调用Base的析构函数?()

A.auto sp = make_shared<Base>();
{
    sp.reset(new Base());
}
cout << "section end" << endl;
B.unique_ptr<Base> up1;
{
    auto up2 = make_unique<Base>();
    swap(up1, up2);
}
cout << "section end" << endl;
C.shared_ptr<Base> up1;
{
    auto up2 = make_shared<Base>();
    swap(up1, up2);
}
cout << "section end" << endl;
D.shared_ptr<Base> sp;
{
    auto up = make_unique<Base>();
    sp = move(up);
}
cout << "section end" << endl;

答案

A选项:在 {} 块内,调用了 sp.reset(new Base())。这个操作会做两件事:

  1. 首先,它会销毁 sp 原来持有的 Base 对象,这会导致第一次调用析构函数。
  2. 然后,它会让 sp 持有一个新分配的 Base 对象。

{} 块结束时,局部作用域内的变量(如果有的话)会被销毁。但在这个例子中,sp 是在外部作用域定义的,所以它不会在这里被销毁。

最后,当 main 函数结束时,sp 会被销毁,这会导致它持有的第二个 Base 对象被析构,从而调用第二次析构函数。

6、(单选)请指出如下程序的输出结果()

class A { public: int n; }; 
class B: public A { }; 
class C: public A { }; 
class D: public B, public C { }; 

void main() { 
    D d; 
    d.B::n=10; 
    d.C::n=20; 
    cout << d.B::n << "," << d.C::n; 
}

A. 20,20

B. 10,20

C. 20,10

D. 10,10

答案

class B、C都包含有class A,所以都有各自的 n

这个题目就和上面的虚基类的题目对应上了。我在这边尝试下使用虚基类看下积累会被初始化成什么样的。

自己写了代码,但是和预期的不一样

class A { public: int n; };
class B: virtual public A { };
class C: virtual public A { };
class D: public B, public C { };

int main()
{
    B b;
    C c;
    b.n = 10;
    c.n = 20;
    cout << b.n << "," << c.n << endl;



    D d;
    d.B::n=10;
    d.C::n=20;
    cout << d.B::n << "," << d.C::n;

    return 0;
}
// 打印信息
10,20
20,20

和预期的不一样,需要找之前的题目类型对比下。

7、(单选)已知CSomething是一个类,执行下面这些语句之后,内存里创建了多少个CSomething对象()

CSomething a;    // 1
CSomething b(2);   // 1
CSomething c[3];   // 3
CSomething &ra = a; // 0
CSomething d = a;  // 1
CSomething *pA = c; // 0
CSomething *p = new CSomething(4); // 1

A. 8

B. 7

C. 10

D. 9

答案

从上到下分别是1 1 3 0 1 0 1一共7个

8、(单选)【继承】有如下的类定义

class Base { 
public: 
virtual ~Base() { std::cout << "Destroy Base" << std::endl; }
}; 

class Derive1 : public Base { 
public: 
~Derive1() { std::cout << "Destroy Derive1" << std::endl; } 
}; 

class Derive2 : public Derive1 {
public:
~Derive2() { std::cout << “Destroy Derive2” << std::endl; }
};      

在执行

Derive1* pObj = new Derive2();
delete pObj;

时,输出是()

A. Destroy Derive1 Destroy Base

B. Destroy Base Destroy Derive1 Destroy Derive2

C. Destroy Derive2 Destroy Derive1 Destroy Base

D. 上面都不对

答案

C 基类的析构是虚函数,则派生类析构也是虚函数,从而会一直向下传递,子子孙孙的析构全是虚函数。

如果把virtual去掉,选A

这个题目我一开始看的时候很纳闷为什么答案是C。为啥会显示Destroy Derive1?仔细看了题目才发现class Derive1是class Derive2的基类。所以new Derive2()时,Derive2的构造函数和Derive1的构造函数以及Base的构造函数都被调用了。析构时,也就先从Derive2一直析构到Base类。

另外一个问题 就是为什么把virtual去掉,选A。问了copilot,没有virtual时

解释如下:

  • delete pObj; 被调用时,由于 Base 类的析构函数不是虚函数,析构函数不会按照从派生类到基类的顺序依次调用。
  • 只会调用 pObj 的静态类型(即 Derive1)的析构函数,而不会调用 Derive2 的析构函数。
  • 因此,首先调用 Derive1 的析构函数,输出 “Destroy Derive1”。
  • 然后调用 Base 的析构函数,输出 “Destroy Base”。

为什么只会调用 pObj的静态类型(即 Derive1)的析构函数,而不会调用 Derive2 的析构函数。

这是因为在 C++ 中,如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这是由于静态类型绑定的原因。

让我们详细解释一下:

  1. 静态类型和动态类型
    • 静态类型是在编译时确定的类型。在你的例子中,pObj 的静态类型是 Derive1*
    • 动态类型是在运行时确定的类型。在你的例子中,pObj 实际上指向的是一个 Derive2 对象。
  2. 非虚析构函数
    • 如果基类的析构函数不是虚函数,当通过基类指针删除派生类对象时,编译器只会调用基类的析构函数,而不会调用派生类的析构函数。这是因为编译器在编译时只知道 pObj 的静态类型是 Derive1*,所以它只会调用 Derive1 的析构函数。
  3. 虚析构函数
    • 如果基类的析构函数是虚函数,当通过基类指针删除派生类对象时,编译器会在运行时根据对象的动态类型调用正确的析构函数。这意味着它会按照从派生类到基类的顺序依次调用析构函数。

第三个问题,为什么类Derive2析构函数不需要写成virtual

为什么类Derive1析构函数不需要写成virtual?

在 C++ 中,当基类的析构函数被声明为虚函数时,所有派生类的析构函数都会自动成为虚函数,即使在派生类中没有显式地使用 virtual 关键字。这是因为虚函数的特性会在继承链中传递。

具体来说,当基类的析构函数是虚函数时,通过基类指针删除派生类对象时,会确保调用派生类的析构函数,然后再调用基类的析构函数。这种机制保证了对象的正确销毁,防止资源泄漏。

通透!


总结

未完待续


原文地址:https://blog.csdn.net/2301_77560238/article/details/145213313

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!