《C++Primer》笔记之二

来源:转载

第4章
1、数组的定义

数组的维数必须用值大于1的常量表达式定义,此常量表达是只能包含整型字面值、枚举常量或者用常量表达式初始化的
整型const对象。例如:

 1 #include <iostream>
2  using namespace std;
3 unsigned int get_size();
4
5  int main()
6 {
7 const unsigned buf_size = 512;
8 unsigned max_files = 20;
9 const unsigned return_value = get_size();
10
11 char input_buffer[buf_size]; //ok
12   string fileTable[max_files + 1]; //error in Visual Stdio,ok in Gcc
13   int test_scores[get_size()]; //error in Visual Stdio,ok in Gcc
14   int vals[return_value]; //error in Visual Stdio,ok in Gcc
15  
16 return 0;
17 }
18
19 unsigned int get_size()
20 {
21 return 0;
22 }

2、显式初始化数组
 在函数体外定义的内置数组,其元素均初始化为0;
 在函数体内定义的内置数组,其元素无初始化;
 不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造函数进行初始化。
 例如:

 1 #include <iostream>
2  using namespace std;
3
4  int value_outside[10];
5
6  int main()
7 {
8 int value_inside[10];
9
10 cout << "Outside" << "/t/t" << "Inside" << endl;
11 for(int index = 0;index < 10;index++)
12 cout << value_outside[index] << "/t/t" << value_inside[index] << endl;
13
14 return 0;
15 }
显示结果为:
 1 Outside Inside
2  0 2293508
3  0 2293560
4  0 2293728
5  0 2009095316
6  0 2008950864
7  0 -1
8  0 2009091625
9  0 2009091650
10  0 4273008
11  0 2293592

3、指针可能的取值
 一个有效的指针必然是以下三种状态之一:保存一个特定对象的地址;指向某个对象后面的另一个对象;或者是0值。
若指针保存为0值,表明它不指定任何对象。
把int型变量赋给指针是非法的,但允许把编译时可获得0值的const量赋给指针,如:

 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 int zero = 0;
7 int *pi;
8 const int c_ival = 0;
9
10 //pi = zero; //error
11   pi = c_ival; //ok
12  
13 return 0;
14 }
同样,可以把指针初始化为NULL,等效于初始化为0值。
预处理器变量不是在std命名空间中定义的,因此其名字应为NULL,而非std::NULL。
 
4、void*指针
它可以保存任何类型对象的地址,如:
 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 double obj = 3.14;
7 double *pd = &obj;
8 int iValue = 1;
9 int *pi = &iValue;
10
11 void *pv = &obj;
12 pv = pd;
13 pv = &iValue;
14 pv = pi;
15
16 return 0;
17
18 }

void*表明该指针与一地址值相关,但不清楚存储在此地址上的对象的类型。

5、指针的算术操作

两个指针减法操作的结果是标准库类型ptrdiff_t的数据,与size_t类型一样,它也是一种与机器相关的类型,size_t
是unsigned类型,而ptrdiff_t则是signed整型。它们有各自的用途:size_t类型用于指明数组长度,必须是一个正数;
ptrdiff_t类型则应保证足以存放同一数组中两个指针之间的差距,有可能是负数。
允许在指针上加减0,使指针保持不变,如果一指针具有0值(空指针),则在该指针上加0仍然是合法的,结果得到
另一个值为0的指针,也可以对两个空指针做减法操作,得到的结果仍为0。如:

 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 int *p1 = 0;
7 int *p2 = (p1 + 0);
8
9 cout << "p1" << "/t/t" << "p2" << endl;
10 cout << p1 << "/t/t" << p2 << endl;
11
12 return 0;
13 }
显示结果为:
1 p1 p2
2  0 0

6、下标和指针
在表达式中使用数组名时,实际上使用的是指向数组第一个元素的指针。
在使用下标访问数组时,实际上是对指向数组元素的指针做下标操作。如:

 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 int ia[] = {0,2,4,6,8};
7
8 int *p = &ia[2];
9 int j = p[1];
10 int k = p[-2];
11
12 cout << "*p = " << *p << endl;
13 cout << "p[1] = " << p[1] << endl; //p[1] == ia[2];
14   cout << "p[-2] = " << p[-2] << endl; //p[-2] == ia[0];
15  
16 return 0;
17 }
显示结果为:
1 *p = 4
2 p[1] = 6
3 p[-2] = 0

7、计算数组的超出末端指针
C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作,一般用来作哨兵使用。而计算数组超
出末端位置之后或数组首地址之前都是不合法的。如:

 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 const size_t arr_size = 5;
