// 1 ///////////////////////////////////////////////////////////////////////
// 常量指针:,指针可修改,变量不可修改(只是对于p指针来说,不能通过p指针来修改其指向的变量的值,但如果有其他指针ptr同时指向此变量,则可通过ptr修改变量的值)
// 也即,变量的不可修改只是对于指针p而言,其他指针可以正常修改变量的值 参见Effective C++ —— 让自己习惯C++(一)条款03 const int *p; int const *p; // 指针常量:指针不可修改(指针的指向不可修改,也即不能将p重指向其他变量),变量可修改(可通过指针p修改变量的值,当然了,也肯定可以通过其他指针修改此变量的值) int *const p; // 指针解引用之前,一定确保已被初始化为一个确定/合适的地址 int *p; // 分配了指针p的内存,但并没有初始化,指针所指向的数据的内存不知道 // 指向数组的指针:指向一个数组的指针 int *p = new int[5]; int a[5] = {1, 2, 3, 4, 5}; int (*p)[5] = &a; // 指针数组:数组的元素是地址(指针) int b1,b2,b3,b4,b5; int *ptr[5] = {&b1, &b2, &b3, &b4, &b5}; // 指向指针数组的指针 int* (*p)[5] = &ptr; // 指针函数: 函数返回类型是某一类型的指针 int *f(x,y); // 函数指针: 指向函数的指针变量,即本质是一个指针变量 int (*f) (int x); /* 声明一个函数指针 */ f=func; /* 将func函数的首地址赋给指针f */ // 函数指针数组:数组的每一个元素都是一个函数指针,符合函数指针数组所规定的返回值和参数标志 double (*f_attr[])(double, double); // 指向函数指针数组的指针 double (*(*f_attr)[])(double, double);
#include <stdio.h> int main(int argc, char *argv[]) { int a = 12; int b = 13; // 常量指针,ptr1指针的指向可以修改 // 但对于指针ptr1,不能通过其修改所指变量a // 可以通过其他方式修改变量a,变量a只对于ptr1是常量 const int* ptr1 = &a; ptr1 = &b; // ok // *ptr1 = b; // Error:assignment of read-only location a = b; // ok // 指针常量,ptr2指针的指向不可修改 // 但指针所指向的变量可以被修改 int* const ptr2 = &a; // ptr2 = &b; //Error:assignment of read-only variable `ptr2' *ptr2 = b; // ok return 0; }
/* ============================================================================ Name : mytestPC.c Author : yangxt Version : Copyright : Your copyright notice Description : Hello World in C, Ansi-style ============================================================================ */ #include <stdio.h> #include <stdlib.h> int nValue = 0; int main(void) { // 栈区向地址减小的方向增长(向下生长) int* ip1, * ip2, ivalue; char* cp1, * cp2, cvalue; ip1 = (int*)0x500; ip2 = (int*)0x518; printf("ip2=%d, ip1=%d, ip2 - ip1 = %d ", ip2, ip1, ip2 - ip1); printf("sizeof(ip1)=%d ", sizeof(ip1)); ivalue = ip2 - ip1; // int 类型指针运算以int的长度[4Bytes]为一个单位 cp1 = (char*)0x500; cp2 = (char*)0x518; printf("cp2=%d, cp1=%d, cp2 - cp1 = %d ", cp2, cp1, cp2 - cp1); printf("sizeof(cp1)=%d ", sizeof(cp1)); // 无论什么类型的指针,对于指针本身来说,它都是占据4Bytes cvalue = cp2 - cp1; // char 类型指针运算以char的长度[1Byte]为一个单位 printf("%d, %d ", ivalue, cvalue); //output: // ip2=1304, ip1=1280, ip2 - ip1 = 6 // sizeof(ip1)=4 // cp2=1304, cp1=1280, cp2 - cp1 = 24 // sizeof(cp1)=4 // 6, 24 return EXIT_SUCCESS; }
// 2 ///////////////////////////////////////////////////////////////////////
临时变量/引用参数和const
--对于一个函数:
(1)如果其接收常规引用参数,其意图在于修改变量,此时C++会禁止创建临时变量;(因为如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现)
(2)如果其接收const引用,其意图在于不修改只使用变量,此时在满足下面两个条件之一的情况下,C++便会为变量创建临时变量:
1. 实参的类型正确,但不是左值;
2. 实参的类型不正确,但可以转换为正确的类型.
--应尽可能使用const引用:
1. 使用const可以避免无意中修改数据的编程错误;
2. 使用const使函数能够处理const和非const实参,否则将只能接受非const数据;
3. 使用const引用使函数能够正确生成并使用临时变量;
--引用非常适合用于结构和类,可以避免大量拷贝导致的资源消耗;同时也可以令返回值为引用,需要注意的是要避免返回函数终止时不再存在的内存单元引用.
--对于返回类型,常规(非引用)返回类型为右值,不能通过地址访问;而引用返回类型为左值,可以被修改和取地址;当然,const引用返回不能被修改.
--临时变量和函数的非引用返回值都是右值,不可取地址,不可被赋值.
函数的返回值,如果是内置类型的非引用返回值,那么不可被修改(修改(赋值)右值不合法);但如果是自定义类型的非引用返回值呢?<Effective C++>(3e)条款03中提供了一个可修改的例子,但它同时也表明:一个"良好的用户定义类型"会避免无端地与内置类型不兼容.所以普遍来说:改动函数非引用返回值从来就不合法.
// 3 ///////////////////////////////////////////////////////////////////////
类成员函数的重载、重写、和覆盖区别
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
// 4 ///////////////////////////////////////////////////////////////////////
5种变量存储方式
存储描述 持续性 作用域 链接性 如何声明
-------- -------- -------- ------- -----------
自动 自动 代码块 无 在代码块中
寄存器 自动 代码块 无 在代码块中,使用关键字register
静态,无链接性 静态 代码块 无 在代码块中,使用关键字static
静态,外部链接性 静态 文件 外部 不在任何函数内
静态,内部链接性 静态 文件 内部 不在任何函数内,使用关键字static
// 5 ///////////////////////////////////////////////////////////////////////
切记:声明类只是描述了对象的形式,并没有创建对象.因此,在创建对象前,将没有用于存储值的空间.
所以,对于需要在类中声明常量,可以采用枚举,同时,针对于静态常整型变量,也可以在类声明时直接赋值;
注意:在类中声明枚举并不会创建类数据成员.也就是说,所有对象中都不包含枚举.
同时,C++11提供了新枚举--作用域内枚举.常规枚举会自动转换为整型,但是作用域枚举不能隐式地转换为整型.
// 6 ///////////////////////////////////////////////////////////////////////
类的自动转换和强制类型转换
1. 在C++中,只有接受一个参数的构造函数才能作为转换函数(如果有2个参数,第二个参数提供默认值,同样可以用来作为转换函数).可以通过使用explicit关闭这种特性.
2. C++类的转换函数(将类对象转换成基础类型):
(1)转换函数必须是类方法;
(2)转换函数不能指定返回类型;
(3)转换函数不能有参数;
例如,转换为typeName类型的函数原型如下:
[explicit] operator typeName()
注:typeName指出了要转换成的类型,因此不需要指定返回类型.转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值.因此,函数不需要参数.
在C++11 中,可以通过explicit将转换运算符声明为显式.
// 7 ///////////////////////////////////////////////////////////////////////
/* * stringImpl.cpp * * Created on: 2019年11月18日 * Author: yangxt */ #include <iostream> #include <string.h> //已知类String的原型如下,请编写String的4个函数: class String { public: String(const char *str = NULL);// 普通构造函数 String(const String &other);// 拷贝构造函数 String(); // 无参构造函数 ~String(void);// 析构函数 String & operator = (const String &other);// 赋值函数 private: // 类含有指针成员,需要进行深拷贝 char *m_data;// 用于保存字符串 }; // 无参构造函数 String::String() { // 无参构造函数,需要置m_data==NULL m_data = NULL; } // String的析构函数 String::~String(void) { delete[] m_data; // 或delete m_data; } //普通构造函数 String::String(const char *str) { if (str == NULL) { m_data = new char[1];// 对空字符串自动申请存放结束标志' '的,加分点:对m_data加NULL判断 *m_data = '