一些重要的C++ 基础

基本类型与内存


内存中寻址(addressable)的最小单位是字节 (byte),存储的最小单位是字 (word)

在大部分计算机的实现中,1 byte=8 bits, 1 word = 4 bytes / 8 bytes = 32 bits / 64 bits


举例,下面是内存中的一个字:(设 1 word = 4 bytes)


左边的是字节表示的地址,从地址736424往后的32个bits就是一个word的大小,含4个字节:736424、736425、736426、736427。


而不同类型所占的比特数不同,因此,知道了某一个类型的对象的初始地址(字节地址)还不行,还必须知道该类型占用多少个比特,这样才知道从该初始地址往后的几个比特才属于这个对象。


在写程序的时候,关于类型定义的几个有用的 tips:


1. 当一个数大小不可能为负时,使用 unsigned。

2. 使用整数时,用 int. 因为 short 通常不够用,而long 的最小大小和 int 相同 (C++ standard). 如果要存储的整数的

    大小超过了 int 的最小大小 (C++ standard),就使用 long long 吧。

3. 当需要用char 来存储小整数的时候,请指明 是 unsigned char 还是 signed char,因为不同机器上的实现不一样。

4. 当需要进行浮点数运算的时候,尽量使用 double. 因为 float 的精度通常不够。在许多机器上, 双精度运算几乎和单       精度运算一样快,甚至更快。long double 这类型,虽然提供了更长的精度,但是运算会带来明显的耗费。


类型转换

内置算术类型转换



自定义类的类型转换请参考我的博文:类的自动转换。更多的关于类型转换的知识点,还是请好好阅读 《C++ Primer 5th》吧。鄙人的博文只是把自己认为容易忘记或比较重要的知识点记录下来而已。


发生越界赋值


unsigned 越界赋值:

对于一个unsigned类型,如果给它赋的值超过了它的界限,那么他实际存储的值为:value % unsigned类型能表示的值的数目。如,unsigned char 类型能表示的值的数目为256(0~255),那么将 -1 赋给 unsigned char 类型的变量,会得到 -1 % 256 = (  (255)%256 - (256)%256 ) % 256 = 255


signed 越界赋值:

对于一个signed类型,则不是这样子了。这个signed类型的变量被赋给一个超出界限的值之后,存储的值是不确定的。从10输出到0,下面的for循环和while循环分别是错误和正确的写法:

for(unsigned i = 10 ; i >= 0 ; i--)
{
	std::cout << i << std::endl;
}

unsigned i = 11;
while(i > 0)
{
	-- i;
	std::cout << i << std::endl;
}

对于 signed 类型变量越界的情况,有些系统会使用 "wrapped around" 的方法,就是当成一个环形的方法来处理。但是别的系统就不一定了。所以说,signed 类型变量越界的情况是 undefined 的。


另外,在表达式中如果同时有unsigned 类型和 signed 类型出现,那么表达式的值会自动转换成 unsigned类型。千万注意。


an object is region of memory that has a type.


变量的默认初始化


a variable with built-in type will be initialize to zero by default when it's created outside any function body.

But if it is created inside the function body, it's undefined(the value is unknown)

As for self-defined class type, it depends on their constructor.


变量的声明(declaration) 和 定义(definition):

       声明只是指定了变量的类型、名称;而定义不仅仅指明了变量的类型和名称,而且还为它分配了内存,并且提供初始化(包含默认初始化)

      Declaration only specifies the type and the name of a variable, but Definition not only does that but also allocates memory for the variable and provides initialization.

      you can use the keyword "extern" to explicitly specify the declaration of a variable. But sometimes we had better not to initialize the variable. Why ? Because it's a declaration. If you want to do it, just make it a definition.


extern int i;	//declares but does not define i
int j;			//declares and defines j


extern double pi = 3.1415926; //definition


And, an extern that has an initializer is a definition.

Remember ! It's an error to provide an initializer on an "extern" inside a function.


note: Variables must be declared only once but can be declared many times.

example: a variable used by multiple files. (separate compilation relevant)


C++ is a statically typed language. We must declare the type of a variable before we use it.


       Names declared in the outer scope can be accessed inside the inner scope but names declares in the inner scope can not be accessed in outer scope. Names can be redefined in the inner scope since it has been declared in the outer scope. And the inner one will hide the outer one. You must use scope operator("::") to get the outer one.


Reference

     reference operator: &

A Reference is just another name for an already existing object. It' s not an object.

More details about Reference can refer to another article of mine -- 指针与引用的区别

A reference may be bound only to an object, not to a literal or to the result of a more general expression. In fact, a const reference can be bound to a literal.


Some example codes using references:

double d = 0, &r2 = d;
r2 = 3.14159; /* It's valid. -- change the value 
			  	   of the object r2 refers to as 3.14159*/