7 int arr[arr_size] = {1,2,3,4,5};
8 int *p = arr;
9 int *p2 = p + arr_size;
10
11 for(;p != p2;p++)
12 cout << *p << endl;
13
14 return 0;
15 }

8、指针和const限定符
①指向const对象的指针

1 const double *cptr;

首先cptr是指针,指向的是double对象,该对象是const类型的。如:

1 const double pi = 3.14;
2  const double *cptr = &pi; //ok
3  double pi1 = 6.28;
4 cptr = &pi1; //ok

非const指针不能指向const对象:

1 const double pi2 = 3.14;
2  double *ptr = &pi2; //error

因为&pi2地址处得对象为const,用非const指针指向该对象,则表示可以修改该对象,有冲突,所以不合理。
同理,也不能使用void*指针保存const对象的地址,而必须使用const void*类型的指针保存const对象的地址。

 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 const int universe = 32;
7 //void *cpv = &universe; //error
8   const void *cpv = &universe; //ok
9  
10 return 0;
11 }
允许把非const对象的地址赋给指向const对象的指针,如:
1 double dval = 3.1415926;
2  const double *cptr = &dval;
任何企图通过指针cptr修改其值的行为都会导致编译时的错误。但可以用其他方法修改其指向的对象,如:
 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 double dval = 3.1415926;
7 const double *cptr = &dval;
8 double *ptr = &dval;
9
10 *ptr = 2.72;
11 cout << *cptr << endl;
12
13 return 0;
14 }
显示结果为:
2.72
应该把指向const的指针理解为“自以为指向const的指针”。
同理,把const对象用非const变量引用时也是错误的,因为这样的话相当于作为非const变量的话,可以修改该对象,
所以不正确。如:
 1 #include <iostream>
2  using namespace std;
3
4  int main()
5 {
6 double pie = 3.1415926;
7 const double *cptr = &pie;
8 double &val = *cptr; //error
9  
10 return 0;
11 }

②const指针
如:

1 int errNumb = 0;
2  int *const curErr = &errNumb;

首先,从右至左看,curErr是const对象,然后它是指针,指向的为int对象。所以不能修改curErr,但是可以修改
curErr指向的int对象。

③指向const对象的const指针

1 const double pi = 3.1415926;
2 const double *const pi_ptr = &pi;

④指针和typedef
如:

 1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 typedef string *pstring;
6
7 int main()
8 {
9 string str = "pointer";
10
11 const pstring cstr = &str;
12
13 *cstr = "changed"; //ok
14
15 string str_pointer = "address changed";
16 cstr = &str_pointer; //error
17
18 return 0;
19 }
1 typedef string *pstring;
2 const pstring cstr;

从右至左看,cstr是pstring类型变量,此变量为const。
声明const pstring时,const修饰的是pstring的类型,而这个类型是指针,所以相当于这个指针是const的。相当于:

1 string *const cstr;

pstr是指针,指向string的对象,该对象为const;

1 const string *pstr;

pstr是const变量,该变量为指针,指向的对象为string类型。

1 string *const pstr;

9、永远不要忘记字符串结束符null

1 char ca[] = {'c','+','+'};
2 cout << strlen(ca) << endl;

ca是一个没有null结束符的字符数组,则计算的结果则不可预料。标准库函数strlen总是假定其参数字符串以null字符
结束,当调用该标准库函数时,系统将会从实参ca指向的内存空间开始一直搜索结束符,直到切好遇到null为止。

10、允许动态分配空数组
之所以要动态分配数组,往往是由于编译时并不直到数组的长度。
C++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组是合法的:10、允许动态分配空数组
之所以要动态分配数组,往往是由于编译时并不直到数组的长度。
C++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组是合法的:

1 char arr[0]; //error
2 char *cp = new char[0]; //ok

用new动态创建长度为0的数组时,new返回有效的非零指针。不能进行解引用操作,因为它毕竟没有指向任何元素。允许
的操作有比较运算,因此该指针能在循环中诗意哦能够;在该指针上加(减)0;或者减去本身,得0值。

第5章

1、bitset对象或整型值的使用
bitset类比整型值上的低级位操作更容易使用。如,假设某老师带了一个班,班中有30个学生,每个星期在班上做一个
测试,只有几个和不及格两种测试成绩,对每个学生用一个二进制位来记录一次测试及格或不及格,以方便我们跟踪每次
测试的结果。下面比较一下两种方法:

