C++学习之路
C++基础,纯手打
一、理论知识
gcc 编译步骤
hello.c
- 预处理
g++ -E:展开宏,头文件,替换条件编译,删除注释,空行,空白 - 编译
g++ -S:检查语法规范 - 汇编
g++ -c:将汇编指令翻译成机器指令 - 链接:数据段合并,地址回填
直接
g++ -c
gcc命令参数-I: 指定头文件的目录-g: 编译带调试信息的可执行文件-O: 优化源代码(编译慢,文件小,运行效率高)-l/-L:指定库文件/指定库文件路径-Wall:打印警告信息-w:关闭警告信息-std=c++11:设置编译标准-o:指定输出文件名-D:定义宏
静态库/动态库(shared库)
静态库:可执行程序运行钱就已经加载到执行代码中。成为执行代码的一部分
动态库: 在执行程序启动时加载到执行程序中,可以被多个程序共享使用(建议使用)
静态库制作及使用:
ar rcs libmyl ib.a file.o:以lib开头 .o结尾
- 将.cpp生成.o文件:
g++ -c xxx.cpp xxx1.o - 使用ar制作静态库:
ar rcs libxxx.a xxx1.o xxx2.o - 编译静态库到可执行文件中:
g++ new.cpp libxxx.a -o a.out
动态库制作及使用:
- 将.cpp生成.o文件(生成与位置无关的代码 -fPIC):
g++ -c xxx.cpp -o xxx.o -fPIC - 使用
g++ -shared制作动态库:g++ -shared -o libxxx.so xxx1.o xxx2.o - 编译可执行程序时,指定所使用的动态库(去掉lib前缀:libmylib.so->-lmylib):
g++ test.c -o a.out -lmylib -L./lib - 运行a.out出错:需要指定动态链接库(通过设置环境变量
LD_LIBRARY_PATH=动态链接库地址或者配置/etc/ld.so.conf文件)
通过
ldd a.out查看调用的动态链接库
GDB调试工具
略
Makefile及Cmake
CMake
基本语法特征
基础语法格:指令(参数1 参数2)
参数使用括弧
参数之间使用空格活分号分开
指令是大小写无关的,参数和变量是大小写相关的**
1 | set(HELLO hello.cpp) |
变量使用${}方式取值,但是在IF控制语句中是直接使用变量名
重要指令和常用变量
cmake_minmum_required:指定CMake的最小版本要求
1 | # cmake_minmum_required(VERSION versionnumber [FATAL_ERROR]) |
project:定义工程名称,并可指定工程支持的语言
1 | # project(projectname [CXX][C][Java]) |
set:显式的定义变量
1 | # set(VAR [VALUE][CACHE TYPE DOCSTRING[FORCE]]) |
include_directories:向工程添加特定的头文件搜索路径
1 | # include_directories([AFTER|BEFORE][SYSTEM]dir1 dir2...) |
link_directories:向工程添加特定的库文件搜索路径
1 | # link_directories(dir1 dir2...) |
add_library:生成库文件
1 | # add_library(libname[SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL] source1 source2...) |
add_compile_options:添加编译参数
1 | # add_compile_options(<option>) |
add_execuable:生成可执行文件
1 | # add_execuable(exename source1 source2 ...) |
taget_link_libraries:为target添加需要链接的共享库
1 | # taget_link_libraries(target library1<debug|optimized> library2 ...) |
add_subdirectory:向当前工程中添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
1 | # add_subdirectory(source_dir [binary_dir][EXCLUDE_FROM_ALL]) |
add_source_directory:发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
1 | # add_source_directory(dir VARIABLE) |
常用变量:
CMAKE_C_FLAGS: gcc编译选项CMAKE_CXX_FLAGS: g++编译选项
1 | # 在CMAKE_CXX_FLAGES编译选项后最佳-std=c++11 |
CMAKE_BUILD_TYPE: 编译类型(Debug,Release)
1 | # 设定编译类型为debug,调试时需要选择debug |
1 | CMAKE_SOURCE_DIR |
- 三个变量内容一致。都是工程顶层目录
- zaiin source build时,跟CMAKE_BINARY_DIR等变量一直
- PROJECT——SOURCE——DIR 略有不同。可这样理解
CMAKE_C_COMPILER:指定编译器CMAKE_CXX_COMPILER:指定C++编译器EXECUTABLE_OUTPUT_PATH:可执行文件输出的存放路径LIBRARY_OUTPUT_PATH:库文件输出的存放路径
使用CMake编译工程
项目主目录存在一个CMakeKists.txt
两种编译规则:
- 包含源文件的子文件夹包含CmakeLsit.txt,主目录的CmakeLists.txt通过
add_subdirectory添加子目录即可; - 包含源文件的子文件夹未包含CMakeLists.txt,子目录便以规则体现在主目录的CmakeLists.txt中
流程:
- 编写CmakeLists.txt。
- 执行命令cmake PATH生成Makefile(PATH是顶层CmakeList.txt所在的目录)。
- 执行命令make进行编译。
构建方式:
- 内部构建:与源文件同级
- 外部构建:不同的目录(推荐)
例:使用最简单的CMake编译HelloWorld
hello.cpp:
略
在.cpp同级目录创建CMakeLists.txt:
1 | cmake_minimum_required(VERSION 3.0) |
执行 :cmake .:
控制台信息
1 | goth@goth-ubuntu:~/Project/CPPwork/testHello$ cmake . |
执行make编译代码:
1 | goth@goth-ubuntu:~/Project/CPPwork/testHello$ make |
结果:
外部构建:
bash:
1 | mkdir build |
1 | goth@goth-ubuntu:~/Project/CPPwork/testHello/build$ cmake ../ |
bash:make:
1 | Scanning dependencies of target hello_cmake |
生成文件目录:

编译略复杂项目:
结构如下:
1 | goth@goth-ubuntu:~/Project/CPPwork/test$ tree |
结构如下:
二、C++基础
命名空间
代码:
1 | namespace A{ |
1.命名空间下可以存放:函数、变量、结构体、类
2.命名空间必须定义再全局作用域下
3.命名空间可以嵌套命名空间
4.命名空间是开放的,可以随时添加内容,相同命名空间名称会进行合并
5.无名/匿名空间:相当于static int c;static int d;
1 | namespace{ |
6.命名空间可以取别名
1 | void test04(){ |
using关键字
using 声明注意避免二义性;
1 | int a = 10; |
using编译指令
1 | using namespace A; |
1 | int a = 10; |
1 | int a = 10; |
C++对C的增强
1.全局变量检测增强
1 | int a; |
1 | int a; |
2.参数类型增强,函数检测增强,返回值检测增强,函数调用参数检测异常
C:
1 | int getNum(w,h){ |
C++:
1 | int getNum(w,h){ |
1 | int getNum(int w, int h){ |
1 | int getNum(int w, int h){ |
C:
1 | void test(){ |
C++:
1 | void test(){ |
3 类型转换检测增强
1 | void test(){ |
1 | void test(){ |
4 struct 增强
C:
1 | struct Person{ |
C++
1 | struct Person{ |
1 | Persion p1//错误,必须加struct关键字 |
1 | Persion p1//正确,可不加struct关键字 |
5 bool类型增强(C中没有bool类型)
bool 类型 非0转为1
6 三目运算符增强
c++返回的是变量
c返回的是值
C:
1 | (a>b?a:b)=100;//错误:a=10,b=20则含义为 20=100 |
C++
1 | (a>b?a:b)=100//正确 |
7 const增强
- C语言中的局部变量const是伪常量,编译器会分配内存
C++中,const不会分配内存:const int b = 20;
C++中使用地址强改会开辟新的内存,不会影响const的值 - C语言中 const 默认外部链接
C++中的const默认内部链接,只能在当前文件中使用
若想使用外部链接定义:extern const int a = 10
调用:extern const int a
const内存情况:
分配内存都可以绕过编译器,进行修改
- const基本数据类型不分配内存 取地址会分配临时内存
- extern编译器也会给consat变量分配内存
- 用变量初始化const变量,会分配内存
int a = 10const int b = a - 自定义数据类型 加 const也会分配内存
尽量使用
const代替#define
引用
引用的语法与注意事项
&:=号左侧为引用。=号右侧为取地址。
1 引用语法:
Type &别名 = 原名
2 引用必须初始化,引用初始化后不可以修改只能赋值
3 引用必须引一块合法的内存空间
4 不要返回局部变量的引用,除非局部变量为静态,如果函数的返回值为引用,那么这个函数调用可以为左值
1
2
3
4
5
6
7
8
9 >int& doWork(){
int a = 10;
//static int a = 10;//正确
return a;
>}
>int &ret = doWork();//上函数局部变量a已经被销毁啦,第一次使用正常。第二次使用将错误。
>doWork() = 1000;//返回值为引用,调用为左值。a=1000;5 对数组建立引用
1
2
3
4
5
6
7
8
9 >//方法1:
>int arr[10];
>for (int i = 0;i<10;i++){
arr[i] = i;
>}
>int (&parr)[10] = arr;//小括号不可省略
>//方法2:
>typedef int(ARRAYREF)[10];//一个具有10个元素的int类型的数组
>ARRAYREF &pArr2 = arr;
引用的本质
引用的本质是指针常量
参数的传递方式
值传递
略:原值无变化
地址传递
1 | void mySwap(int *a,int *b) { |
引用传递
//等价于地址传递,只是将原位置起个新名字传递过去
1 | void mySwap(int &a,int &b) { |
指针的引用
用一级指针引用代替二级指针
传统指针方式:(难理解)
1 | //定义结构体 |
利用指针以用开辟空间
1 | //定义结构体 |
常量引用
使用场景:修饰形参为只读
const int a = 10:会分配内存
1 | //int &ref = 10;//引用了不合法的内存,不可以 |
1 | //以用使用场景 用来修饰形参 |
const 引用场景;
常量引用主要用在函数的形参,尤其是类的拷贝/复制构造函数
优势:
- 引用不产生新的变量,减少形参与实参传递的开销
- 由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。希望实参随着形参改变使用一般引用。不希望改变则使用常引用。
类
系统会提供3个默认函数:默认构造、拷贝构造、析构函数
设计抽象类
1 | class 类名{ |
使用:
类名 对象名;
对象操作
分文件编写
.h写类成员函数的声明.cpp写成员函数的实现
Point.h
1 |
|
Point.cpp
1 |
|
初始化和清理
初始化:构造函数
清理:析构函数
1 | //未提供构造函数 |
创建Person类,并提供构造函数
分类:
按照参数类型进行分类:无参构造函数、有参构造函数
按照类型分类:普通构造函数、拷贝函数
1 |
|
构造函数调用:
1 | //括号法调用 |
拷贝函数调用的方式
1 用已经创建好的对象初始化新的对象
1 | Person p1(1); |
2 以值传递的方式给函数参数传值
1 | void doWork(Person p1){//Person p1 = Person(p);//使用了拷贝构造 |
3 以值方式返回局部对象
1 | Person doWork(){ |
构造函数的调用规则
当我们提供了有参构造函数,系统不会再提供默认构造函数,需要重写无参构造函数,仍然提供默认拷贝构造函数
当我们提供了拷贝构造函数,系统不会再提供其他构造,析构函数依然提供
深拷贝和浅拷贝
浅拷贝(系统默认拷贝):拷贝值
如果属性里有指向堆区空间的数据,那么简单的浅拷贝会导致重复释放内存的异常
深拷贝:拷贝数据
初始化列表的基本使用
1 | Person(int a,int b,int c):A(a),C(c){ |
等同于:
1 | Person(int a,int b,int c){ |
默认参数初始列表
1 | Person():A(10),B(20),C(30){ |
类作为类成员的案例
visualstudio:”类对象作为类成员”
类对象作为类成员时,构造顺序先将类对象–构造。然后构造自己,析构顺序相反
explicit关键字
防止隐式类型转换
new运算符 动态对象创建
malloc函数的问题:
- 程序员必须确定对象的长度。
- malloc返回的void指针.C++不允许将void赋值给其他任何指针,必须强转。
- malloc可能申请失败,所以必须判断返回值来确保内存分配成功
- 用户使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数由编译器调用),用户可能忘记调用初始化函数。
new使用:
1 | Animal a1;//栈区开辟空间 |
所有new出来的对象,都会返回该类型的指针。malloc返回void*还要强转
new 会调用构造函数,malloc不会
delete释放堆区空间
void *接受指针导致无法delete
1 | void *a = new Animal;//无法 |
通过new开辟数组,一定会调用默认构造函数所以一定要提供默认构造
1 | Animal *aArray = new Person[10];//构造函数调用10次 |
静态成员变量和静态成员函数
静态成员变量
- 编译阶段分配内存
- 所有对象共享数据
- 对象访问,类名访问
- 具有权限控制
- 类内声明,类外初始化
- 可以被普通方法访问
静态成员变量需要类内声明,类外初始化实现。这样便可以使用类名访问其静态属性。私有权限无法访问
1 | class Person { |
静态成员函数
- 对象访问,类名访问
- 具有权限控制
- 可以被普通方法访问
1 | ```C++ |
静态成员变量实现单例模式
目的:为了让类只有一个实例,实例不需要自己释放
- 将默认构造和拷贝构造私有化
- 内部类维护一个对象指针
- 私有化唯一指针
- 对外提供
getinstance方法来访问这个指针
例1:主席单例
1 | class ChairMan { |
封装
C语言属性和行为分开处理啦,类型检测不够
C++中封装严格类型转换检测,让属性和行为绑定在一起。
C++ 有权限控制
struct 和class 一个意思,唯一的不同是默认权限不同。struct:默认权限publicclass:默认权限private
C++中的权限:
privateprotectedpublic
与java一样建议将内部属性设置为私有,提供get,set方法访问。
对象模型初探
- 空类大小为1,用char维护对象的地址
- 成员变量和成员函数是分开存储
- 非静态成员变量属于对象上。静态成员变量方法和非静态成员方法都不属于对象
this指针的使用
- 永远指向当前对象
- this解决命名冲突
*this指向对象的本体- 非静态成员函数才有this指针,可以加if判断,如果this为NULL就return
空指针访问成员函数
- 如果成员函数没有用到this,那么空指针可以直接访问
- 如果成员函数用的this指针使用到
常函数与常对象
常函数: void func() const {}
不允许修改指针指向的值
mutable关键字:修改的值常函数可以修改
常对象:不允许修改属性
- 不能调用普通函数,可以调用常函数
- 只能读不能修改属性。
const Person p1;
友元
全局函数作友元函数
- 友元关系不能被继承
- 友元关系是单向的,类A是B的友元,B不一定是A的友元
- 友元关系不具有传递性
类作友元类
目的:访问类中的私有成员属性friend goodGay::visit();
成员函数做友元函数
目的:访问类中的私有成员属性friend class A
函数
内联函数(inline function)
作用:用来代替C语言的宏函数
宏函数:
#define MyAdd(x,y)((x)+(y)):问题多,没有作用域
使用:
1 | inline void func(int a);//声明,无任何效果 |
写法:
1 | inline int func(int a){return a++;} |
类内部的内联函数
类内部的内联函数不是必须要求
inline关键字。类内部定义的成员函数默认加inline自动成为内联函数
内联函数的限制
inline关键字只是建议编译器,编译器也可能对非内联函数内联。编译器在以下条件下可能不会对函数进行内联编译:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
函数的默认参数
C++在声明函数原型的时可指定缺省值。
1 | int sum(int a,int b, int c=10){ |
- 如果默认参数在参数列表最后面,也就是说,每个有默认参数的参数后面的参数都有默认值
- 如果函数在声明时有了默认参数,那么函数实现时必须没有
函数的占位参数
没用但是不能没有
1 | void fun(int a,int){ |
可以有默认值
1 | void fun(int a,int = 1){ |
函数重载
与java类似但是返回值不能作为重载的条件!!!
在同一作用域下,函数名相同。参数列表(类型,个数,顺序)不同。(返回值不能作为条件)
当函数重载碰到默认参数,要避免二义性。
例子:
1 | void fun(int a,int b =1){ |
引必须要引合法的空间内存
const 耶可以作为重载条件
例子:
1 | void fun(int &a){//不能直接调用基本数据类型 |
原理: 编译器会根据不同参数编译成不同函数名
extern “C”浅析
使用C编译改代码
比如C语言实现show()方法:
s.h
1 | void show(); |
s.c
1 | void show(){ |
在C++中调用该方法有错误。因为C++函数存在重载,会改名字。
因此在C++中使用:
去掉头文件
1 | extern "C" void show();//按照C语言的方法做链接 |
当c代码太多使用如下方法:
在C代码中:
1 |
|
运算符重载
不要乱用符号重载
内置数据类型的运算符不能重载
operator[+][-][*][/]
如重载+运算符
1 |
|
重载左移运算符
重载左移运算符不能写在成员函数中
1 | class Person{ |
递增运算符重载
1 | class MyInterger{ |
指针运算符重载
智能指针:用来托管自定义类型的对象,让对象自动释放
赋值运算符重载
索引运算符重载
关系运算符重载
函数调用运算符重载
() 仿函数
继承
1 | class Son : [public] Base{}; |
- 基类中的private 子类都不可以继承
- public 继承 ,原public/protect不会改变
- protect 继承,public/protect都变为protect
- private 继承,public/protect都变为private
继承会继承私有成员,只是被编译器隐藏
1 | class Son : public Base{ |
子类创建对象,先调用父类构造,然后调用自身构造
析构顺序相反
子类不会继承父类的构造函数和析构函数
继承中的同名处理
成员属性:直接调用,先调用子类。调用父类需要作用域
成员函数:直接调用先调用子类,父类的所有同名函数都会被隐藏(参数列表无关),除非用作用域
继承中的静态成员处理
成员属性:直接调用,先调用子类。调用父类需要作用域
成员函数:直接调用先调用子类,父类的所有同名函数都会被隐藏(参数列表无关),除非用作用域
多继承
1 | class Son : [public] Base,[public] Base2{}; |
通过作用域解决二义性
菱形继承
同时拥有两个父类的属性
解决:
利用virtual虚基类
1 | //虚基类 |
sun1内部结构
- vbptr 虚基类指针
- 指向一张虚基类表
- 通过表找到偏移量
多态
定义:父类的引用和指针指向子类
静态联编和动态联编
静态联编:地址早绑定,编译阶段绑定好地址
动态联编:地址晚绑定。运行阶段绑定地址
使用virtual关键字
1 | //虚函数 |
1 | //纯虚函数 |
一个类使用了一个纯虚函数就是抽象类,无法实例化。子类必须实力化所有纯虚函数
虚析构
父类对象指向子类指针,父类的析构函数。可能对子类数据析构不干净。因此使用虚析构
1 | virtual ~类名(){}; |
纯虚析构
父类对象指向子类指针,父类的析构函数。可能对子类数据析构不干净。因此使用虚析构。需要类内生命,类外实现
1 | //在父类中 |
向下转型向上转型
向下转型:
基类转派生类:不安全
向上转型:
派生类转基类:安全
如果发生多态。向上转型向下转型总是安全的
C++模板
函数模板
普通函数可以进行饮食类型转换,隐式函数不可以隐式类型转换
如果出现重载,优先使用普通函数
如果想强制使用模板,可以使用空参数列表”<>例:swap<>(a,b)
如果模板函数更好匹配(如果同名普通函数会产生隐式类型转换),则优先使用模板函数
机制:
模板不是万能的,不能通用所有的数据类型
模板不能直接调用,需要生成模板函数
会对T替换后的类型进行二次编译
1 | template <class T>//告诉编译器,T是通用类型 |
函数模板的局限性:
用户自定义类型无法直接进行数值比较。
通过具体化自定义类型,解决上述问题。
优先匹配具体化
1 | template<class T> |
类模板
类模板不支持自动类型推导,有默认类型参数
1 | //Person p("小明",20);//不支持自动类型推导 |
查看类型名称:typeid(T).name()
继承中的类模板
- 如果基类是模板类,必须让子类告诉编译器,基类中的T到底是什么类型,否则无法分配内存,编译不通过。
class Child :public Base<int>
类模板的类外实现:
1 | template <class T1,class T2> |
类模板分文件编写(模板建议不要分文件编写):
将声明和实现写在一个类中。最后把后缀名改为.hpp
类模板与友元函数
类内实现:
1 | friend void printPerson(Person<T1,T2> &p){ |
类外实现:
1 | //提前让编译器看到类和方法 |
类型转换
静态转换
static_cast关键字
- 可以对基本类型转换
- 可以对父子类型转换
- 不可以对无关类型转换
1 | Base *base = NULL; |
动态转换
dynamic_cast关键字
- 无法转换基础数据类型
- 普通情况子类无法转父类
- 多态子类父类都可以使用动态转换
常量转换
const_cast
- 不能对非指针或者非引用进行转换
重新解释转换(不安全,不用)
reinterpret_cast
异常
1 | try{ |
栈解旋
从try开始到throw结束,所有栈上的对象都会被释放
异常的接口声明
//只抛出特定的异常
1 | void func() throw (int,double){ |
异常的生命周期
- 如果使用MyException e:会调用拷贝构造,多开销一份数据
- 如果使用MyException *e:没有使用
new会提前释放对象,使用new需要自己管理delete - 推荐使用 MyException &e:同一份数据
异常的多态使用
使用同一个接口打印不同的异常音系
使用系统的标准异常
使用:略
编写自己的异常类,需要继承exception,重写析构和what方法
三、C++标准库、模板库
STL六大部件
容器(Containers)
pair<long,string>(1,"2");
分配器(Allocators)
算法(Algorithms)
迭代器(Iterator)
适配器(Adapters)
仿函数(Functors)
四、问题总结
拷贝赋值
检测自我赋值
1. char str[] 和char* str的区别
1 | char* get_str(void){ |
char str[] = {“abcd”};定义了一个局部字符数组,尽管是数组,但它是一个局部变量,返回它的地址肯定是一个已经释放了的空间的地址。
此函数返回的是内部一个局部字符数组str的地址,且函数调用完毕后 此数组被销毁,所以你返回的指针也就指向一块被销毁的内存,这种写法是错误的。
1 | char* get_str(void){ |
char* str = {“abcd”};表示先定义个字符串常量,并将其地址赋给str。
此函数返回的是字符串常量的地址,而像这种字符串都是属于全局的,在编译的时候就已经分配了内存了,只有程序退出的时候才会被销毁,所以返回它的地址是没有问题的,但是你最好返回常量指针,因为你不能去改变字符串常量的值。
这个str就在栈里面,但后面的字符串在常量区,函数返回的时候,先将常量区的地址返回,然后作用域结束,释放str在栈里面的空间。。
1 | const char str[] = "abcd";//abc存储在堆栈中 |
准确的说,上面两个“abc”都是存储在静态存储区,即常量区。常量区是可读不可写的。所以任何试图对常量区进行写的操作都是非法的,当然了,这也不是一定不可写的,你可以采取某种渠道改变常量区的内存属性,比如改变pe相关节的属性就可以对常量区进行读写,当然了,这个目前可以忽略。。。
那么为什么str[] = “abc”; 可以写呢? 答案就在str[] = “abc”;会有一个额外的拷贝过程,即把常量区的 “abc”拷贝到栈内存去,所以就可以写了。
总结:
所有以” “或’ ‘包含的字符、字符串都是常量,应该是存储在堆上。
char *str = "xxxxx",str指向该常量地址。char str[] = "xxxxx",str在栈上申请空间。 将常量内容复制进来,所以是局部变量。首先,数组和指针是不同的数据类型,有本质的区别:
1 | char str[] = "abcd"; //sizeof(str) == 5 * sizeof(char) |
- 数组可以自动转型成指针,指针不能转成数组。
字符串等价于字符数组,而不等于字符指针。根据上一条,字符串可以自动转型成字符指针。
再然后,”abcd”叫做“字符串常量”,任何类型的常量都是右值(没有名字的临时变量),必须让”abcd”成为左值(有名字的变量),才能够修改”abcd”这个字符串
1 | char str[] = "abcd"; //等号两端是相同的数据类型,右值成为左值 |
char * str是存储在全局静态存储区,所以,虽然是局部变量但函数返回后依然可以拿到正确的值!
char str[] 是存储在栈上的,local variable ,函数返回后,OS就收回空间了,就不复存在了,所以,拿不到正确的结果!
char str[]=”name”;与char str[5];str=”name” 的不同之处在哪,能不能从内存分配的角度讲一讲,我知道数组名字是一个常量地址(指针),第一个为什么对,第二个为什么错?
第二个先定义了一个数组,要知道数组名str是数组分配到的空间的首地址,str=”name”应该是等号两边类型不匹配的错误。一般的常量应该没有内存地址的,除非有某个变量指向了该常量。
数组名是地址常量,那么常量当然不允许被重新赋值。
“name”是一个字符串常量他存储在常量存储区,只能用一个指针指向它却不允许改变:char*p;p=”name”;
一般情况下char str[]=”name”;数组是在栈上的空间由编译器分配,内容可以由用户改变。