r2 = r1;	  /* It's valid -- change the value 
			  	   of the object r2 refers to as the value of
			  	   the object which r1 refers to (that is 0)*/
i = r2;		  /* It's valid -- change the value of i as 
			  	   the value of the object to which r2 refers
			  	   (that is 0) */
r1 = d;		  /* change the value of the object which r1 refers
					   to as 0*/
std::cout << "r2: " << r2 << std::endl;	// it will print 0
std::cout << "r1: " << r1 << std::endl; // it will print 0


Pointer

dereference operator: *

Unlike reference, pointer is an object. It can also be used to indirectly access to an object like reference.

More details about pointer can refer to another article of mine -- 指针与引用的区别


Like other built-in types, the object that a pointer points to is undefined if this pointer is defined by default inside a function.


Two pointers are equal ( == operator return true) 

(1)if they are null 

(2)if they address the same object 

(3)if they are both pointers one past the same object方法  


(3)是不是觉得有点难理解,毕竟是英语的, "one past" 指的就是刚好越界的意思,下面有我写的一个示例,看不懂那就没办法啦。

int list[10];
int *p1 = &list[9] + 1, *p2 = &list[10];

//the condition will be true
if(p1 == p2)
	std::cout << "Two pointers are equal !" << std::endl;

      void * pointer can hold the address of any object with any type, but we can not use it to operate on the object it points to because the concrete type of the object is unknown. And, the type determines what operations we can perform.



const qualifier

       

       有时候,我们不希望改变一个对象的值的时候,我们要用const来修饰它。也因此,由const修饰的变量在创建的时候就要被初始化,而且以后其值再也不能被改变。对于一个源文件来说,编译器在编译的时候会将源文件中的那个标识符替换成那个常量。由于这个原因,多个文件中可以定义名称相同的常量--反正最后会被替换成值。这样有一个缺点--如果我希望只在一个文件中定义常量,然后其他的文件可以共享使用这个常量就办不到了。


      C++提供了 extern 关键字来提供这个功能:

      

      我可以在 file1.cc 中初始化定义一个常量 extern const int bufsize = 5。然后,如果需要在 file1.h 中使用这个常量,那我只需要声明一下:extern const int bufsize; 即可。为多个文件所共享的常量,只需在一个文件中定义一次,其他文件中要使用只需声明一下就好。另外,不要忘记使用 extern 关键字哦。

      另外,设计 指针和引用与 const 修饰符有关的部分,请参考我的博文 -- 指针与引用的区别

       对指针来说,low-level const 指的是指针声明时,放在最外面的const修饰符,这决定了通过该指针不能改变指针所指向的那个对象。top-level const 则指的是放在里面的const修饰符,这表明该指针本身(不要忘记,指针本身是一个对象)是一个常量。对于引用来说,则只有 low-level const,因为引用本身不是对象,修饰引用的const只是用来表明 ”不能用这个引用来改变“引用的对象而已。另外,对于自定义 class type 和 内置 built-in type 则都是 top-level const。

       总结一下:指针既可以拥有 low-level const 也可以拥有 high-level const,并且它们的出现是互不影响的;引用只能有 low-level const;自定义类型和内置类型只能有 high-level const。

       当把一个值(不仅指对象还包括引用)赋给另外一个值的时候,不用考虑 双方的 top-level const ,但是要考虑双方的 low-level const。可以赋值当且仅当:


1)low-level const 情况相同,但是要求类型相同。

2)low-level const 情况不同,但是有正确的临时变量转换

       1. 指针:类型一样,但是被赋值一方使用 low-level const 修饰, 赋值一方不使用 low-level const 修饰

       2. 引用:类型可以不一样(但要求能转换,比如说double 到 int),但是被赋值一方使用 low-level const 

                      修饰,赋值一方不使用 low-level const 修饰


下面是一些示例:

const int value1 = 1; //没有low-level const 修饰

/* low-level const 情况相同 
 * */

//
//值 -> 指针
const int *p1 = &value1;		//valid
//指针 -> 指针
const int *p2 = p1;				//valid
const int *const p3 = p1;		//valid
//int *p4 = p1;					//invalid, low-level const情况不同

//值 -> 引用
const int &r1 = value1;			//valid
//引用 -> 引用
const int &r2 = r1;				//valid
//int &r3 = r1;					//invalid, low-level const情况不同


/* ----------------------------------------- */


/* low-level const 情况不同但允许合理的临时变量类型转换	
 * */

double value2 = 2.0;
//const int *P1 = &value2;		//invalid, this kind is only for reference

const int &R1 = value2;			//valid, convert double to const int

const double &R2 = value1;		//valid, convert int to const double

int value3 = 3;
const int *p4 = &value3;		//valid, convert int* to const int*