1 bitset<30> bitset_quiz1;
2 unsigned long int_quiz1 = 0;
使用bitset类型时,可根据所需要的大小明确地定义bitset_quiz1,它的每一位都默认设置为0值。如果使用内置类型
来测试成绩,则应将变量int_quiz1定义为unsigned long类型,这种数据类型在所有机器上都至少拥有32位的长度。最后,
显式地初始化int_quiz1以保证该变量在使用前具有明确的值。
假设第27位所表示的学生及格了,则可以使用:
1 bitset_quiz1.set(27);
2 int_quiz1 |= 1UL << 27;
使用bitset类,可直接传递要置位的位给set函数,而用unsigned long实现时,实现的方法则比较复杂。
如果老师重新复核测试成绩,发现第27位学生实际上在该测试中不及格,则:
1 bitset_quiz1.reset(27);
2 int_quiz1 &= ~(1UL << 27);
最后,可通过以下代码或者第27个学生是否及格:
1 bool status;
2 status = bitset_quiz1[27];
3 status = int_quiz1 & (1UL << 27);

2、IO操作符为左结合

1 cout << "hi" << " there" << endl;
 执行为:
1 ((cout << "hi") << " there") << endl;
移位操作符具有中等优先级,其优先级比算数操作符低,但比关系操作符,赋值操作符和条件操作符优先级高。若IO
表达式的操作数包含了比IO操作符优先级低的操作符,相关的优先级别将影响书写该表达式的方式。如:
1 cout << 42 + 10; //ok
2 cout << (10 < 42); //ok
3 cout << 10 < 42; //error

3、new和delete表达式
动态创建对象时,只需指定其数据类型,而不必为该对象命名。new表达式返回指向新创建对象的指针,如:

1 int i;
2 int *pi = new int;

这个new表达式在自由存储区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi。

动态创建对象的默认初始化:
如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象则用该类的默认
构造函数初始化;而内置类型的对象则无初始化。比如:

 1 #include <iostream>
2 using namespace std;
3
4 int *outsidePointer = new int;
5
6 int main()
7 {
8 int *insidePointer = new int;
9
10 cout << "Outside/t/tInside" << endl;
11 cout << *outsidePointer << "/t/t" << *insidePointer << endl;
12
13 return 0;
14 }
显示结果为:
1 Outside Inside
2 0 4064536
 同样,可以对动态创建的对象做值初始化
1 string *ps = new string();
2 int *pi = new int();
3 cls *pc = new cls();

以上表明程序员想通过在类型名后面使用一对内容为空的圆括号对动态创建的对象做值初始化。
对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化:无论程序是明确地不初始化还是要求进行值初始化
,都会自动调用其默认构造函数初始化该对象。而对于内置类型或没有定义默认构造函数的类型,采用不同初始化方式则有
明显的差别。

C++没有明确定义如何释放不是用new分配的内存地址的指针,下面提供了安全和不安全的delete表达式:

1 int i;
2 int *pi = &i;
3 string str = "dwarves";
4 double *pd = new double(33);
5 delete str; //error
6 delete pi; //error
7 delete pd; //ok
如果指针的值为0,则在其上做delete操作是合法的,但这样做没有任何意义:
1 int *ip = 0;
2 delete ip;
删除0值的指针式安全的。
在delete之后,重设指针的值
执行语句:
1 delete p;

后,p变成没有定义。尽管p没有定义,但仍然存放了它之前所指向对象的地址,然而p指向的内存已经被释放掉,因此p不再有效。
删除指针后,指针变成悬挂指针。悬挂指针指向曾经存放对象的内存,但该对象不再存在了。悬挂指针往往导致程序
错误,而且很难检测出来。
一旦删除了指针所指向的对象,立即将该指针置为0,这样就非常清楚地表明了指针不指向任何对象。
 
删除指向动态分配内存的指针失败,因而无法将该快内存返还给自由存储区,删除动态分配内存失败称为"内存泄露",
它一般很难发现,一般需应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄露才会显露出来。
读写已删除的对象。如果删除指针所指向的对象之后,将指针置为0值,则比较容易检测出这类错误。
 
4、强制类型转换

1 const_cast
将转换掉表达式的const性质。如,
1 const char *pc_str;
2 char *pc = const_cast<char*>(pc_str);

只有使用const_cast才能将const性质转换掉。

编译器隐式执行的任何类型转换都可以由static_cast显式完成:

1 double d = 3.1415926;
2 void *p = &d;
3 double *dp = static_cast<double*>(p);
可通过static_cast将存放在void*中的指针强制转换为原来的指针类型,此时应确保保持指针值。


分享给朋友:
您可能感兴趣的文章:
随机阅读: