关于extern
使用extern 声明而不定义,它是说明变量定义在程序其他地方
全局不初始化的extern int i; 是声明不定义;只要声明并且有初始化式,那么就是定义;带有extern且有初始化的声明(也是定义),比如extern float fval =2.34; 这种必须放在函数外面,否则出错
文件B要访问另外一个文件A中定义的变量,那么在B中必须先extern声明一下,并且不需要include A。另外,A中定义的变量一定是全局变量。
Extern C
const常量在函数外定义默认是文件级,别人不可访问。要想成为程序级(被其他文件访问)必须要加extern。A.pp中extern const int ival=23;而在B.cpp中extern const int ival;声明一下就可以使用(声明时const不是必须,但最好加上)。另外,const常量在声明的时候必须初始化,如果是使用常量表达式初始化,最好放在头文件去定义(头文件特殊的可以放定义的三个之一)。否则只能放在源文件中定义,并加上extern以能被多个文件共享。
struct MSGMAP_ENTRY {
UINT nMessage;
void (*pfn)(HWND, UINT, WPARAM,LPARAM);
};
struct MSGMAP_ENTRY _messageEntres[] = {
WM_LBUTTONDOWN, OnLButtonDown,
WM_RBUTTONDOWN, OnRButtonDown,
WM_PAINT, OnPaint,
WM_DESTROY, OnDestroy
};
关于结构体的说明:
上面定义了一种结构体类型后,后面要定义数据类型时就要struct MSGMAP_ENTRY 变量名。 蓝色部分就是当变量类型使用
1. 只有内置类型存在字面值,没有类类型或标准库类型的字面值(可以这样理解,是内置类型组成了其他类型)。
C++中有整型字面值,浮点字面值,布尔字面值和字符字面值,字符串字面值,转义序列,多行字面值
2. 下面
a) ‘数字1’===0x31 (49)
b) ‘A’===0x41 (65)
c) ‘a’=====0x61(97)
3. 由空格,制表,换行连接的字符串字面值可以连接成一个新的字符串
但是连接字符串与宽字符串的结果就不可预料了
代码续行 \后面不能有空格,只能是回车,接下来的一行要从头开始,没有缩进 字符串换行要加/ ===》小线倾斜的方向不一样
4. 1024f有错,整数后面不能有f
5. 2.34UL有错,浮点数后面不能有U
6. 标识符不能以数字开头
7. 直接初始化与复制初始化 int ival = 123; int ival(34);
8. int ival = 09; 错! 八进制数,不能有大于等于8的数字
9. 函数外定义的变量初始化为0,函数内的变量不进行初始化(可能是一些无意义的值但是合法的值,所以造成错,所有编译器难以发现所有这类错)
所以,建议每个内置类型对象都要初始化
在函数外定义的类类型对象使用默认构造函数初始化,对于没有默认构造函数的类型,要显式初始化,即使用带参构造函数
10. 初始化不是赋值。初始化要分配空间并给初值,而赋值则是要替换当前值
11. 声明与定义的区别
a) 定义要分配空间,一个变量程序中只能定义一次
b) 声明用于向程序表明自己的名字和类型,程序中可以出现多次;定义也是声明,定义的时候也声明了它的名字和类型
c) 使用extern 声明而不定义,它是说明变量定义在程序其他地方
函数内或外int i; 都是定义(都分配空间,但函数内没有初始化为0) // 不能放在头文件中。
d) 全局不初始化的extern int i; 是声明不定义;只要声明并且有初始化式,那么就是定义;带有extern且有初始化的声明(也是定义),比如extern float fval = 2.34;这种必须放在函数外面,否则出错
e) 文件B要访问另外一个文件A中定义的变量,那么在B中必须先extern声明一下,并且不需要include A。另外,A中定义的变量一定是全局变量。
f) 在正式编写程序语句前定义的一些全局变量或局部变量,在C中为声明,C++中为定义 ( int a;//在标C中为声明,是不可执行语句;在C++中为定义)
12. 定义在函数外的变量有全局作用域
13. 局部同名变量屏蔽了上层作用域的变量,烂程序,不易读。在局部中要想使用全局中的同名变量在变量前使用域作用符::
14. 在内建数据类型的情况下,++i与i++效率没区别,在自定义数据类型的情况下++i的效率高
15. c++中,定义和声明可以放在任何可以放语句的位置,所以,通常把一个对象定义在首次使用它的地方是一个很好的办法
16. const int pi = 3.14; 采用const易维护(在多次出现3.14的地方使用const变量来代替)
再比如:
const int max = 23;
for (int i = 0; i != max; ++i)
标准C++中,for中定义的i只在语句作用域中(for语句),出了for就不可见了
17. 全局变量的程序级与文件级, const全局变量
a) 普通变量在函数外定义就是程序级,别的文件要使用只先extern声明一下就行。并且不用include变量定义的文件
b) const常量在函数外定义默认是文件级,别人不可访问。要想成为程序级(被其他文件访问)必须要加extern。A.pp中extern const int ival=23;而在B.cpp中extern const int ival;声明一下就可以使用(声明时const不是必须,但最好加上)
c) const常量在声明的时候必须初始化,如果是使用常量表达式初始化,最好放在头文件去定义(头文件特殊的可以放定义的三个之一)。否则只能放在源文件中定义,并加上extern以能被多个文件共享。
18. 关于const引用与非const引用与它们所绑定的对象的关系:(const引用是指向const对象的引用,非const引用是指向非const对象的引用)
a) 非const引用类型必须要与它所引用的变量的类型一致(非const引用只能绑定到与该引用同类型的对象,而不能是右值)
int ival =12;
int &ref = dval 或34; //error 初始化的时候只能绑定到同类型的对象(不能是右值)。 非初始化的时候可以被赋值,引用与其绑定的对象的值都会改变
b) const引用可以绑定到不同但相关(即可以转化)的类型的对象(可以是非const对象)或右值
比如[const] double dval= 3.14; //这里要不要const都行
const int &refVal = dval; // warning,编译器会中间把3.14转成int temp的3,然后再给了refVal,这里编译不出错,但是会给出警告
constint &refVal2 = 2.33; // warning,这里const不可少,不然不使用右值
refVal2 = dval; // error, const引用的值不可改变
19. enum color {red, blue=4, white};其中white是5
枚举成员是常量而不能改变,所以初始化时要用常量表达式,const常量与整形字面值都是常量表达式
扩展color black = red; // 必须使用里面定义的来作为右值进行初始化
color pink = 3; // error
20. 设计类:从操作开始设计类。先定义接口,可以决定需要哪些数据以及是否需要私有函数来支撑公有函数
21. class与struct定义类仅仅影响的是默认访问级别,struct为public,class为private
22. c++支持分别编译separatecompilation,头文件和源文件。将main放在其他的源文件中.
头文件中有类定义,extern变量声明,变量和函数声明,带来的莫大的好处,一是统一性:保证所有文件使用的同一声明,二是易维护:需要修改时,只改头文件就行了,易维护
设计头文件时注意:声明最好放在一起。编译头文件需要一定的时间。有些C++编译器支持预编译头文件,需要查手册。
头文件用于声明,而不是定义——有三个例外:类定义,用常量表达式初始化的const变量和inline函数的定义。它们可以在多个源文件中定义(相当于头文件被include到了多个源文件中),只要在多个源文件中的定义时相同的。
解释:允许在头文件中定义类和inline函数是因为编译器需要它们的定义来产生代码,对于类类型需要知道类对象的数据成员和操作才能分配空间。但是对于单个源文件A.cpp为了避免多重包含,在定义时须加#ifndef与#endif(头文件应该有保护符,会被其他文件包含)
如果将inline函数的定义放在某源文件中,那么其他源文件将永远访问不到
允许在头文件中定义const常量的原因:const常量是文件级作用域的,多个文件包含它互不影响,不算是重复定义。所以可以在头文件中定义。多个文件共享const变量的前提是必须保证多个文件使用的是相同的名称和值,将它放在头文件中,谁需要的时候就include它就行,安全方便。
另外要注意:在实际中,源文件里大多编译器只是像宏替换一样的使用常量表达式来替换const变量,而没有分配空间来存储用常量表达式初始化的const变量。
如果const变量不是用常量表达式初始化的,这就不应该放在头文件中定义。此时它应该放在源文件中定义并初始化,加上extern以使它能够被多个文件共享。
23. //Page58页小字部分:编译和链接多个源文件组成的程序
24. 关于inline函数
inline函数目的是:为了提高函数的执行效率(速度)。以目标代码的增加为代价来换取时间的节省。
非内联函数调用有栈内在的管理,包括栈创建和释放的开销。函数调用函数调用前保护好现场,返回后恢复现场,并按原来保存的地址继续执行。对于短小且频繁执行的函数,将影响程序的整体性能
在C中可以用#define,编译器用复制宏代码的方式取代函数调用,但没有参数类型检查。
有两点特点注意的:
(1) 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。
(2) 内联函数要在函数被调用之前定义,否则内联失效。将它的定义放在头文件中就很好的可以做到这点,由于是在源文件中先include,再后面调用的,就保证了调用之前定义
(3)关键字inline必须与函数定义体放在一起才能使函数真正内联,仅把inline放在函数声明的前面不起任何作用。因为inline是一种用于定义的关键字,不是一种用于声明的关键字。(根据高质量C/C++指南,声明前不应该加,因为声明与定义不可混为一谈)
25. 复合类型compoundtype,引用,数组,指针
1.#include
usingstd::string;
using std::cout;
2.头文件中定义确定需要的东西
3. 注意区分string类型与字符串字面值
4. 在Windows下用命令行要编译和运行.cpp文件
打开vs2005命令提示
cl /EHsc simple.cpp 生成exe文件//EHsc 命令行选项指示编译器启用 C++ 异常处理
若要运行 simple.exe 程序,请键入 simple 并按 Enter
要加入程序变量: simple < data\book_sales
vs2005中同样也可以加入程序变量,就在命令行参数里
5.
a) cin.getline(char*, int, char) // 是istream流
// 可以接收空格并输出,它的参数有三个,第三个是结束符,默认为'\n'
char ch[20];
cin.getline(ch,5);
cout<< ch << endl; // 输入abcdefg,输出abcd最后一个为'\0'
//这个\0是自动加上的,所以可以cout << ch;
char ch[20];
cin.getline(ch,5,'a'); // 遇到a结束,并加上\0
cout<< ch << endl; //输入xyamnop,输出xy
b) getline(cin, str), 须加#include
#include
istream& getline( istream& is, string& s, char delimiter = '\n' ); // delimiter分隔符
接收一行字符串,可以接收空格并输出,遇到回车返回,并丢掉换行符。
不忽略行开头的换行符,如果第一字符是回车,那么str将是空string
逐行输出:
string str;
while(getline(cin, str)) {
cout << line << endl; // 由于不含换行符,需要endl刷新输出缓冲区
}
逐词输出:
string str;
while (cin>> str) {
cout << str << endl;
}
c) cin.get()吃掉没有用的字符,读到回车才退出
d) char ch1[100],ch2[100];
cin >> ch1 >> ch2; //接收字符串,忽略有效字符前的空白字符,以空白符(空格,TAB,回车)作为输入结束
而getline(cin,str) 不忽略开头的空白字符,读取字符直到遇到换行符,读取终止并丢掉换行符
e) gets(str) 接收一个字符串,可以接收空格并输出。加#include
与getline(cin,str)类似
f) getchar()无参数,须加#include
6. str.size()的实现(返回有效字符的个数,不含最后的空字符,它的类型是string::size_type)
constchar *st = "The expense of spirit\n";
int len = 0;
while (*st) { ++len; ++st; }
str.size的应用
for (string::size_type ix = 0; ix !=str1.size(); ++ix)
7. string对象可以==, >=, <= !=操作
8. string的赋值操作:str1 = str2;须先把str1的内存释放,然后再分配能存放副本大小的空间,再复制过来
9. string s2 = “hello” + s1; //error,开头必须是变量
10. #include
isalnum(c) 字母数字
isalpha(c) 字母
iscntrl 控制字符
isdigit 数字
isgraph 不是空格但是可以打印
islower 小写
isprint 可打印
ispunct 标点
isspace 空格
isupper 大写
isxdigit 十六进制数
tolower 变小写
toupper
11. p78 使用C标准库头文件,使用#include
12. string s;
cout << s[0] << endl; // error
13. t3_10,输入一个字符串,去掉里面的标点:
ispunct(ch)
result_str+= ch;
14. string是数据类型,vector是模板 vector
15. vector
vector
虽然能预先分配内存,但使用空vector来push_back更好
for(ix!=vector_size) vec.push_back(ix*5);
vector
for(vector
vector
for(vector
//cout << *it << endl;
*it = 42;// error遍历只读
}
vector的操作p81 , ==, !=, < >=
16. 迭代器可以==或!=操作,若指向同一个元素 则相等
迭代器的算法操作
it+n size_type类型
it-n difference_type类型
it1-it2 difference_type类型 it1或it2可以是vec.end()
记住:push_back()等改变长度的操作,使存在的迭代器全部失效!!
vector
17. bitset
bitset
bitset
bitset
如:
bitset<16> bitvec1(0xffff); // 高十六个0去掉, 0到15位设为1
0xffff代表32位的unsigned long,高十六个0,低十六个1
bitset<128> bitvec2(0xffff); // 31位以上的都是0
string s(“1100”); // s[0]值为1
bitset<8> bitvec3(s); // 读入位集的顺序是从右向左(用string最大下标元素来赋值给bitvec的最小下标值),结果是0000 1100, 这样bitvec[0]的值是最右的那个0
size_t sz = bitvec1.size(); // #include
//一个与机器相关的unsigned类型,其大小足以保证存储内存中对象的大小
if (bitvec.test(i))
if (bitvec[i])
bitvec.reset(); 全为0
bitvec.set(); 全为1
bitvec.flip(1);
bitvec[1].flip();
bitvec.flip(); // 全部取反
uLong = bitvec.to_ulong(); 取回unsigned long值
// 若bitvec的位数128超过了unsigned long长度,那以会产生overflow_error异常
t3_24:
1,2,3,5,8,13,21的相应位置为1
bitset<32> b;
int x = 0, y = 1, z = x + y;
while (z <= 21) {
b.set(z);
x = y;
y = z;
z = x + y;
}
1. int arr[32]; // 维数必须是字面值,枚举常量,常量表达式初始化的const变量
上面这句话在函数外,则初始化为0
若定义在函数内,没有初始化,但分配了空间
int arr[5] = {1,2,3}; // 剩下的初始化0,若是类类型就使用默认初始化构造函数, java中不行,不能写维数的
vector
2. 数组不能直接复制和赋值
3. 数组下标越界导致:buffer overflow
4. 比较两个vector,先比较长度
5. 现代C++使用vector替换数组,使用string替换C风格字符串
6. 关于指针:
a) string* s1, *s2看上去很不好,所以尽量将*与变量名放在一块
b) 避免使用未初始化的指针
C++没有办法检查出未初始化的指针,使用它可能会导致基础数据
如果要指向的对象还没有存在,那么就先不要定义这个指针。如果要定义,就=0或NULL(#include
预处理器变量NULL不是在std命名空间中定义的,所以不是std::NULL
c) void *
void* 这不叫空指针,这叫无确切类型指针.这个指针指向一块内存,却没有告诉程序该用何种方式来解释这片内存.所以这种类型的指针不能直接进行取内容的操作.必须先转成别的类型的指针才可以把内容解释出来;
'\0'不是空指针所指的内容,而是表示一个字符串的结尾,不是NULL;
真正的空指针是说,这个指针没有指向一块有意义的内存,比如说:
char* p;
这里这个p就叫空指针.我们并未让它指向任意地点,又或者 char* p = NULL;这里这个p也叫空指针,因为它指向NULL 也就是0,注意是整数0,不是'\0'
一个空指针我们也无法对它进行取内容操作.
空指针只有在真正指向了一块有意义的内存后,我们才能对它取内容.也就是说要这样p = "hello world!"; 这时p就不是空指针了
后面会看到:不能使用void*指向const对象,必须使用const void *类型的指针保存const对象的地址
void *p; 只支持以下操作:
与另一指针比较
作参数或函数返回值
给另一个void *赋值
7. 指针与引用的比较:
引用定义时必须初始化!
引用被赋值后改变的是所引用的对象
指针被赋值后将会指向另一个变量的地址,不专一
8. int arr[5];
int *p = arr;
int *p2 = p + 2;// 那么,p的算术运算要求原 指针与计算出的新指针都要指向同一数组的元素,结果范围必须在[0,5],//注意这里5也算合法结果
注意:C++允许计算数组或对象的超出末端的地址,但不允许对此地址进行解引用操作
可以把指针看成是数组的迭代器
ptrdiff_tn = p1 – p2; // #include
// 与机器相关:size_t是unsigned, ptrdiff_t是signed
注意,ptrdiff_t只保证指向同一数组的两个指针间的距离,若是指向不同的指针,那么error
t4_17:
p1 += p2– p1; 若p1与p2指向同一数组那么始终合法,让p1指向p2所指向的地址
9. 关于const指针
a) 指向const对象的指针
const double pi = 3.14; // 不能用普通指针来指向它 但是相反地,可以用const double*的指针指向非const的变量
const double *cptr = 0或NULL; //这里const限定的是所指向的对象,而不是cptr本身,所以定义的时候可以不初始化, 蓝const是必须的,防止通过cptr来改变所指对象的值
cptr = pi; // 也可以在定义的时候初始化
指向const对象的指针可以指向非const对象
注意:不能保证cptr指向的对象的值一定不可修改!它完全有可能指向一个非const对象,将它赋给普通指针就可以改
double dval = 2.34; // 非const
cptr = &dval; //可以指向一个非const对象,但仍不可通过cptr来修改
const void *cpv = pi; // ok ,不能使用void*指向const对象,必须使用const void *类型的指针保存const对象的地址
t5_33: 将一个指向const对象的指针成void *
const string *ps;
void *pv;
pv = (void *)ps; // error!!!应该按下面写
pv = static_cast
把ps变成string*之后再转成void*,就是说,先去掉const特性
static_cast
//============找回原来指针值,与上例t5_33没关系
对于void *pv;
小括号强制转化pc = (char*)pv; // 找回原来指针值
或者pc =static_cast
b) const指针 //不能再指向别的对象,但能改变所指向对象的值
int ival = 32;
int *const ptr = &ival; // 从右向左读:指向int对象的const指针
*ptr = 34; 则ival的值也变成34了。
但ptr = &ival2; //error指针值不可变
c) typedef与const指针
typedef string *pstring;
const pstring cstr; // 这里不要简单的理解为const string *cstr, 这里const限定的是指针,而不是所指对象!! string* const cstr 最好写成pstringconstcstr; 好理解
10. t4_19:
const int ival; // error必须初始化
int *const ptr1; // error必须初始化
const int *ptr2; // ok,可以不用初始化
const int ival2 = 23; // 不能由指向非const对象的指针来指向它
int *const ptr2 = &ival2; // error,指向非const对象的指针不能指向const对象
11. p113讲:C风格字符串尽量少用,因为它常常带来很多错误,是导致大量安全问题的根源!
c-style character string
const char *cp = “hello world”;
while (*cp) {
//TODO..
++cp;
}
c风格字符串的库函数:#include
strlen(s)// 返回有效长度
strcmp
strcat
strcpy
strncat(s1, s2, n) // 前n个字符, 返回s1
strncpy(s1, s2, n)
实参指针不能是空指针,即指向0或NULL的指针。且指向以NULL结束的字符数组中的指针
必须确保目标字符串足够大,否则造成安全漏洞
char ca[] = {‘a’,’b’,’c’};
strlen(ca); // error!!!
使用strn比strcat strcpy更安全
strncat连接时,第二个字符串要覆盖掉第一个字符串的NULL
12. t4_25
两个string类型的比较可以使用>,< 而两个C风格字符串比较使用strcmp
它们读入:t4_26
string str;
cin >> str;
size_t str_size = 90;
char ch[str_size];
cin >> ch;
13. 创建动态数组, 在运行时确定长度
int *p = new int[3]; //内置类型分配空间 无初始化
int *p = new int[4](); 值初始化为0, 但没有初始化值列表的形式
const int *p = new const int[22](); //最后的()是必须的,不然就没有初始化const元素 ,虽然vs2005通过
string *pstr = new string[4]; // 分配空间 使用默认构造函数初始化
const char *p4 = s2.c_str();
14. int arr[0]; // error,不能为0
int *parr = new int[0]; // ok, 可以为0,但是parr不能进行解引用操作
15. delete[] parr; // 漏掉[]编译器不会发现错,运行时错
16. 使用string比C风格字符串的程序执行速度要快很多
17. 可以使用C风格字符串对string对象赋值或初始化
C风格字符串可以作为左操作数与string对象相加
char*ch = "hello ";
strings("good");
strings2;
s2 =ch +s; // ok
s2= “abc” + ch + s; // error
18. 使用数组初始化vector
in iarr[6] = {0,1,2,3,4,5};
vector
19. t4_34
vector
string str;
while (cin >> str) { // 读入一组string放在vector中
ivec.push_back(str);
}
//下面是将vector这组string拷到一个字符指针数组中。
char **pa = new char*[ivec.size()];
size_t k = 0; // #include
for(vector
char *p = new char[(*it).size() +1]; // 分配空间
strcpy(p, (*it).c_str()); //拷贝 #include
pa[k++] = p;
}
20. int *p[3];
int (*p)[3];
21. 使用typedef简化多维数组
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
typedef iint int_array[4];
int_array *p; // p也可以定义为 int (*p)[4];
for (p = ia; p != ia + 3; ++p;) {
for(int *q = *p; q != *p+ 4; ++q) {
cout<< *q << endl;
}
}
1. 指针不能转化为浮点类型
2. short [-32768,32767]
unsigned short: [0, 65535]
int: [-21.4亿, 21.4亿]
3. -21 % -8 值为 -5
4. 对于位操作,由于系统不能确保如何处理符号位,所以建议使用unsigned数!
左移补0
unsigned char ch = 0277;
ch = ~ch; 全部取反 flip操作
ch << 1; // 左移,右边补0
//右移左边补0; 右移个数要小于8(操作数的位数)
对于signed,右移补0还是符号位根据机器而定
bitset优于整数数据的低级直接位操作
bitset_quiz1.set(27);
int_quiz1 |= 1UL << 27;
bitset_quiz1.reset(27);
int_quiz1 &= ~(1UL << 27);
<<优先级高于<
所以 cout << (10 < 32); // ok
cout << 10 < 32; // error
5. i + j = k; // error
int i;
const int ci = i; // ok, 将来ci无意义
6. int a; int *p;
a = p = 0; // error 不能将指针类型赋给int
7. a += b; a计算了一次, a = a + b; a计算了两次
8. 使用++i而不用i++:
工作量少啊,后置操作要先保存操作数原来的值,以便返回未加1之前的值作为操作的结果。
9. .>* 取成员操作大于解引用操作
(*pstu).name = “leiming”;
pstu->name = “leiming32”;
++>* ++也大于解引用操作
*iter++ 相当于 *(iter++)
10. t5_18:定义一个string指针的vector,输入内容,然后输出
vector
string str;
cout << "enter strings:" ;
while (cin >> str) {
string *pstr = new string; // 必须新分配一块地方
// string *pstr = &str;不对,指向同一空间啦
*pstr = str; // 每个指针单独指向一块空间,赋值
vec.push_back(pstr);
if(vec.size()==4) cout <<"vec[1]:" << *vec[1] << endl;
}
for (vector
cout <<**it << ", len: " << (**it).size()<< endl;
}
11. 三种语法:
sizeof (expr)
sizeof (type name) // 必须加括号
sizeof expr : 并没有计算表达式的值
Sales_item item, *p;
sizeof item;
sizeof *p; // 并没有解引用操作, p指向一块没有意义 的地址
sizeof (Sales_item)
12. 逗号表达式的是其最右边表达式的值
13. int *p = new int; //ok 没有初始化
int *p = new int();// ok,p所指的对象内容初始化为0
int *p = 0;
delete p; // ok,可以删除空指针
delete p;之后,删掉了它所指向的对象,p此时成了悬挂指针,应该p = 0;
如果你使用delete是未加括号,delete便假设删除对象是单一对象。否则便假设删除对象是个数组。delete[] 相比 delete 会多做一件事,就是在删除这个内存块前,析构每个元素,特别是对于对象数组
万一需要重载 new, delete,new[], delete[], 我们必须全部都重载,而不能仅仅重载 new, delete, 然后希望 new[],delete[] 会调用它们
string*stringPtr1 = new string;
string *stringPtr2 = newstring[100];
……
delete stringPtr1;
delete [] stringPtr2;
如果你对着stringPtr1使用“[]”形式,其结果未定义。如果你对着stringPtr2没有使用“[]”形式,其结果亦未定义。犹有进者,这对内建型别如int者亦未定义,即使这类型别并没有destructors。
游戏规则很简单,如果你在调用new时使用了[],则你在调用delete时也使用[],如果你在调用new的时候没有[],那么你也不应该在调用时使用[]。
14. a[][5]; // 第一维可缺省 ,5不可省
15. 动态创建const对象:
const int *pci = new const int(32); // 定义的时候必须初始化,初始化后就不可以修改 pci指向一个const对象,所以它也要是const的
1) 普通指针不能指向const对象;
void reset(int *pi) //实参不能是指向const int对象的指针
// 为了保护所指向的值,一般地将定义为指向const对象的指针
void reset(const int *cpi);
2) 指向const对象的指针可以用普通指针来初始化
3) 指向const对象的指针可以指向非const对象,但不能修改它,可以赋给非指向const对象的指针来改;
4) 可以使用普通指针初始化const指针
5) 不能用const对象来初始化非const的引用
6) 可以使用const对象来初始化非const对象,反之亦然
所以const形参的时候,实参可以是const对象也可以是非const对象
C语言中,const形参与非const形参没有区别,为了支持与C的兼容,编译器将const形参看作是普通的形参,所以const不能看作是函数重载的根据
const string *pcs = new conststring; // ok
内置类型的以及没有默认构造函数的类型必须显式初始化
可以删除const指针:
delete pci; // ok
delete pcs; // ok
16. t5_30:
vector
vector
//右边返回的是一个指向元素类型是vector
17. 算术转换:
char, signed char, unsigned char, short, unsignedshort如果能包容在int内则转换为int,否则转成unsigned int(主要针对unsiged short)
bool 提升为int, false转为0, true转为1
inta = 2 + true;
cout<< a << endl; // 3
向上提升:int->unsigned int->long->unsignedlong
signed int + unsigned int,则signed int会转为unsigned int
unsignedchar b = -1; // 1000 0001,符号位不变所有位取反,再加1 : 1111 1111
cout<< (int)b << endl; // 255
inti = 32;
constint &iref = i; // 使用非const对象初始化const对象的引用,将非const对象转成const对象
i= 33;
//iref= 34; // error
cout<< iref << endl; //ok, iref要随着i而变 = 33
const int *cpi = &i; // 非const对象的地址(或非const指针)转成指向相关const类型的指针
int *pi = &i;
const int *cpi2 = pi; // 可以使用非const指针初始化const指针
18. 显式转换
1) 什么时候使用
i. 强制转换
ii. 存在多种类型转换时,需要选择一种特定的类型转换
2) cast-name
dynamic_cast
运行时识别指针或引用所指向的对象
3) const_cast
int*cpi3 = const_cast<int *>(cpi);// 是指针或对象
//*cpi= 100; // error
*cpi3= 43;
cout<< "i=" << i << endl;
const int ci = 32;
int ival = const_cast<int>(ci);//error不能从const int转int
int &iref2 = const_cast<int&>(ci); // 可以将const转换掉
iref2 = 35;
cout << ci << endl; // 32
cout << iref2 << endl; // 35
4) static_cast
int ival = static_cast
可以找回放在void*中的地址值:
double dval = 2.33;
void *p = &dval;
double *pd = static_cast
5) reinterpret_cast
为操作数的位模式提供较低层次的重新解释,依赖于机器,要阅读编译器细节。
int *ip;
char *pc = reinterpret_cast
string str(pc); // compile error
6) 最后:避免使用强制类型转换,也能写出好程序
使用const_cast抛弃cast属性意味着设计缺陷
有些强制转换有危险
如果必须要用,则限制强制转换值的作用域,记录所有假定涉及的类型
7) 在新编译器上写程序,最好不要用()式的老强制转换
19. dval = ui * fval; // ui先转成float再乘,结果是double