sizeof


        C++ sizeof 操作符,除了可以判断类型的大小外,还可以传入表达式,用法:sizeof expr(可视优先级情况给表达式加括号)。当它判断表达式的时候,是 “肤浅” 的。

        肤浅的地方在于 sizeof 操作符并不会去把 expr 的值算出来。例如,当传入的表达式为一个指针的反引用(*p)的时候,只会返回这个指针在声明的时候,所指向的那个类型的大小。那么显而易见,对于一个父类的指针 pa指向一个子类对象的时候,sizeof *pa 返回的是父类类型的大小,而不是子类类型的大小。因为,sizeof 是不会把 expr 的值算出来的,即不会真的去反引用出 pa 指向的对象,然后 sizeof 这个对象。

        举两个例子,自己感受下:

#include <iostream>
#include <string>
using namespace std;

class Parent
{
public:
	int id;
};

class Child : public Parent
{
public:
	string name;
};

int main() {
	Parent a;
	Parent *pa = &a;
	Child b;
	Child *ch = &b;
	cout << "sizeof *pa, a Parent obj pointed by a pointer of type Parent* if evaluated: "
		<< sizeof *pa << endl;
	cout << "sizeof *ch, a Child obj pointed by a pointer of type Child* if evaluated: "
		<< sizeof *ch << endl;

	pa = &b;
	cout << "sizeof *pa, a Child obj pointed by a pointer of type Parent* if evaluated: "
		<< sizeof *pa << endl;
	return 0;
}

#include <iostream>
#include <string>
using namespace std;

int test();

int main() {
	//it will print 4
	cout << sizeof test() << endl;
	return 0;
}


Extra

const_cast 只是能够把 用了 low-level const 修饰的引用/指针 赋给 没用 low-level const 修饰的引用/指针 而已。



显式类型转换


C++ 11的显式类型转换有 static_cast、const_cast、dynamic_cast 还有 reinterpret_cast。

dynamic_cast
        还没看到

static_cast
        一般C++规定好的转换规则中,除了 low-level const 的转换外,都可以满足

const_cast
        的话,只是能够把 用了 low-level const 修饰的引用/指针 赋给 没用 low-level const 修饰的引用/指针 而已,反过来也行。至于进一步的,能否用得到的引用/指针改变 地址所在的对象的值,那就要看具体情况了,不赘述。

reinterpret_cast
       可以做 low-level 级别的解释,就是能够把一种类型的值,用另一种类型来解释。有点说不清楚呵。我写了一个例子,有一个 unsigned short 类型的指针指向一个unsigned short 类型的值为 97 << 8 的变量。然后,使用 reinterpret_cast 把这个指针 解释为一个 指向一个char 类型值的指针 并赋给一个 char 类型的指针,然后使用这个 char 类型的指针,就访问到了 97。

#include <iostream>
using namespace std;

int main() {
	//corresponding int value: 97
	char alpha = 'a';
	//97 << 8 = 24832 
	unsigned short us = ((int)alpha) << 8;
	unsigned short *pus = &us;

	char *ch = reinterpret_cast<char*>(pus);

	/*
	 * Because pointer points to low byte by default, so
	 * we need to increment ch for one byte
	*/
	ch ++;
	cout << "number: " << int(*ch) << endl;
	cout << "corresponding alpha: " << (char)(*ch) << endl;
	return 0;
}


       其实,对象,也就是占用了一块内存的字节用来存储比特而已,它的类型决定了它能占多少字节。我在这里把 2 个 byte 的 unsigned short 类型解释为 1 个 byte 的 char 类型。然后反引用这个char 指针的时候,就可以得到原来那个 2 个 byte 中的低位字节。在上面的代码中,地位的字节是8个0的,我移了一下位,就可以得到高位字节也就是 97了。
       最后,对于这些显式转换,作者还给了我们忠告:别乱用 cast,除了 const_cast 对于 函数重载 比较有用之外,其他的都不太用到。如果用到 reinterpret_cast 的,一般都是程序设计上存在重大的缺陷(design flaw)--- 类型好好定义了,还用重新解释?
       显示类型转换除了 用 “cast” 来强制转换之外,还可以使用早期版本的C++就有的方式,比如 type (expr) 和 (type) expr。前者是函数风格的 cast,后者则是 C 风格的 cast。这钟老式的方法包含了 const_cast、static_cast 和 reinterpret_cast 三种 cast 的功能。一般是,如果 const_cast 和 static_cast 不能满足需求的话,可能会启用 reinterpret_cast。比如下面的代码就使用了 reinterpret_cast 的功能。
int value = 1;
int *pi = &value;
char *pc = (char*) pi;	//use the functionality as reinterpret_cast


       所以,我们要好好掌握内置类型的隐式转换,还要学习写自定义的类(self-defined class)和别的类型的之间的转换方法。后者的话,请参考我的博文 --- 类的自动转换

Reference

[1] 《C++ Primer Fifth Edition》  Stanley B. Lippman, Josée Lajoie, Barbara E. Moo 

[2]   指针与引用的区别

[3]   类的自动转换

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值