C++ 不能将const char*类型分配到char 可以转成对象吗?

抽象和类c++中的类c++程序员将接口放在头文件中,并将实现放在源代码文件中。关键字private和public描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数(或友元函数)来访问对象的私有成员。什么是封装?公有接口表示设计的抽象组件。将实现细节放在一起并将他们与抽象分开被称为封装。数据隐藏(将数据放在类的私有部分中)是一种封装,将实现细节隐藏在私有部分中,也是一种封装,将类函数定义和类函数声明放在不同文件中也是封装。不必在类声明中使用关键字private,因为这是类对象的默认访问控制。定义成员函数定义成员函数,使用作用域解析运算符::来标识函数所属的类类方法可以访问类的private组件。例如:void Stock::update(double price)
class Stock
{
void set_tot()
{
total_val=shares *share_val}
};
其定义位于类声明中的函数都将自动成为内联函数,Stokc::set_tot是一个内联函数,也可以在类声明外定义成员函数,只需加上inline限定符所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类方法。类的构造函数和析构函数class Stock
{
public:
Stock(const string& co, int a, double b)
{
s1 = co;
aa = a;
bb = b;
}
void show()
{
cout << s1 << " " << aa << " " << bb;
}
private:
int aa;
double bb;
string s1;
};
int main()
{
Stock a("yes", 1, 2.0);
a.show();
}
可以在函数外面定义构造函数,例如:class Stock
{
public:
Stock(const string& co, int a, double b);
void show()
{
cout << s1 << " " << aa << " " << bb;
}
private:
int aa;
double bb;
string s1;
};
int main()
{
Stock a("yes", 1, 2.0);
a.show();
}
Stock::Stock(const string& co, int a, double b)
{
s1 = co;
aa = a;
bb = b;
}
成员名和参数名将类成员名称用作构造函数的参数名是错误的,例如class Stock
{
public:
Stock(int a,int b)
{
}
private:
int a,b;
为避免这种混乱,一种常见的做法是在数据成员名中使用m_前缀:class Stock
{
private:
string m_company;
long m_shares;
}
或者是在成员名后面使用后缀_。使用构造函数c++提供了两种构造函数来初始化对象。第一种是显式地调用构造函数:Stock food=Stock("dsfsd",1,2.0)
这将s1设置为"dsfsf",aa为1,bb为2.0,以此类推,另一种方式隐式地调用构造函数:Stock food("dsfds",1,2.0)
这与上面的显式是等价的。下面是将构造函数和new一起使用的方法:stock* pstock= new Stock("dsfsd",1,2.0)
这条语句创建一个Stock对象,将其初始化为参数提供的值,并将该对象的地址赋给pstock指针。Stock a("yes", 1, 2.0);
a.Stock("yes", 1, 2.0);
不能使用对象来调用构造函数,构造函数被用来 创建对象。初始化列表如果数据成员是const或者引用的话,必须采用舒适化列表例如:class Time
{
private:
const int hours, minutes;
public:
Time():hours(0),minutes(0)
{
}
Time(int h, int m):hours(h),minutes(m)
{
}
默认构造函数例如:Stock fluffy_the_cat;
它是默认构造函数的隐式版本,不做任何工作。默认构造函数没有参数,因为声明中不包含值。当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类定义了构造函数后,程序员就必须为他提供默认构造函数。如果提供了非默认构造函数,但没有提供默认构造函数,则下面的声明将出错stock stock1;
这样做的原因可能是想禁止创建为初始化的对象。然而,如果要创建对象,而不显式地初始化,则必须定义一个不接受任何参数的默认构造函数。定义默认构造函数优两种方式,一种是给已有构造函数的所有参数提供默认值:Stock(const string &co="fdsfds",.int n=0,double pr=0.0);
这个注意是在函数原型里面加默认值。还有一种方法是为Stock类定义一个默认构造函数:Stock ::Stock()
{
s1="dsf"
aa=1;
bb=1.0;
}
不要同时采用这两种方式会报错。析构函数与构造函数不同的是,析构函数没有参数,因此Stock的析构函数的原型必须是这样的:~Stock();
如果创建的是静态存储类对象,则其析构函数将在程序结束时自动被调用,如果创建的是自动存储对象,则其析构函数将在程序执行完该代码块时自动被调用,如果对象是通过new创建的,当使用delete来释放内存时,析构函数自动被调用,程序可以创建临时对象来完成特定的操作,程序将在结束对该对象的使用时自动调用其析构函数。class Stock
{
public:
~Stock()
{
cout << "yes";
}
};
int main()
{
Stock stock2 = Stock();
}
这种方式是调用构造函数来创建一个临时对象,然后将该临时对象复制到stock2中并丢弃它,这将为临时对象调用析构函数,因此会输出yes.class Stock
{
public:
string b;
Stock(string a)
{
b = a;
}
~Stock()
{
cout << b << endl;
}
};
int main()
{
Stock stock2("one");
Stock stock3("two");
}
由于自动变量被放在栈中,因此后创建的对象将最先被删除。
Stock stock2 = Stock("one");
Stock stock1;
stock1 = Stock("two");
这两条语句有根本性的差别,第一条语句是初始化,它创建有指定值的对象,可能会创建临时对象(也可能不会),我操作的是不会的,第二条语句是赋值,像这样在赋值语句中使用构造函数总会导致在赋值前创建一个临时对象。如果既可以通过初始化,也可以通过赋值来设置对象的值,则应采用初始化方式。效率更高。const成员函数class Stock
{
public:
string b;
Stock(string a)
{
b = a;
}
void show()
{
cout << b << endl;
}
};
int main()
{
const Stock stock2 = Stock("one");
stock2.show();
}
对于c++来说,编译器将拒绝第二行,因为show()的代码无法保证调用对象不被修改,show()使用的对象是由方法调用隐式提供的。需要新的一种语法来保证函数不会修改调用对象。c++的解决办法是将const关键字放在函数的括号后面。例如:void show()const;
同样函数的定义的开头也应这样:void Stock::show()const
以这种方式声明和定义的类函数被称为const成员函数。只要类方法不修改调用对象,就应将其声明为const。构造函数和析构函数小结如果构造函数只有一个参数,则将对象初始化为一个与参数的类型相同的值时,该构造函数将被调用。例如:class Bozo
{
public:
int a;
Bozo(int age)
{
a = age;
}
};
int main()
{
Bozo one = 32;
cout << one.a;
}
这样也能将one初始化,不过它可能会让人很难受,会在第十一章介绍关闭这项特性的方式。默认构造参数可以没有任何参数;如果有,则必须给所有参数提供默认值。对于未初始化的对象,程序将使用默认构造函数来创建:Bozo bubi;
Bozo *pb=new Bozo;
如果构造函数使用了new,则必须提供使用delete的析构函数。this指针假设要对两个对象进行比较,例如const max1& compare(const max1& s)const
可以这样写:max1 one(3);
max1 two(4);
two.compare(one);
对于two.compare(one)来说,这种格式显式地访问one,隐式地访问two.对于two来说他有名字s,但是对于one来说却没有名字,c++解决这种问题的方法是:使用被称为this的特殊指针,this指针指向用来调用成员函数的对象。例如:class max1
{
public:
int a;
max1(int b)
{
a = b;
}
const max1& compare(const max1& s)const
{
if (s.a > a)
{
return s;
}
else
{
return *this;
}
}
};
int main()
{
max1 one(3);
max1 two(4);
two.compare(one);
cout << two.a;
}
对象数组例如:Stock mystuff[4];
上述声明,这个类如果没有定义任何构造函数,将使用不执行任何操作的隐式默认构造函数,可以用构造函数来初始化数组元素例如:class max1
{
public:
int a;
max1(int b)
{
a = b;
}
};
int main()
{
max1 arr[4] = { max1(1),max1(2),max1(3),max1(4) };
}
但是要对所有元素调用构造函数,除非有默认构造函数。类作用域作用域为类的常量类声明可能使用字面值为30来指定数组的长度,由于该常量对于所有对象都是相同的,因此创建一个由所有对象共享的常量是个不错的注意。您可能以为这样做可行:class Bakery
{
private:
const int Months = 12;
double costs [Months];
};
这样是不行的,因为声明类只是描述了对象的形式,没有创建对象。因此在创建对象前,将没有用于存储的空间。下面有两种方式:第一种方式是在类中声明一个枚举。例如:class Bakery
{
private:
enum{Months=12};
double costs [Months];
};
由于这里只是为了创建符号常量,因此不需要提供枚举名。第二种方法是使用关键字static:class Bakery
{
private:
static const int Months = 12;
double costs [Months];
};
该常量将于其他静态变量存储在一起,而不是存储在对象中。作用域内枚举两个枚举定义的枚举量可能发生冲突。例如:enum egg{small,medium,large,jumbo};
enum t_shirt{small,medium,large,xlarge};
c++11提供了一种新枚举,其枚举量的作用域为类。例如:enum class ege{small,medium,large,jumbo};
enum class t_shirt{small,medium,large,xlarge};
也可以使用关键字struct来代替class。enum struct ege { small, medium, large, jumbo };
enum struct t_shirt { small, medium, large, xlarge };
无论哪种方式都要使用限定名来限定枚举量:
enum struct ege { small, medium, large, jumbo };
enum struct t_shirt { small, medium, large, xlarge };
ege one = ege::small;
t_shirt two = t_shirt::large;
c++11还提高了作用域内枚举的类型安全。在有些情况下,常规枚举将自动转换为整形,如将其赋给int变量或用于比较表达式时,但作用域内枚举不能隐式地转换为整形:enum ege{small,medium,large,jumbo};
enum class t_shirt{small,medium,large,xlarge};
ege one = medium;
t_shirt rolf = t_shirt::large;
int king = one;
int ring = rolf;fasle
if (king < jumbo)///allow
{
}
if (king < t_shirt::medium)///not allowed
{
}
int Frodo = int(t_shirt::small);///allowed
必要时可以进行显式类型转换。枚举用底层整形表示,在c++98中,如何选择取决于实现,因此包含枚举的结构的长度可能随系统而异,对于作用域内枚举,c++11消除了这种依赖性,默认情况下,c++11作用域内枚举的底层类型为int。另外还提供了一种语法,可以做出不同的选择:enum etype2 : short { small ,medium,large,xlarge };
c++ primer plus给的是enum class : short pizza { small ,medium,large,xlarge };
会报错。应该是这样才对enum class pizza :short { small ,medium,large,xlarge };
下面是使用类来实现stack;#include<iostream>
#include"stack.h"
using namespace std;
int main()
{
Stack st;
char ch;
int po;
while (cin >> ch && toupper(ch) != 'Q')
{
while (cin.get() != '\n')
{
continue;
}
if (!isalpha(ch))
{
cout << '\a';
continue;
}
switch (ch)
{
case'A':
case'a':cout << "Enter a PO number to add:";
cin >> po;
if (st.isfull())
{
cout << "stack alreat full\n";
}
else
{
st.push(po);
}
break;
case 'p':
case'P':if(st.isempty())
{
cout << "stack already empty\n";
}else
{
st.pop(po);
cout << "po#" << po << "popped\n";
}
break;
}
}
}
#ifndef Stack_H
#define Stack_H
class Stack
{
private:
enum{MAX=5};
int items[MAX];
int top;
public:
Stack();
bool isempty()const;
bool isfull()const;
bool push(const int& x);
bool pop(int &x);
};
#endif
#include"stack.h"
#include<iostream>
using namespace std;
Stack::Stack()
{
top = 0;
}
bool Stack::isempty()const
{
return top == 0;
}
bool Stack::isfull()const
{
return top == MAX;
}
bool Stack::push(const int& x)
{
if (top < MAX)
{
items[top++] = x;
return true;
}
return false;
}
bool Stack::pop(int& x)
{
if (top > 0)
{
x = items[--top];
return true;
}
return false;
}

本文涉及对C和C++特性比较深入的探讨, 并不适合新手阅读, 仅仅希望解决问题的请跳转最后一节寻找解决方案。对于本文大部分内容, 需要读者至少清晰知道int* const和const int* 类型的区别, 并能读懂形如const int **const *const **的多层const类型, 确保不会被绕晕, 对此概念不清淅的可以阅读该回答。
g++编译通过的样例:/*sample1*/
char p2;
const char p1 = p2;
/*sample2*/
char * p2;
const char* p1 = p2;
/*sample3*/
char p2;
const char& p1 = p2;
g++编译错误的样例:char ** p2;
const char** p1 = p2;
1. 什么? g++编译错误? 我gcc第一个不服!当我看到我将int**类型赋值给const int**类型的代码被g++狠狠打上一个error之后,我第一反应是, 这怎么可能不行?把一个不带const的赋值给带const的, 不是理所当然应该编译通过吗?我脑洞大开想起了gcc, 希望gcc能站在我这边给予我赞同。于是我使用gcc编译了完全相同的这两行代码, 编译通过!我很感动, 但是我没法忽略gcc给我留下温柔的警告:warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
和g++的error进行对比如下:这一刻我意识到, 这个行为在编译器眼里并不是一个简单的规则, 不是一个简单的对与错。在这两行代码背后, 或许还有着更复杂的原因, 一个美丽的故事。2. const 的层次多重指针+多重const是一个一听就很晕的概念, 为了清晰地描述这类概念, 我先引入const的层次的概念。《C++primer》将int const * int类型中左边的const称为顶层const, 右边的const称为底层const,但没有涉及多重指针的相关概念。对于多重指针,我认为依旧按照这个描述来定义层次关系是没问题的。这样的层次关系可用以下拓扑表示。 int const * const 或 const int * const
const char * const *
char * const * const下文中,只要该拓扑结构不同,一律描述为const层次结构不同。 3. C语言之指向常量的指针在C语言语境下的const, 是一个很冷门的话题, 因为用的人真的不多, 唯一比较常见的用处是传递指针的时候禁止修改该地址的内容。但是对于为什么用得少, 一直还没有一个概念。在做了几个实验, 看了些资料后, 终于明白了其中特别重要的一个原因, 叫做“设计缺陷”。3.1. 两种情况下的警告 其他层的const相同,将上一层有const的指针赋值给上一层没有const的指针。(即指向const的指针赋值给不指向const的指针) /*sample1*/
const char *const *p1;
const char **p2 = p1;
/*sample2*/
const char* p1;
char* p2 = p1;
使用gcc编译通过,给出明确的警告
warning: initialization discards ‘const’ qualifier from pointer target type
在这种情况下,gcc明确地告诉我们我们这样做的后果是使这个指针指向的变量失去const属性,可能被修改。
除了[1]中提到的情况外,以及除了“两个最底层const不同”的多重指针相互赋值(值传递, 编译通过)的情况外, 出现的其他“const层次结构不同”的多重指针相互赋值的情况。最典型的例子莫过于我们标题中给出的样例。 /*sample1*/
const char** p1;
char** p2 = p1;
/*sample2*/
const char*const* p1;
char**p2 = p1;
/*sample3*/
const char*const* p1;
char**p2 = p1;
使用gcc编译通过,给出模糊的警告 warning: initialization from incompatible pointer type [-Wincompatible-pointer-types]
sample1中, 明显看出该赋值操作使**p1失去了它的const属性, 可能被修改。但是gcc并没有告诉我们是丢失了const属性, 而是模糊地给出"不兼容"的警告。这是为什么呢? 4. C语言挖下的大坑这个时候,回顾一下我们的题目中无法在C++编译通过的示例char ** p2;
const char** p1 = p2;
就像我们简单粗暴地认为将不带const的变量赋值给带const的变量是合乎逻辑的一样。最初的C标准指定者也犯下了一个大错, 就是允许了这样的行为(看来大神们也和会和我翻一样的错啊)。然而在标准即将发行的时候有人发现,这种赋值方式会导致一个神奇的漏洞,人们可以利用这个漏洞随意修改另一个const变量。以下骚气的代码来自《C primer plus》const int n = 13; /*我是一个与世无争的const变量*/
int *p1;
const int **pp2 = &p1; /* int**类型 -> const int**类型*/
*pp2 = &n;
/*const int*类型 -> const int*类型*/
*p1 = 10;
/*看起来没毛病...不对,与世无争的 n 怎么被修改了?*/
在发现了这个惊天大bug之后, 大神们没来得及深究背后的逻辑关系, 情急之下只好禁用(给出警告)了大部分不同const层次结构的变量之间的相互赋值,比如上面介绍到的一些情况。由于没查清楚真正的原因,所以对于该类型的赋值,编译器一律给出一个含糊其辞的incompatible警告,把这个巨大的坑留给可怜的开发者自己处理。至此, 我们已经解决了问题的一半, C语言的故事帮助我们弄明白了char**类型变量为何不能赋值给const char**变量。但是我们还没有实现我们的目的, 因为无论我们如何修改我们的代码, 也没有办法在gcc不发出警告的情况下实现我们为双重指针添加最顶层const限定的目的。也正是因为C语言的const非常鸡肋, 所以存在感很低, 大家用得非常少。5. 老爸的坑儿子填, 且看C++如何填坑《C primer plus》中简单地提到C++和C语言中const类型的区别, 其中主要特点就是C++的类型审查更加严格。毕竟C语言给的全是warning, 起码C++会给error。但是在上一节中, C语言很不负责任地留下了一个没有研究清楚的问题, 那就是这堆incompatible的警告里面, 到底哪些行为是应该被允许的, 哪些是不应该被允许的?聪明的C++语言设计者们深入研究了这个坑后发现以下规律并以此为基准对C++标准进行了制定。
只有出现"在某一层const后面跟着一层非const(不考虑最底层)"的情况下, 才会导致出现第4节中的bug。 也就是说, 在这种情景下多重指针的const要从右往左填满。
这里应该还有严格的数学证明, 我作为一个离散数学全忘了的彩笔就暂且跳过证明这一部分吧。根据我们"发现"的这个规律, 我们可以轻而易举地将我们的代码修改成g++喜欢的样子并写出各种各样疯狂的代码。在这里我们代入业务情景:定义了一个二维数组, 将它传给printArray()`函数的时候希望限定它不要被修改。
传值传引用定义了一个三维数组…
传引用2.传值…… …6. 所以到底应该怎么写呢?在上一节, 我们给出了非常装逼的解决方案, 同时也是最本质的解决方案, 但是我没把它写在最后一节, 是因为它并不是一个适合用于工程中的写法。接下来, 让我们暂且忘掉前面装逼的代码。回到我们的业务需求上, 看一下最适当的解决方案。Talk is cheap, show you the code!定义了一个二维数组, 将它传给 printArray()函数的时候希望限定它不要被修改。 传值。
传引用。 从C/C++的故事中走出来, 我不禁感慨:历史给他们套上沉重的镣铐,他们戴着镣铐跳舞,却依旧无所不能,永远不停地追逐着计算机世界中所剩不多的那一些浪漫。在他们身上,即使是缺陷,也是这样美丽的。这大概,就是世界上最好的语言吧 (狗头)

我要回帖

更多关于 不能将const char*类型分配到char 的文章

 

随机推荐